Lightpen on

This commit is contained in:
2026-08-24 16:35:24 +00:00
parent 0837de8db3
commit d9df1052a1
7 changed files with 176 additions and 36 deletions
@@ -131,6 +131,10 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
terminalPanel.repaint();
}));
actionsMenu.addSeparator();
actionsMenu.add(createMenuItem("Toggle Light Pen (Alt+L)", KeyEvent.VK_L, () -> {
terminalPanel.toggleLightPen();
}, true));
actionsMenu.addSeparator();
actionsMenu.add(createMenuItem("File Transfer...", KeyEvent.VK_T, this::showFileTransferDialog));
menuBar.add(actionsMenu);
@@ -149,18 +153,22 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
return menu;
}
private JMenuItem createMenuItem(String text, int acceleratorKey, Runnable action) {
JMenuItem item = new JMenuItem(text);
private JMenuItem createMenuItem(String name, int mnemonic, Runnable action, boolean useAlt) {
JMenuItem item = new JMenuItem(name);
item.setBackground(new Color(40, 40, 40));
item.setForeground(new Color(200, 200, 200));
if (acceleratorKey > 0) {
item.setAccelerator(KeyStroke.getKeyStroke(acceleratorKey,
Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()));
if (mnemonic > 0) {
int modifier = useAlt ? KeyEvent.ALT_DOWN_MASK : Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
item.setAccelerator(KeyStroke.getKeyStroke(mnemonic, modifier));
}
item.addActionListener(e -> action.run());
return item;
}
private JMenuItem createMenuItem(String name, int mnemonic, Runnable action) {
return createMenuItem(name, mnemonic, action, false);
}
private void showFileTransferDialog() {
if (client == null) {
JOptionPane.showMessageDialog(this, "Connect to a host before using File Transfer.",
@@ -278,7 +286,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
client.addScreenUpdateListener(this);
terminalPanel.setClient(client);
statusBar.setClient(client);
statusBar.setClient(client, terminalPanel);
String tlsIndicator = config.isUseTls() ? (config.isTlsVerifyCert() ? " [TLS]" : " [TLS/Unverified]") : "";
setTitle("j3270 — " + config.getHost() + ":" + config.getPort() + tlsIndicator);
@@ -312,7 +320,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
client.disconnect();
client = null;
terminalPanel.setClient(null);
statusBar.setClient(null);
statusBar.setClient(null, terminalPanel);
terminalPanel.repaint();
setTitle("j3270 — Java TN3270 Terminal Emulator");
}
@@ -22,6 +22,7 @@ public class StatusBar extends JPanel {
private final JLabel modelInfo;
private Telnet3270Client client;
private TerminalPanel terminalPanel;
// OIA colors
private static final Color OIA_BG = new Color(20, 20, 20);
@@ -44,6 +45,19 @@ public class StatusBar extends JPanel {
modelInfo = createLabel("", oiaFont, OIA_DIM);
cursorPosition = createLabel("001/001", oiaFont, OIA_FG);
JButton lpButton = new JButton("LightPen: OFF");
lpButton.setFont(oiaFont);
lpButton.setForeground(OIA_FG);
lpButton.setBackground(OIA_BG);
lpButton.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
lpButton.setFocusable(false);
lpButton.addActionListener(e -> {
if (terminalPanel != null) {
terminalPanel.toggleLightPen();
lpButton.setText("LightPen: " + (terminalPanel.isLightPenMode() ? "ON" : "OFF"));
}
});
add(Box.createHorizontalStrut(6));
add(connectionStatus);
add(Box.createHorizontalStrut(10));
@@ -52,6 +66,8 @@ public class StatusBar extends JPanel {
add(luName);
add(Box.createHorizontalStrut(12));
add(lockStatus);
add(Box.createHorizontalStrut(12));
add(lpButton);
add(Box.createHorizontalGlue());
add(modelInfo);
add(Box.createHorizontalStrut(12));
@@ -66,8 +82,9 @@ public class StatusBar extends JPanel {
return label;
}
public void setClient(Telnet3270Client client) {
public void setClient(Telnet3270Client client, TerminalPanel terminalPanel) {
this.client = client;
this.terminalPanel = terminalPanel;
}
public void updateStatus() {
@@ -62,6 +62,9 @@ public class TerminalPanel extends JPanel {
private static final Color SELECTION_COLOR = new Color(75, 110, 175, 100);
// ========== Resize guard ==========
private boolean lightPenMode = false;
private long lastGraphicsUpdateCount = -1;
private java.awt.image.BufferedImage cachedGraphicsImage = null;
private boolean resizeGuard = false;
/**
@@ -207,6 +210,12 @@ public class TerminalPanel extends JPanel {
int newAddr = selectionStartRow * sb.getCols() + selectionStartCol;
sb.setCursorAddress(newAddr);
clearSelection();
if (lightPenMode) {
if (client.getInputProcessor().lightPenSelect(newAddr)) {
refreshScreen();
return;
}
}
if (client.getGocaDecoder() != null && client.getGraphicsPlane() != null) {
int ox = getRenderOffsetX();
@@ -558,6 +567,7 @@ public class TerminalPanel extends JPanel {
int shortcutMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, shortcutMask), "j3270-COPY");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_V, shortcutMask), "j3270-PASTE");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_L, java.awt.event.InputEvent.ALT_DOWN_MASK), "j3270-LIGHTPEN");
// Create actions for all bound keys
am.put("j3270-ENTER", createAction(this::handleEnter));
@@ -586,6 +596,7 @@ public class TerminalPanel extends JPanel {
// Copy/Paste actions
am.put("j3270-COPY", createAction(this::copySelection));
am.put("j3270-PASTE", createAction(this::pasteClipboard));
am.put("j3270-LIGHTPEN", createAction(this::toggleLightPen));
for (int i = 1; i <= 24; i++) {
final int pf = i;
@@ -900,9 +911,13 @@ public class TerminalPanel extends JPanel {
int gHeight = client.getGraphicsPlane().getCanvasHeight();
int[] rgb = client.getGraphicsPlane().getRgbBuffer();
if (rgb != null && gWidth > 0 && gHeight > 0) {
java.awt.image.BufferedImage img = new java.awt.image.BufferedImage(gWidth, gHeight, java.awt.image.BufferedImage.TYPE_INT_ARGB);
img.setRGB(0, 0, gWidth, gHeight, rgb, 0, gWidth);
g2.drawImage(img, ox, oy, gridW, gridH, null);
long currentUpdateCount = client.getGraphicsPlane().getUpdateCount();
if (cachedGraphicsImage == null || currentUpdateCount != lastGraphicsUpdateCount || cachedGraphicsImage.getWidth() != gWidth || cachedGraphicsImage.getHeight() != gHeight) {
cachedGraphicsImage = new java.awt.image.BufferedImage(gWidth, gHeight, java.awt.image.BufferedImage.TYPE_INT_ARGB);
cachedGraphicsImage.setRGB(0, 0, gWidth, gHeight, rgb, 0, gWidth);
lastGraphicsUpdateCount = currentUpdateCount;
}
g2.drawImage(cachedGraphicsImage, ox, oy, gridW, gridH, null);
}
}
@@ -1039,8 +1054,14 @@ public class TerminalPanel extends JPanel {
if (client.getGocaDecoder() != null && client.getGocaDecoder().isGraphicsCursorActive() && client.getGraphicsPlane() != null) {
int gocaX = client.getGocaDecoder().getGraphicCursorX();
int gocaY = client.getGocaDecoder().getGraphicCursorY();
int px = ox + client.getGraphicsPlane().mapX(gocaX);
int py = oy + client.getGraphicsPlane().mapY(gocaY);
int canvasPx = client.getGraphicsPlane().mapX(gocaX);
int canvasPy = client.getGraphicsPlane().mapY(gocaY);
int gridW = cols * cellWidth;
int gridH = rows * cellHeight;
int gWidth = client.getGraphicsPlane().getCanvasWidth();
int gHeight = client.getGraphicsPlane().getCanvasHeight();
int px = ox + (gWidth > 0 ? (int)Math.round((double)canvasPx * gridW / gWidth) : canvasPx);
int py = oy + (gHeight > 0 ? (int)Math.round((double)canvasPy * gridH / gHeight) : canvasPy);
g2.setColor(Color.WHITE);
g2.setXORMode(Color.BLACK);
@@ -1103,4 +1124,15 @@ public class TerminalPanel extends JPanel {
if (blinkTimer != null)
blinkTimer.stop();
}
public void toggleLightPen() {
this.lightPenMode = !this.lightPenMode;
if (client != null && client.getGocaDecoder() != null) {
client.getGocaDecoder().setGraphicsCursorActive(this.lightPenMode);
repaint();
}
}
public boolean isLightPenMode() {
return this.lightPenMode;
}
}