diff --git a/j3270/src/main/java/haus/nightmare/j3270/ui/TerminalPanel.java b/j3270/src/main/java/haus/nightmare/j3270/ui/TerminalPanel.java index ac0da82..9aa4163 100644 --- a/j3270/src/main/java/haus/nightmare/j3270/ui/TerminalPanel.java +++ b/j3270/src/main/java/haus/nightmare/j3270/ui/TerminalPanel.java @@ -239,7 +239,7 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable, I public void mousePressed(MouseEvent e) { Window win = SwingUtilities.getWindowAncestor(TerminalPanel.this); boolean wasWindowInactive = (win != null && (!win.isActive() || !win.isFocused())); - boolean wasPanelUnfocused = !hasFocus(); + boolean wasPanelUnfocused = !java.awt.GraphicsEnvironment.isHeadless() && !hasFocus(); isActivationClick = wasWindowInactive || wasPanelUnfocused; if (win != null && wasWindowInactive) { @@ -556,13 +556,12 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable, I 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); - boolean isDoubleClick = (e.getClickCount() >= 2); - // For detectable fields (FA bits 4-5 = 01 or 11): - // Double-clicking or invoking mnemonic [cursel] toggles the designator character (? -> >). - // If field has immediate select attribute, immediately transmit Cursor Select AID (0x7E). - if (lightPenMode || isDoubleClick || isToggleField || isEnterField || !isProtected) { + // Automatic mouse selection applies to toggle fields (? -> >) and enter fields (&). + // Standard text fields (space/null designator) must NOT trigger light-pen selection + // on normal mouse clicks, so users can position cursor and edit text normally (HoD parity). + // Hardware light-pen emulation on space designators only fires when lightPenMode is active. + if (lightPenMode || isToggleField || isEnterField) { if (client.lightPenSelect(clickAddr)) { refreshScreen(); return; diff --git a/j3270/src/test/java/haus/nightmare/j3270/ui/TerminalPanelSelectionTest.java b/j3270/src/test/java/haus/nightmare/j3270/ui/TerminalPanelSelectionTest.java index 82ac2de..e34411d 100644 --- a/j3270/src/test/java/haus/nightmare/j3270/ui/TerminalPanelSelectionTest.java +++ b/j3270/src/test/java/haus/nightmare/j3270/ui/TerminalPanelSelectionTest.java @@ -119,4 +119,143 @@ public class TerminalPanelSelectionTest { } catch (HeadlessException ignored) { } } + + @Test + @DisplayName("Clicking an unprotected, intensified line (e.g. XEDIT current line) does not transmit AID_SELECT") + public void testClickOnUnprotectedIntensifiedFieldDoesNotTriggerAidSelect() { + 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); + + // Row 3 (address 240): Unprotected, intensified line with spaces (XEDIT located line) + int row3Start = 3 * 80; + sb.setCellFA(row3Start, (byte) (FA_PRINTABLE | FA_INT_HIGH_SEL)); // Unprotected, high intensity (detectable) + // Space designator and COBOL text + String lineText = " IDENTIFICATION DIVISION."; + for (int i = 0; i < lineText.length(); i++) { + sb.getCell(row3Start + 1 + i).ucs4 = lineText.charAt(i); + sb.getCell(row3Start + 1 + i).ec = (byte) (lineText.charAt(i) == ' ' ? 0x40 : 0xC1 + i); + } + + panel.setClient(client); + + 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(); + assertTrue(mouseListeners.length > 0); + MouseListener ml = mouseListeners[0]; + + // Click at row 3, col 28 (like in j3270.log) + int clickX = ox + 28 * cellW + cellW / 2; + int clickY = oy + 3 * cellH + cellH / 2; + + MouseEvent pressEvent = new MouseEvent(panel, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(), + 0, clickX, clickY, 1, false, MouseEvent.BUTTON1); + ml.mousePressed(pressEvent); + + MouseEvent releaseEvent = new MouseEvent(panel, MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(), + 0, clickX, clickY, 1, false, MouseEvent.BUTTON1); + ml.mouseReleased(releaseEvent); + + // Cursor must move to clicked address without sending AID_SELECT (AID 0x7E) + assertEquals(row3Start + 28, sb.getCursorAddress(), "Cursor must be placed at clicked position"); + assertEquals(AID_NO, client.getInputProcessor().getLastAid(), "No AID must be transmitted on single click"); + + // Text on line must NOT have been modified or cleared + assertEquals('I', (char) sb.getCell(row3Start + 8).ucs4); + } catch (HeadlessException ignored) { + } + } + + @Test + @DisplayName("Clicking a toggle field (? -> >) toggles the designator without transmitting AID") + public void testClickOnToggleField() { + 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); + + // Field with ? designator + sb.setCellFA(0, (byte) (FA_PRINTABLE | FA_INT_NORM_SEL)); + sb.getCell(1).ucs4 = '?'; + sb.getCell(1).ec = (byte) 0x6F; + + panel.setClient(client); + + 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 ml = panel.getMouseListeners()[0]; + + int clickX = ox + 1 * cellW + cellW / 2; + int clickY = oy + cellH / 2; + + ml.mousePressed(new MouseEvent(panel, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(), + 0, clickX, clickY, 1, false, MouseEvent.BUTTON1)); + ml.mouseReleased(new MouseEvent(panel, MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(), + 0, clickX, clickY, 1, false, MouseEvent.BUTTON1)); + + assertEquals('>', (char) sb.getCell(1).ucs4, "Toggle field ? should toggle to > on click"); + assertEquals(AID_NO, client.getInputProcessor().getLastAid(), "No AID transmitted for deferred toggle field"); + } catch (HeadlessException ignored) { + } + } + + @Test + @DisplayName("Clicking an unprotected space-designator field when lightPenMode is enabled transmits AID_SELECT") + public void testClickInLightPenModeTransmitsAidSelect() { + 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); + + // Unprotected, high-intensity field with space designator + sb.setCellFA(0, (byte) (FA_PRINTABLE | FA_INT_HIGH_SEL)); + sb.getCell(1).ucs4 = ' '; + sb.getCell(1).ec = (byte) 0x40; + + panel.setClient(client); + panel.toggleLightPen(); + assertTrue(panel.isLightPenMode()); + + 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 ml = panel.getMouseListeners()[0]; + + int clickX = ox + 1 * cellW + cellW / 2; + int clickY = oy + cellH / 2; + + ml.mousePressed(new MouseEvent(panel, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(), + 0, clickX, clickY, 1, false, MouseEvent.BUTTON1)); + ml.mouseReleased(new MouseEvent(panel, MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(), + 0, clickX, clickY, 1, false, MouseEvent.BUTTON1)); + + assertEquals(AID_SELECT, client.getInputProcessor().getLastAid(), "AID_SELECT must be transmitted in lightPenMode"); + } catch (HeadlessException ignored) { + } + } }