Bugfixes from zOS testing
This commit is contained in:
@@ -104,6 +104,29 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Ensure clicking anywhere inside the frame brings focus to window and terminalPanel
|
||||
Toolkit.getDefaultToolkit().addAWTEventListener(event -> {
|
||||
if (event instanceof MouseEvent && event.getID() == MouseEvent.MOUSE_PRESSED) {
|
||||
MouseEvent me = (MouseEvent) event;
|
||||
Object src = me.getSource();
|
||||
if (src instanceof Component) {
|
||||
Window win = SwingUtilities.getWindowAncestor((Component) src);
|
||||
if (win == J3270App.this && !isAnyChildDialogActive()) {
|
||||
if (!J3270App.this.isFocused() || !J3270App.this.isActive()) {
|
||||
J3270App.this.toFront();
|
||||
J3270App.this.requestFocus();
|
||||
}
|
||||
boolean isMenu = (src instanceof JMenuBar) || (src instanceof JMenuItem) || (src instanceof JPopupMenu)
|
||||
|| SwingUtilities.getAncestorOfClass(JMenuBar.class, (Component) src) != null
|
||||
|| SwingUtilities.getAncestorOfClass(JPopupMenu.class, (Component) src) != null;
|
||||
if (!isMenu && terminalPanel != null && !terminalPanel.hasFocus()) {
|
||||
terminalPanel.requestFocusInWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, AWTEvent.MOUSE_EVENT_MASK);
|
||||
}
|
||||
|
||||
private boolean isAnyChildDialogActive() {
|
||||
@@ -694,6 +717,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
dialog.setInitialTls(haus.nightmare.j3270.config.Settings.getAutoConnectTls());
|
||||
dialog.setInitialVerifyCert(haus.nightmare.j3270.config.Settings.getAutoConnectVerifyCert());
|
||||
dialog.setInitialCodePage(haus.nightmare.j3270.config.Settings.getCodePage());
|
||||
dialog.setInitialGraphicsMode(haus.nightmare.j3270.config.Settings.getGraphicsMode());
|
||||
dialog.setVisible(true);
|
||||
|
||||
if (dialog.isConfirmed()) {
|
||||
@@ -730,6 +754,9 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
haus.nightmare.j3270.config.Settings.setAutoConnectTls(config.isUseTls());
|
||||
haus.nightmare.j3270.config.Settings.setAutoConnectVerifyCert(config.isTlsVerifyCert());
|
||||
haus.nightmare.j3270.config.Settings.setCodePage(config.getCodePage());
|
||||
if (config.getGraphicsMode() != null) {
|
||||
haus.nightmare.j3270.config.Settings.setGraphicsMode(config.getGraphicsMode());
|
||||
}
|
||||
|
||||
disconnect();
|
||||
|
||||
|
||||
@@ -234,7 +234,6 @@ public class HostStorage {
|
||||
if (map.containsKey("tn3270e")) h.setTn3270e(parseBool(map.get("tn3270e"), true));
|
||||
if (map.containsKey("graphicsMode")) {
|
||||
String gm = String.valueOf(map.get("graphicsMode"));
|
||||
if ("PROGRAMMED_SYMBOLS".equalsIgnoreCase(gm)) gm = "BOTH";
|
||||
h.setGraphicsMode(gm);
|
||||
}
|
||||
if (map.containsKey("codePage")) h.setCodePage(String.valueOf(map.get("codePage")));
|
||||
|
||||
@@ -6,6 +6,7 @@ import haus.nightmare.lib3270j.ConnectionConfig;
|
||||
import haus.nightmare.lib3270j.TerminalModel;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import javax.swing.border.TitledBorder;
|
||||
import java.awt.*;
|
||||
import java.util.List;
|
||||
@@ -226,6 +227,24 @@ public class ConnectDialog extends JDialog {
|
||||
graphicsCombo.setSelectedItem(haus.nightmare.j3270.config.Settings.getGraphicsMode());
|
||||
graphicsCombo.setFont(ThemeManager.getUiFont());
|
||||
ThemeManager.styleComboBox(graphicsCombo);
|
||||
graphicsCombo.setRenderer(new DefaultListCellRenderer() {
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||
setBorder(new EmptyBorder(4, 8, 4, 8));
|
||||
if (value instanceof haus.nightmare.lib3270j.graphics.GraphicsMode) {
|
||||
setText(((haus.nightmare.lib3270j.graphics.GraphicsMode) value).getDescription());
|
||||
}
|
||||
if (isSelected) {
|
||||
setBackground(ThemeManager.getSelectionBg());
|
||||
setForeground(ThemeManager.getSelectionFg());
|
||||
} else {
|
||||
setBackground(ThemeManager.getBgComponent());
|
||||
setForeground(ThemeManager.getFgMain());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
});
|
||||
contentPanel.add(graphicsCombo, gbc);
|
||||
|
||||
gridy++;
|
||||
@@ -664,7 +683,7 @@ public class ConnectDialog extends JDialog {
|
||||
|
||||
String gfx = "BOTH";
|
||||
if (graphicsCombo.getSelectedItem() != null) {
|
||||
gfx = graphicsCombo.getSelectedItem().toString();
|
||||
gfx = ((haus.nightmare.lib3270j.graphics.GraphicsMode) graphicsCombo.getSelectedItem()).name();
|
||||
}
|
||||
|
||||
String cp = "037";
|
||||
@@ -731,7 +750,9 @@ public class ConnectDialog extends JDialog {
|
||||
result.setLuName(lu);
|
||||
}
|
||||
if (graphicsCombo.getSelectedItem() != null) {
|
||||
result.setGraphicsMode((haus.nightmare.lib3270j.graphics.GraphicsMode) graphicsCombo.getSelectedItem());
|
||||
haus.nightmare.lib3270j.graphics.GraphicsMode gm = (haus.nightmare.lib3270j.graphics.GraphicsMode) graphicsCombo.getSelectedItem();
|
||||
result.setGraphicsMode(gm);
|
||||
haus.nightmare.j3270.config.Settings.setGraphicsMode(gm);
|
||||
}
|
||||
|
||||
Object cpSelection = codePageCombo.getSelectedItem();
|
||||
@@ -776,6 +797,14 @@ public class ConnectDialog extends JDialog {
|
||||
result.setProxy(pType, pHost, pPort, pUser.isEmpty() ? null : pUser, pPass.isEmpty() ? null : pPass);
|
||||
}
|
||||
|
||||
if (selectedProfileId != null) {
|
||||
SavedHost existing = HostStorage.findById(selectedProfileId);
|
||||
String pName = (existing != null && existing.getName() != null && !existing.getName().trim().isEmpty())
|
||||
? existing.getName() : host;
|
||||
SavedHost updated = buildCurrentSavedHost(pName);
|
||||
HostStorage.saveHost(updated);
|
||||
}
|
||||
|
||||
confirmed = true;
|
||||
dispose();
|
||||
}
|
||||
@@ -810,6 +839,12 @@ public class ConnectDialog extends JDialog {
|
||||
tn3270eCheckBox.setSelected(tn3270e);
|
||||
}
|
||||
|
||||
public void setInitialGraphicsMode(haus.nightmare.lib3270j.graphics.GraphicsMode mode) {
|
||||
if (mode != null && selectedProfileId == null && graphicsCombo != null) {
|
||||
graphicsCombo.setSelectedItem(mode);
|
||||
}
|
||||
}
|
||||
|
||||
public void setInitialCodePage(String cp) {
|
||||
if (cp != null && codePageCombo != null) {
|
||||
for (int i = 0; i < codePageCombo.getItemCount(); i++) {
|
||||
|
||||
@@ -25,8 +25,8 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
// Font and cell dimensions
|
||||
private Font terminalFont;
|
||||
private Font boldTerminalFont;
|
||||
private int cellWidth;
|
||||
private int cellHeight;
|
||||
int cellWidth;
|
||||
int cellHeight;
|
||||
private int fontAscent;
|
||||
private int fontDescent;
|
||||
private int padding = 4;
|
||||
@@ -74,6 +74,7 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
private int selectionStartRow = -1, selectionStartCol = -1;
|
||||
private int selectionEndRow = -1, selectionEndCol = -1;
|
||||
private boolean isDragging = false;
|
||||
private boolean isActivationClick = false;
|
||||
private static final Color SELECTION_COLOR = new Color(75, 110, 175, 102); // 40% alpha blend
|
||||
|
||||
// ========== Search Highlight state ==========
|
||||
@@ -117,7 +118,7 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
/**
|
||||
* Compute the horizontal render offset to center the grid within the panel.
|
||||
*/
|
||||
private int getRenderOffsetX() {
|
||||
int getRenderOffsetX() {
|
||||
int termCols = 80;
|
||||
if (client != null) termCols = client.getScreenBuffer().getDisplayCols();
|
||||
int gridW = termCols * cellWidth;
|
||||
@@ -128,7 +129,7 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
/**
|
||||
* Compute the vertical render offset to center the grid within the panel.
|
||||
*/
|
||||
private int getRenderOffsetY() {
|
||||
int getRenderOffsetY() {
|
||||
int termRows = 24;
|
||||
if (client != null) termRows = client.getScreenBuffer().getDisplayRows();
|
||||
int gridH = termRows * cellHeight;
|
||||
@@ -186,7 +187,16 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
MouseAdapter mouseHandler = new MouseAdapter() {
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
requestFocusInWindow();
|
||||
Window win = SwingUtilities.getWindowAncestor(TerminalPanel.this);
|
||||
boolean wasWindowInactive = (win != null && (!win.isActive() || !win.isFocused()));
|
||||
boolean wasPanelUnfocused = !hasFocus();
|
||||
isActivationClick = wasWindowInactive || wasPanelUnfocused;
|
||||
|
||||
if (wasWindowInactive) {
|
||||
win.toFront();
|
||||
win.requestFocus();
|
||||
}
|
||||
requestFocus();
|
||||
|
||||
// Right-click context menu
|
||||
if (SwingUtilities.isRightMouseButton(e)) {
|
||||
@@ -206,16 +216,7 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
row = Math.max(0, Math.min(row, displayRows - 1));
|
||||
|
||||
boolean isGraphic = client.getGocaDecoder() != null && client.getGocaDecoder().isGraphicsCursorActive();
|
||||
boolean isSelectableField = false;
|
||||
if (sb.isFormatted()) {
|
||||
int faPos = sb.findFieldAttribute(row * displayCols + col);
|
||||
if (faPos >= 0) {
|
||||
int fa = sb.getCell(faPos).fa & 0xFF;
|
||||
isSelectableField = haus.nightmare.lib3270j.protocol.DS3270Constants.faIsSelectable(fa) &&
|
||||
!haus.nightmare.lib3270j.protocol.DS3270Constants.faIsZero(fa);
|
||||
}
|
||||
}
|
||||
if (isGraphic || lightPenMode || isSelectableField) {
|
||||
if (isGraphic || lightPenMode) {
|
||||
clearSelection();
|
||||
} else {
|
||||
selectionStartRow = row;
|
||||
@@ -292,7 +293,9 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
row = Math.max(0, Math.min(row, displayRows - 1));
|
||||
int clickAddr = row * displayCols + col;
|
||||
|
||||
boolean isGraphic = client.getGocaDecoder() != null && client.getGocaDecoder().isGraphicsCursorActive() && client.getGraphicsPlane() != null;
|
||||
boolean isGraphic = client.getGocaDecoder() != null &&
|
||||
(client.getGocaDecoder().isGraphicsCursorActive() ||
|
||||
(client.getGraphicsPlane() != null && client.getGraphicsPlane().hasContent()));
|
||||
|
||||
if (isGraphic) {
|
||||
clearSelection();
|
||||
@@ -307,8 +310,11 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
py = Math.max(0, Math.min(py, gHeight - 1));
|
||||
client.getGocaDecoder().setGraphicCursorFromPixel(px, py);
|
||||
|
||||
int gx = client.getGocaDecoder().getGraphicCursorX();
|
||||
int gy = client.getGocaDecoder().getGraphicCursorY();
|
||||
int button = javax.swing.SwingUtilities.isRightMouseButton(e) ? 2 : 1;
|
||||
client.getInputProcessor().sendGraphicMouseAid(
|
||||
gx, gy,
|
||||
haus.nightmare.lib3270j.protocol.DS3270Constants.AID_ENTER,
|
||||
button,
|
||||
e.isShiftDown(),
|
||||
@@ -318,25 +324,57 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
return;
|
||||
}
|
||||
|
||||
// Automatic Light-Pen / Selectable Field detection on mouse click:
|
||||
boolean wasDrag = (selectionStartRow != -1 && selectionEndRow != -1 &&
|
||||
!(selectionStartRow == selectionEndRow && selectionStartCol == selectionEndCol));
|
||||
boolean activation = isActivationClick;
|
||||
isActivationClick = false;
|
||||
|
||||
if (wasDrag) {
|
||||
// Preserve drag/block selection so user can interact with menus or press Ctrl+C
|
||||
repaint();
|
||||
return;
|
||||
}
|
||||
|
||||
// Single click (not a drag):
|
||||
clearSelection();
|
||||
sb.setCursorAddress(clickAddr);
|
||||
|
||||
// If this was an activation click (e.g. clicking to resume focus),
|
||||
// do not fire field actions or send AIDs
|
||||
if (activation) {
|
||||
refreshScreen();
|
||||
return;
|
||||
}
|
||||
|
||||
// Automatic Light-Pen / Selectable Field detection on mouse click (HoD parity):
|
||||
if (sb.isFormatted()) {
|
||||
int faPos = sb.findFieldAttribute(clickAddr);
|
||||
if (faPos >= 0) {
|
||||
int fa = sb.getCell(faPos).fa & 0xFF;
|
||||
if (haus.nightmare.lib3270j.protocol.DS3270Constants.faIsSelectable(fa) &&
|
||||
!haus.nightmare.lib3270j.protocol.DS3270Constants.faIsZero(fa)) {
|
||||
clearSelection();
|
||||
if (client.lightPenSelect(clickAddr)) {
|
||||
refreshScreen();
|
||||
return;
|
||||
int size = displayRows * displayCols;
|
||||
int desPos = (faPos + 1) % size;
|
||||
char desAscii = (char) sb.getCell(desPos).ucs4;
|
||||
int desEbcdic = sb.getCell(desPos).ec & 0xFF;
|
||||
|
||||
boolean isToggleField = (desEbcdic == 0x6F || desAscii == '?' || desAscii == '\uFF1F' ||
|
||||
desEbcdic == 0x6E || desAscii == '>' || desAscii == '\uFF1E');
|
||||
boolean isEnterField = (desEbcdic == 0x50 || desAscii == '&' || desAscii == '\uFF06');
|
||||
boolean isProtected = haus.nightmare.lib3270j.protocol.DS3270Constants.faIsProtected(fa);
|
||||
|
||||
// Only auto-select toggle fields (?/>), enter fields (&), or unprotected space fields
|
||||
if (lightPenMode || isToggleField || isEnterField || !isProtected) {
|
||||
if (client.lightPenSelect(clickAddr)) {
|
||||
refreshScreen();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lightPenMode) {
|
||||
clearSelection();
|
||||
sb.setCursorAddress(clickAddr);
|
||||
boolean result = client.lightPenSelect(clickAddr);
|
||||
if (result) {
|
||||
refreshScreen();
|
||||
@@ -346,12 +384,8 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
}
|
||||
}
|
||||
|
||||
if (selectionStartRow == selectionEndRow && selectionStartCol == selectionEndCol) {
|
||||
sb.setCursorAddress(clickAddr);
|
||||
clearSelection();
|
||||
refreshScreen();
|
||||
return;
|
||||
}
|
||||
refreshScreen();
|
||||
return;
|
||||
}
|
||||
repaint();
|
||||
}
|
||||
|
||||
@@ -304,4 +304,40 @@ public class HostStorageAndProxyTest {
|
||||
dialog.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("SavedHost preserves all GraphicsMode settings across JSON serialization without clobbering to BOTH")
|
||||
public void testSavedHostGraphicsModeSerializationRoundtrip() {
|
||||
for (haus.nightmare.lib3270j.graphics.GraphicsMode gm : haus.nightmare.lib3270j.graphics.GraphicsMode.values()) {
|
||||
SavedHost host = new SavedHost(
|
||||
UUID.randomUUID().toString(),
|
||||
"Gfx " + gm.name(),
|
||||
"test.host.com",
|
||||
23,
|
||||
4,
|
||||
24,
|
||||
80,
|
||||
"",
|
||||
false,
|
||||
"TSO",
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
gm.name(),
|
||||
"037",
|
||||
"NONE",
|
||||
"",
|
||||
0,
|
||||
"",
|
||||
""
|
||||
);
|
||||
String json = HostStorage.serializeHostsJson(List.of(host));
|
||||
List<SavedHost> parsed = HostStorage.parseHostsJson(json);
|
||||
assertEquals(1, parsed.size());
|
||||
assertEquals(gm.name(), parsed.get(0).getGraphicsMode(),
|
||||
"Graphics mode " + gm.name() + " must be preserved and not coerced to BOTH");
|
||||
assertEquals(gm, parsed.get(0).toConnectionConfig().getGraphicsMode(),
|
||||
"ConnectionConfig must reflect GraphicsMode." + gm.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package haus.nightmare.j3270.ui;
|
||||
|
||||
import haus.nightmare.lib3270j.ConnectionConfig;
|
||||
import haus.nightmare.lib3270j.ConnectionState;
|
||||
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.awt.HeadlessException;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.awt.event.MouseMotionListener;
|
||||
|
||||
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class TerminalPanelSelectionTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("Selection drag over protected/intensified fields is preserved on mouse release (Bugs 3 & 4)")
|
||||
public void testSelectionOverProtectedFieldsAndReleasePreservation() {
|
||||
try {
|
||||
TerminalPanel panel = new TerminalPanel();
|
||||
panel.setSize(800, 600);
|
||||
|
||||
Telnet3270Client client = new Telnet3270Client(new ConnectionConfig("localhost", 23));
|
||||
client.getTelnetFSM().setConnectionState(ConnectionState.CONNECTED_3270);
|
||||
|
||||
ScreenBuffer sb = client.getScreenBuffer();
|
||||
sb.erase(false);
|
||||
|
||||
// Put a protected, intensified selectable field at pos 0 (ISPF header simulation)
|
||||
sb.setCellFA(0, (byte) (FA_PRINTABLE | FA_PROTECT | FA_INT_HIGH_SEL));
|
||||
String headerText = " ISPF PRIMARY OPTION MENU";
|
||||
for (int i = 0; i < headerText.length(); i++) {
|
||||
sb.getCell(1 + i).ucs4 = headerText.charAt(i);
|
||||
sb.getCell(1 + i).ec = (byte) (0x40 + i);
|
||||
}
|
||||
|
||||
panel.setClient(client);
|
||||
|
||||
// Cell dimensions in panel
|
||||
int cellW = panel.cellWidth > 0 ? panel.cellWidth : 10;
|
||||
int cellH = panel.cellHeight > 0 ? panel.cellHeight : 18;
|
||||
int ox = panel.getRenderOffsetX();
|
||||
int oy = panel.getRenderOffsetY();
|
||||
|
||||
MouseListener[] mouseListeners = panel.getMouseListeners();
|
||||
MouseMotionListener[] motionListeners = panel.getMouseMotionListeners();
|
||||
assertTrue(mouseListeners.length > 0);
|
||||
assertTrue(motionListeners.length > 0);
|
||||
|
||||
MouseListener ml = mouseListeners[0];
|
||||
MouseMotionListener mml = motionListeners[0];
|
||||
|
||||
// 1. Mouse Pressed at cell (row 0, col 2)
|
||||
int startX = ox + 2 * cellW + cellW / 2;
|
||||
int startY = oy + cellH / 2;
|
||||
MouseEvent pressEvent = new MouseEvent(panel, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(),
|
||||
0, startX, startY, 1, false, MouseEvent.BUTTON1);
|
||||
ml.mousePressed(pressEvent);
|
||||
|
||||
// 2. Drag to cell (row 0, col 15)
|
||||
int dragX = ox + 15 * cellW + cellW / 2;
|
||||
int dragY = oy + cellH / 2;
|
||||
MouseEvent dragEvent = new MouseEvent(panel, MouseEvent.MOUSE_DRAGGED, System.currentTimeMillis(),
|
||||
0, dragX, dragY, 1, false, MouseEvent.BUTTON1);
|
||||
mml.mouseDragged(dragEvent);
|
||||
|
||||
assertTrue(panel.hasSelection(), "Drag over protected field must create active selection (Bug 3)");
|
||||
|
||||
// 3. Mouse Released at cell (row 0, col 15)
|
||||
MouseEvent releaseEvent = new MouseEvent(panel, MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(),
|
||||
0, dragX, dragY, 1, false, MouseEvent.BUTTON1);
|
||||
ml.mouseReleased(releaseEvent);
|
||||
|
||||
// Selection MUST NOT be dropped upon mouse release! (Bug 4)
|
||||
assertTrue(panel.hasSelection(), "Selection must remain active after mouse release (Bug 4)");
|
||||
String selected = panel.getSelectedText();
|
||||
assertNotNull(selected);
|
||||
assertTrue(selected.length() > 0, "Selected text must not be empty");
|
||||
|
||||
// 4. A subsequent single click without drag should clear the selection and move the cursor
|
||||
int clickX = ox + 5 * cellW + cellW / 2;
|
||||
int clickY = oy + cellH / 2;
|
||||
MouseEvent clickPress = new MouseEvent(panel, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(),
|
||||
0, clickX, clickY, 1, false, MouseEvent.BUTTON1);
|
||||
ml.mousePressed(clickPress);
|
||||
MouseEvent clickRelease = new MouseEvent(panel, MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(),
|
||||
0, clickX, clickY, 1, false, MouseEvent.BUTTON1);
|
||||
ml.mouseReleased(clickRelease);
|
||||
|
||||
assertFalse(panel.hasSelection(), "Single click must clear previous selection");
|
||||
assertEquals(5, sb.getCursorAddress(), "Single click must position cursor at clicked cell");
|
||||
|
||||
} catch (HeadlessException e) {
|
||||
// Handled gracefully in headless environments
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user