IBM-DYNAMIC
This commit is contained in:
@@ -178,6 +178,38 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
viewMenu.add(themeMenu);
|
||||
viewMenu.addSeparator();
|
||||
|
||||
JCheckBoxMenuItem rulerItem = new JCheckBoxMenuItem("Crosshair Ruler", terminalPanel.isCrosshairRulerEnabled());
|
||||
ThemeManager.styleMenuItem(rulerItem);
|
||||
rulerItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.ALT_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK));
|
||||
rulerItem.addActionListener(e -> {
|
||||
boolean en = rulerItem.isSelected();
|
||||
terminalPanel.setCrosshairRulerEnabled(en);
|
||||
haus.nightmare.j3270.config.Settings.setCrosshairRuler(en);
|
||||
});
|
||||
viewMenu.add(rulerItem);
|
||||
|
||||
JMenu cursorMenu = createMenu("Cursor Style");
|
||||
ButtonGroup cursorGroup = new ButtonGroup();
|
||||
JRadioButtonMenuItem blockCursorItem = new JRadioButtonMenuItem("Block Cursor", terminalPanel.getCursorStyle() == TerminalPanel.CursorStyle.BLOCK);
|
||||
ThemeManager.styleMenuItem(blockCursorItem);
|
||||
blockCursorItem.addActionListener(e -> {
|
||||
terminalPanel.setCursorStyle(TerminalPanel.CursorStyle.BLOCK);
|
||||
haus.nightmare.j3270.config.Settings.setCursorStyle("BLOCK");
|
||||
});
|
||||
cursorGroup.add(blockCursorItem);
|
||||
cursorMenu.add(blockCursorItem);
|
||||
|
||||
JRadioButtonMenuItem underlineCursorItem = new JRadioButtonMenuItem("Underline Cursor", terminalPanel.getCursorStyle() == TerminalPanel.CursorStyle.UNDERLINE);
|
||||
ThemeManager.styleMenuItem(underlineCursorItem);
|
||||
underlineCursorItem.addActionListener(e -> {
|
||||
terminalPanel.setCursorStyle(TerminalPanel.CursorStyle.UNDERLINE);
|
||||
haus.nightmare.j3270.config.Settings.setCursorStyle("UNDERLINE");
|
||||
});
|
||||
cursorGroup.add(underlineCursorItem);
|
||||
cursorMenu.add(underlineCursorItem);
|
||||
viewMenu.add(cursorMenu);
|
||||
viewMenu.addSeparator();
|
||||
|
||||
// CodePage Submenu
|
||||
JMenu cpMenu = createMenu("Code Page");
|
||||
String[] codePages = {
|
||||
@@ -704,7 +736,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
"Delete — Delete character\n" +
|
||||
"Backspace — Backspace\n" +
|
||||
"Insert — Toggle insert mode\n" +
|
||||
"Escape — Reset\n" +
|
||||
"Escape / Alt+R — Reset\n" +
|
||||
"PageUp/Down — PF7/PF8\n" +
|
||||
"Alt+C / Alt+K — Clear\n" +
|
||||
"Alt+E — Erase Input\n" +
|
||||
@@ -713,6 +745,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
"Alt+Q — Cursor Select\n" +
|
||||
"Alt+L — Toggle Light Pen\n" +
|
||||
"Alt+T — File Transfer\n" +
|
||||
"Alt+Shift+R — Crosshair Ruler\n" +
|
||||
"Alt+=/-/0 — Font size +/-/reset\n" +
|
||||
"Cmd/Ctrl+F — Find on Screen\n" +
|
||||
"Cmd/Ctrl+G / F3— Find Next\n" +
|
||||
@@ -893,10 +926,15 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
}
|
||||
TerminalModel model = TerminalModel.IBM_3279_4;
|
||||
if (remainingArgs.size() >= 3) {
|
||||
try {
|
||||
int modelNum = Integer.parseInt(remainingArgs.get(2));
|
||||
model = TerminalModel.forModel(modelNum, true);
|
||||
} catch (Exception ignored) {}
|
||||
String mArg = remainingArgs.get(2).trim();
|
||||
if (mArg.equalsIgnoreCase("dynamic") || mArg.equals("0")) {
|
||||
model = TerminalModel.IBM_DYNAMIC;
|
||||
} else {
|
||||
try {
|
||||
int modelNum = Integer.parseInt(mArg);
|
||||
model = TerminalModel.forModel(modelNum, true);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
ConnectionConfig config = ConnectionConfig.parseHostString(hostArg, port, model);
|
||||
if (finalTls) {
|
||||
|
||||
@@ -113,6 +113,22 @@ public class Settings {
|
||||
prefs.put("codePage", (codePage != null && !codePage.trim().isEmpty()) ? codePage.trim() : "037");
|
||||
}
|
||||
|
||||
public static int getDynamicRows() {
|
||||
return prefs.getInt("dynamicRows", 62);
|
||||
}
|
||||
|
||||
public static void setDynamicRows(int rows) {
|
||||
prefs.putInt("dynamicRows", Math.max(1, rows));
|
||||
}
|
||||
|
||||
public static int getDynamicCols() {
|
||||
return prefs.getInt("dynamicCols", 160);
|
||||
}
|
||||
|
||||
public static void setDynamicCols(int cols) {
|
||||
prefs.putInt("dynamicCols", Math.max(1, cols));
|
||||
}
|
||||
|
||||
public static Color getColorOverride(int index, Color defaultColor) {
|
||||
String hex = prefs.get("color_" + index, null);
|
||||
try {
|
||||
@@ -233,6 +249,26 @@ public class Settings {
|
||||
prefs.putBoolean("blockSelectMode", block);
|
||||
}
|
||||
|
||||
// ========== Crosshair Ruler ==========
|
||||
|
||||
public static boolean getCrosshairRuler() {
|
||||
return prefs.getBoolean("crosshairRuler", false);
|
||||
}
|
||||
|
||||
public static void setCrosshairRuler(boolean enabled) {
|
||||
prefs.putBoolean("crosshairRuler", enabled);
|
||||
}
|
||||
|
||||
// ========== Cursor Style ==========
|
||||
|
||||
public static String getCursorStyle() {
|
||||
return prefs.get("cursorStyle", "BLOCK");
|
||||
}
|
||||
|
||||
public static void setCursorStyle(String style) {
|
||||
prefs.put("cursorStyle", style != null ? style.toUpperCase() : "BLOCK");
|
||||
}
|
||||
|
||||
private static void applyConfigEntry(String section, String key, String value) {
|
||||
switch (section) {
|
||||
case "appearance":
|
||||
@@ -244,6 +280,13 @@ public class Settings {
|
||||
case "uiTheme":
|
||||
setJavaUiTheme(UITheme.fromString(value));
|
||||
break;
|
||||
case "crosshairRuler":
|
||||
case "ruler":
|
||||
setCrosshairRuler(Boolean.parseBoolean(value));
|
||||
break;
|
||||
case "cursorStyle":
|
||||
setCursorStyle(value);
|
||||
break;
|
||||
default:
|
||||
log.warning("Unknown appearance key: " + key);
|
||||
}
|
||||
@@ -284,6 +327,14 @@ public class Settings {
|
||||
case "charset":
|
||||
setCodePage(value);
|
||||
break;
|
||||
case "dynamicRows":
|
||||
case "dynamic_rows":
|
||||
setDynamicRows(Integer.parseInt(value));
|
||||
break;
|
||||
case "dynamicCols":
|
||||
case "dynamic_cols":
|
||||
setDynamicCols(Integer.parseInt(value));
|
||||
break;
|
||||
case "blockSelectMode": setBlockSelectMode(Boolean.parseBoolean(value)); break;
|
||||
default:
|
||||
log.warning("Unknown behavior/connection key: " + key);
|
||||
@@ -340,6 +391,8 @@ public class Settings {
|
||||
w.println("javaUiTheme = " + getJavaUiTheme().name());
|
||||
w.println("fontFamily = " + getFontFamily());
|
||||
w.println("fontSize = " + getFontSize());
|
||||
w.println("crosshairRuler = " + getCrosshairRuler());
|
||||
w.println("cursorStyle = " + getCursorStyle());
|
||||
w.println();
|
||||
|
||||
// [behavior]
|
||||
@@ -354,6 +407,8 @@ public class Settings {
|
||||
w.println("autoConnectTn3270e = " + getAutoConnectTn3270e());
|
||||
}
|
||||
w.println("codePage = " + getCodePage());
|
||||
w.println("dynamicRows = " + getDynamicRows());
|
||||
w.println("dynamicCols = " + getDynamicCols());
|
||||
w.println("blockSelectMode = " + getBlockSelectMode());
|
||||
w.println();
|
||||
|
||||
|
||||
@@ -14,6 +14,10 @@ public class ConnectDialog extends JDialog {
|
||||
private JTextField hostField;
|
||||
private JTextField portField;
|
||||
private JComboBox<TerminalModel> modelCombo;
|
||||
private JLabel dynamicDimLabel;
|
||||
private JPanel dynamicDimPanel;
|
||||
private JSpinner dynamicRowsSpinner;
|
||||
private JSpinner dynamicColsSpinner;
|
||||
private JComboBox<haus.nightmare.lib3270j.graphics.GraphicsMode> graphicsCombo;
|
||||
private JComboBox<String> codePageCombo;
|
||||
private JTextField luField;
|
||||
@@ -79,10 +83,49 @@ public class ConnectDialog extends JDialog {
|
||||
ThemeManager.styleComboBox(modelCombo);
|
||||
mainPanel.add(modelCombo, gbc);
|
||||
|
||||
// LU Name
|
||||
// Dynamic Dimensions (shown when IBM-DYNAMIC is selected)
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 3;
|
||||
gbc.weightx = 0;
|
||||
dynamicDimLabel = new JLabel("Screen Size:");
|
||||
dynamicDimLabel.setFont(labelFont);
|
||||
mainPanel.add(dynamicDimLabel, gbc);
|
||||
|
||||
gbc.gridx = 1;
|
||||
gbc.weightx = 1.0;
|
||||
dynamicDimPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 0));
|
||||
dynamicDimPanel.setOpaque(false);
|
||||
|
||||
JLabel rowsLabel = new JLabel("Rows:");
|
||||
rowsLabel.setFont(labelFont);
|
||||
dynamicRowsSpinner = new JSpinner(new SpinnerNumberModel(haus.nightmare.j3270.config.Settings.getDynamicRows(), 24, 255, 1));
|
||||
ThemeManager.styleSpinner(dynamicRowsSpinner);
|
||||
|
||||
JLabel colsLabel = new JLabel("Cols:");
|
||||
colsLabel.setFont(labelFont);
|
||||
dynamicColsSpinner = new JSpinner(new SpinnerNumberModel(haus.nightmare.j3270.config.Settings.getDynamicCols(), 80, 255, 1));
|
||||
ThemeManager.styleSpinner(dynamicColsSpinner);
|
||||
|
||||
dynamicDimPanel.add(rowsLabel);
|
||||
dynamicDimPanel.add(dynamicRowsSpinner);
|
||||
dynamicDimPanel.add(colsLabel);
|
||||
dynamicDimPanel.add(dynamicColsSpinner);
|
||||
mainPanel.add(dynamicDimPanel, gbc);
|
||||
|
||||
Runnable updateDynamicVisibility = () -> {
|
||||
TerminalModel m = (TerminalModel) modelCombo.getSelectedItem();
|
||||
boolean isDyn = (m != null && m.isDynamic());
|
||||
dynamicDimLabel.setVisible(isDyn);
|
||||
dynamicDimPanel.setVisible(isDyn);
|
||||
pack();
|
||||
};
|
||||
modelCombo.addActionListener(e -> updateDynamicVisibility.run());
|
||||
updateDynamicVisibility.run();
|
||||
|
||||
// LU Name
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 4;
|
||||
gbc.weightx = 0;
|
||||
JLabel luLabel = new JLabel("LU Name:");
|
||||
luLabel.setFont(labelFont);
|
||||
mainPanel.add(luLabel, gbc);
|
||||
@@ -93,7 +136,7 @@ public class ConnectDialog extends JDialog {
|
||||
|
||||
// Graphics Mode
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 4;
|
||||
gbc.gridy = 5;
|
||||
gbc.weightx = 0;
|
||||
JLabel graphicsLabel = new JLabel("Graphics:");
|
||||
graphicsLabel.setFont(labelFont);
|
||||
@@ -108,7 +151,7 @@ public class ConnectDialog extends JDialog {
|
||||
|
||||
// Code Page
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 5;
|
||||
gbc.gridy = 6;
|
||||
gbc.weightx = 0;
|
||||
JLabel cpLabel = new JLabel("Code Page:");
|
||||
cpLabel.setFont(labelFont);
|
||||
@@ -167,7 +210,7 @@ public class ConnectDialog extends JDialog {
|
||||
|
||||
// TLS / SSL Checkbox
|
||||
gbc.gridx = 1;
|
||||
gbc.gridy = 6;
|
||||
gbc.gridy = 7;
|
||||
gbc.weightx = 1.0;
|
||||
tlsCheckBox = new JCheckBox("Enable TLS/SSL");
|
||||
ThemeManager.styleCheckBox(tlsCheckBox);
|
||||
@@ -186,7 +229,7 @@ public class ConnectDialog extends JDialog {
|
||||
|
||||
// Verify Certificate Checkbox
|
||||
gbc.gridx = 1;
|
||||
gbc.gridy = 7;
|
||||
gbc.gridy = 8;
|
||||
verifyCertCheckBox = new JCheckBox("Verify Server Certificate");
|
||||
ThemeManager.styleCheckBox(verifyCertCheckBox);
|
||||
verifyCertCheckBox.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 13));
|
||||
@@ -196,7 +239,7 @@ public class ConnectDialog extends JDialog {
|
||||
|
||||
// TN3270E Checkbox
|
||||
gbc.gridx = 1;
|
||||
gbc.gridy = 8;
|
||||
gbc.gridy = 9;
|
||||
tn3270eCheckBox = new JCheckBox("Enable TN3270E (Extended 3270)");
|
||||
ThemeManager.styleCheckBox(tn3270eCheckBox);
|
||||
tn3270eCheckBox.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 13));
|
||||
@@ -223,7 +266,7 @@ public class ConnectDialog extends JDialog {
|
||||
buttonPanel.add(connectBtn);
|
||||
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 9;
|
||||
gbc.gridy = 10;
|
||||
gbc.gridwidth = 2;
|
||||
mainPanel.add(buttonPanel, gbc);
|
||||
|
||||
@@ -256,7 +299,15 @@ public class ConnectDialog extends JDialog {
|
||||
return;
|
||||
}
|
||||
|
||||
result = new ConnectionConfig(host, port, (TerminalModel) modelCombo.getSelectedItem());
|
||||
TerminalModel selectedModel = (TerminalModel) modelCombo.getSelectedItem();
|
||||
result = new ConnectionConfig(host, port, selectedModel);
|
||||
if (selectedModel != null && selectedModel.isDynamic()) {
|
||||
int dRows = (Integer) dynamicRowsSpinner.getValue();
|
||||
int dCols = (Integer) dynamicColsSpinner.getValue();
|
||||
result.setDynamicDimensions(dRows, dCols);
|
||||
haus.nightmare.j3270.config.Settings.setDynamicRows(dRows);
|
||||
haus.nightmare.j3270.config.Settings.setDynamicCols(dCols);
|
||||
}
|
||||
String lu = luField.getText().trim();
|
||||
if (!lu.isEmpty()) {
|
||||
result.setLuName(lu);
|
||||
|
||||
@@ -98,19 +98,21 @@ public class ScreenExporter {
|
||||
writer.write("<style>\n");
|
||||
writer.write("body { background-color: #0a0a0a; color: #32cd32; font-family: 'Courier New', Courier, monospace; font-size: 14px; margin: 20px; }\n");
|
||||
writer.write(".screen { background-color: #000; padding: 15px; border-radius: 4px; display: inline-block; box-shadow: 0 0 10px rgba(0,0,0,0.8); line-height: 1.2; }\n");
|
||||
writer.write(".c-blue { color: #5078ff; }\n");
|
||||
writer.write(".c-red { color: #ff3232; }\n");
|
||||
writer.write(".c-pink { color: #ff82b4; }\n");
|
||||
writer.write(".c-green { color: #32cd32; }\n");
|
||||
writer.write(".c-turq { color: #40e0d0; }\n");
|
||||
writer.write(".c-yellow { color: #ffff50; }\n");
|
||||
writer.write(".c-blue { color: #7890f0; }\n");
|
||||
writer.write(".c-red { color: #ff0000; }\n");
|
||||
writer.write(".c-pink { color: #ff00ff; }\n");
|
||||
writer.write(".c-green { color: #00ff00; }\n");
|
||||
writer.write(".c-turq { color: #00ffff; }\n");
|
||||
writer.write(".c-yellow { color: #ffff00; }\n");
|
||||
writer.write(".c-white { color: #ffffff; }\n");
|
||||
writer.write(".c-black { color: #000000; }\n");
|
||||
writer.write(".c-orange { color: #ffa500; }\n");
|
||||
writer.write(".c-purple { color: #b482ff; }\n");
|
||||
writer.write(".c-palegreen { color: #90ee90; }\n");
|
||||
writer.write(".c-paleturq { color: #afeeee; }\n");
|
||||
writer.write(".c-grey { color: #aaaaaa; }\n");
|
||||
writer.write(".c-deepblue { color: #000080; }\n");
|
||||
writer.write(".c-orange { color: #ffa200; }\n");
|
||||
writer.write(".c-purple { color: #800080; }\n");
|
||||
writer.write(".c-palegreen { color: #008000; }\n");
|
||||
writer.write(".c-paleturq { color: #008080; }\n");
|
||||
writer.write(".c-mustard { color: #a0a000; }\n");
|
||||
writer.write(".c-grey { color: #c0c0c0; }\n");
|
||||
writer.write(".bold { font-weight: bold; }\n");
|
||||
writer.write(".underline { text-decoration: underline; }\n");
|
||||
writer.write("</style>\n</head>\n<body>\n<div class=\"screen\"><pre>");
|
||||
@@ -209,17 +211,17 @@ public class ScreenExporter {
|
||||
case 6: return "c-yellow";
|
||||
case 7: return "c-white";
|
||||
case 8: return "c-black";
|
||||
case 9: return "c-blue";
|
||||
case 9: return "c-deepblue";
|
||||
case 10: return "c-orange";
|
||||
case 11: return "c-purple";
|
||||
case 12: return "c-palegreen";
|
||||
case 13: return "c-paleturq";
|
||||
case 14: return "c-grey";
|
||||
case 15: return "c-white";
|
||||
case 14: return "c-mustard";
|
||||
case 15: return "c-grey";
|
||||
}
|
||||
}
|
||||
if (faIsProtected(currentFA & 0xFF)) {
|
||||
return faIsHigh(currentFA & 0xFF) ? "c-white" : "c-blue";
|
||||
return faIsHigh(currentFA & 0xFF) ? "c-white" : "c-turq";
|
||||
}
|
||||
return faIsHigh(currentFA & 0xFF) ? "c-red" : "c-green";
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ public class SettingsDialog extends JDialog {
|
||||
private JTextField hostField;
|
||||
private JTextField portField;
|
||||
private JCheckBox blockSelectCheck;
|
||||
private JSpinner dynamicRowsSpinner;
|
||||
private JSpinner dynamicColsSpinner;
|
||||
|
||||
// Advanced tab state tracking
|
||||
private final Color[] tempHostColors = new Color[16];
|
||||
@@ -235,8 +237,30 @@ public class SettingsDialog extends JDialog {
|
||||
blockSelectCheck = new JCheckBox("Block selection mode (rectangular select)", Settings.getBlockSelectMode());
|
||||
panel.add(blockSelectCheck, gbc);
|
||||
|
||||
// Placeholder for potentially more behavior options below
|
||||
// Default Dynamic Screen Size
|
||||
gbc.gridy = 3;
|
||||
gbc.gridwidth = 1;
|
||||
gbc.gridx = 0;
|
||||
panel.add(new JLabel("Default Dynamic Screen:"), gbc);
|
||||
|
||||
JPanel dynDimPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 0));
|
||||
dynDimPanel.setOpaque(false);
|
||||
dynDimPanel.add(new JLabel("Rows:"));
|
||||
dynamicRowsSpinner = new JSpinner(new SpinnerNumberModel(Settings.getDynamicRows(), 24, 255, 1));
|
||||
ThemeManager.styleSpinner(dynamicRowsSpinner);
|
||||
dynDimPanel.add(dynamicRowsSpinner);
|
||||
dynDimPanel.add(new JLabel("Cols:"));
|
||||
dynamicColsSpinner = new JSpinner(new SpinnerNumberModel(Settings.getDynamicCols(), 80, 255, 1));
|
||||
ThemeManager.styleSpinner(dynamicColsSpinner);
|
||||
dynDimPanel.add(dynamicColsSpinner);
|
||||
|
||||
gbc.gridx = 1;
|
||||
panel.add(dynDimPanel, gbc);
|
||||
|
||||
// Placeholder for potentially more behavior options below
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 4;
|
||||
gbc.gridwidth = 2;
|
||||
gbc.weighty = 1.0;
|
||||
panel.add(Box.createGlue(), gbc);
|
||||
|
||||
@@ -555,6 +579,12 @@ public class SettingsDialog extends JDialog {
|
||||
// Block select mode
|
||||
Settings.setBlockSelectMode(blockSelectCheck.isSelected());
|
||||
|
||||
// Default Dynamic screen dimensions
|
||||
if (dynamicRowsSpinner != null && dynamicColsSpinner != null) {
|
||||
Settings.setDynamicRows((Integer) dynamicRowsSpinner.getValue());
|
||||
Settings.setDynamicCols((Integer) dynamicColsSpinner.getValue());
|
||||
}
|
||||
|
||||
// Propagate visual changes to the app
|
||||
// Save Colors
|
||||
for (int i=0; i<16; i++) {
|
||||
|
||||
@@ -110,25 +110,25 @@ public class StatusBar extends JPanel {
|
||||
break;
|
||||
case CONNECTED_3270:
|
||||
connectionStatus.setText("TN3270");
|
||||
connectionStatus.setForeground(ThemeManager.getOiaFgNormal(theme));
|
||||
connectionStatus.setForeground(ThemeManager.getOiaStatusSysAvail());
|
||||
break;
|
||||
case CONNECTED_TN3270E:
|
||||
connectionStatus.setText("TN3270E");
|
||||
connectionStatus.setForeground(ThemeManager.getOiaFgNormal(theme));
|
||||
connectionStatus.setForeground(ThemeManager.getOiaStatusSysAvail());
|
||||
break;
|
||||
case CONNECTED_SSCP:
|
||||
connectionStatus.setText("SSCP-LU");
|
||||
connectionStatus.setForeground(ThemeManager.getOiaFgNormal(theme));
|
||||
connectionStatus.setForeground(ThemeManager.getOiaStatusSysAvail());
|
||||
break;
|
||||
case CONNECTED_NVT:
|
||||
case CONNECTED_NVT_CHAR:
|
||||
case CONNECTED_E_NVT:
|
||||
connectionStatus.setText("NVT");
|
||||
connectionStatus.setForeground(ThemeManager.getOiaFgNormal(theme));
|
||||
connectionStatus.setForeground(ThemeManager.getOiaStatusSysAvail());
|
||||
break;
|
||||
case CONNECTED_UNBOUND:
|
||||
connectionStatus.setText("Unbound");
|
||||
connectionStatus.setForeground(ThemeManager.getOiaFgWarn(theme));
|
||||
connectionStatus.setForeground(ThemeManager.getOiaAttention());
|
||||
break;
|
||||
default:
|
||||
connectionStatus.setText(state.name());
|
||||
@@ -144,11 +144,11 @@ public class StatusBar extends JPanel {
|
||||
String protocol = session != null ? session.getProtocol() : "TLS";
|
||||
if (verified) {
|
||||
tlsStatus.setText("🔒 TLS");
|
||||
tlsStatus.setForeground(ThemeManager.getOiaFgNormal(theme));
|
||||
tlsStatus.setForeground(ThemeManager.getOiaStatusSysAvail());
|
||||
tlsStatus.setToolTipText(protocol + " / " + cipher + " (Verified)");
|
||||
} else {
|
||||
tlsStatus.setText("🔓 TLS (Unverified)");
|
||||
tlsStatus.setForeground(ThemeManager.getOiaFgWarn(theme));
|
||||
tlsStatus.setForeground(ThemeManager.getOiaAttention());
|
||||
tlsStatus.setToolTipText(protocol + " / " + cipher + " (Verification Bypassed)");
|
||||
}
|
||||
} else {
|
||||
@@ -164,38 +164,46 @@ public class StatusBar extends JPanel {
|
||||
lu = "LU:" + client.getConfig().getLuName();
|
||||
}
|
||||
luName.setText(lu);
|
||||
luName.setForeground(ThemeManager.getOiaFgNormal(theme));
|
||||
luName.setForeground(ThemeManager.getOiaStatusSysAvail());
|
||||
|
||||
// Lock / Inhibit status
|
||||
int inhibit = client.getOIA().getInputInhibited();
|
||||
if (inhibit != ECLConstants.INHIBIT_NOT_INHIBITED) {
|
||||
Color lockFg = ThemeManager.getOiaInputInhibited(); // White (oII)
|
||||
switch (inhibit) {
|
||||
case ECLConstants.INHIBIT_SYSTEM_LOCK:
|
||||
lockStatus.setText("X SYSTEM");
|
||||
lockFg = ThemeManager.getOiaInputInhibited(); // White (oII)
|
||||
break;
|
||||
case ECLConstants.INHIBIT_COMM_CHECK:
|
||||
lockStatus.setText("X COMM");
|
||||
lockFg = ThemeManager.getOiaCommCheck(); // Red (oEI)
|
||||
break;
|
||||
case ECLConstants.INHIBIT_NUMERIC_ONLY:
|
||||
lockStatus.setText("X NUM");
|
||||
lockFg = ThemeManager.getOiaAttention(); // Yellow (oAI)
|
||||
break;
|
||||
case ECLConstants.INHIBIT_PROTECTED_FIELD:
|
||||
lockStatus.setText("X PROT");
|
||||
lockFg = ThemeManager.getOiaInputInhibited(); // White (oII)
|
||||
break;
|
||||
case ECLConstants.INHIBIT_OVERFLOW:
|
||||
lockStatus.setText("X >");
|
||||
lockFg = ThemeManager.getOiaAttention(); // Yellow (oAI)
|
||||
break;
|
||||
case ECLConstants.INHIBIT_OPERATOR_DUE:
|
||||
lockStatus.setText("X OP");
|
||||
lockFg = ThemeManager.getOiaAttention(); // Yellow (oAI)
|
||||
break;
|
||||
default:
|
||||
lockStatus.setText("X LOCKED");
|
||||
lockFg = ThemeManager.getOiaInputInhibited(); // White (oII)
|
||||
break;
|
||||
}
|
||||
lockStatus.setForeground(ThemeManager.getOiaFgAlert(theme));
|
||||
lockStatus.setForeground(lockFg);
|
||||
} else if (client.getInputProcessor().isInsertMode()) {
|
||||
lockStatus.setText("INSERT");
|
||||
lockStatus.setForeground(ThemeManager.getOiaFgNormal(theme));
|
||||
lockStatus.setForeground(ThemeManager.getOiaStatusSysAvail());
|
||||
} else {
|
||||
lockStatus.setText("");
|
||||
}
|
||||
@@ -223,7 +231,13 @@ public class StatusBar extends JPanel {
|
||||
ScreenBuffer sb = client.getScreenBuffer();
|
||||
int rows = sb.getDisplayRows();
|
||||
int cols = sb.getDisplayCols();
|
||||
modelInfo.setText(client.getConfig().getModel().name().replace('_', '-') + " [" + rows + "x" + cols + "]");
|
||||
String modelName;
|
||||
if (client.getConfig().isDynamicModel() || (client.getConfig().getModel() != null && client.getConfig().getModel().isDynamic())) {
|
||||
modelName = "IBM-DYNAMIC [" + rows + "x" + cols + "]";
|
||||
} else {
|
||||
modelName = client.getConfig().getModel().name().replace('_', '-') + " [" + rows + "x" + cols + "]";
|
||||
}
|
||||
modelInfo.setText(modelName);
|
||||
modelInfo.setForeground(ThemeManager.getOiaFgDim(theme));
|
||||
|
||||
// Cursor position and buffer address
|
||||
|
||||
@@ -57,11 +57,22 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
CHAR_STRINGS[i] = String.valueOf((char) i);
|
||||
}
|
||||
}
|
||||
public enum CursorStyle {
|
||||
BLOCK,
|
||||
UNDERLINE
|
||||
}
|
||||
private CursorStyle cursorStyle = CursorStyle.BLOCK;
|
||||
private static final Color CURSOR_COLOR = new Color(255, 255, 255, 180);
|
||||
private boolean crosshairRulerEnabled = false;
|
||||
private static final Color CROSSHAIR_RULER_COLOR = new Color(0, 255, 0, 102); // 40% alpha (cRC)
|
||||
private boolean textBlinkVisible = true;
|
||||
private Image wallpaperImage = null;
|
||||
private haus.nightmare.lib3270j.graphics.HODWallpaper hodWallpaper = null;
|
||||
|
||||
private int selectionStartRow = -1, selectionStartCol = -1;
|
||||
private int selectionEndRow = -1, selectionEndCol = -1;
|
||||
private boolean isDragging = false;
|
||||
private static final Color SELECTION_COLOR = new Color(75, 110, 175, 100);
|
||||
private static final Color SELECTION_COLOR = new Color(75, 110, 175, 102); // 40% alpha blend
|
||||
|
||||
// ========== Search Highlight state ==========
|
||||
private int searchHighlightAddr = -1;
|
||||
@@ -96,31 +107,31 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
return Math.max(padding, extra / 2);
|
||||
}
|
||||
|
||||
// Default Host color mapping
|
||||
// Default Host color mapping aligned 1:1 with IBM Host On-Demand ColorRemapModel3270
|
||||
public static final Color[] DEFAULT_HOST_COLORS = {
|
||||
new Color(0, 0, 0), // 0: Neutral Black
|
||||
new Color(80, 120, 255), // 1: Blue
|
||||
new Color(255, 50, 50), // 2: Red
|
||||
new Color(255, 130, 180), // 3: Pink
|
||||
new Color(50, 205, 50), // 4: Green
|
||||
new Color(64, 224, 208), // 5: Turquoise
|
||||
new Color(255, 255, 80), // 6: Yellow
|
||||
new Color(120, 144, 240), // 1: Blue (0x7890F0 CUSTOMBLUE)
|
||||
new Color(255, 0, 0), // 2: Red
|
||||
new Color(255, 0, 255), // 3: Pink
|
||||
new Color(0, 255, 0), // 4: Green
|
||||
new Color(0, 255, 255), // 5: Turquoise / Cyan
|
||||
new Color(255, 255, 0), // 6: Yellow
|
||||
new Color(255, 255, 255), // 7: Neutral White
|
||||
new Color(0, 0, 0), // 8: Black
|
||||
new Color(30, 60, 180), // 9: Deep Blue
|
||||
new Color(255, 165, 0), // 10: Orange
|
||||
new Color(180, 130, 255), // 11: Purple
|
||||
new Color(144, 238, 144), // 12: Pale Green
|
||||
new Color(175, 238, 238), // 13: Pale Turquoise
|
||||
new Color(170, 170, 170), // 14: Grey
|
||||
new Color(255, 255, 255), // 15: White
|
||||
new Color(0, 0, 128), // 9: Deep Blue
|
||||
new Color(255, 162, 0), // 10: Orange (0xFFFFA200)
|
||||
new Color(128, 0, 128), // 11: Purple
|
||||
new Color(0, 128, 0), // 12: Pale Green
|
||||
new Color(0, 128, 128), // 13: Pale Turquoise
|
||||
new Color(160, 160, 0), // 14: Mustard (0xFFA0A000)
|
||||
new Color(192, 192, 192), // 15: Grey (0xFFC0C0C0)
|
||||
};
|
||||
|
||||
// Default 3278 monochrome colors
|
||||
public static final Color DEFAULT_MONO_NORMAL = new Color(50, 205, 50);
|
||||
public static final Color DEFAULT_MONO_INTENSIFY = new Color(255, 255, 255);
|
||||
public static final Color DEFAULT_MONO_PROTECTED = new Color(80, 120, 255);
|
||||
public static final Color DEFAULT_MONO_PROTECTED_HIGH = new Color(255, 255, 255);
|
||||
// Default 3278 / base monochrome colors aligned with HoD
|
||||
public static final Color DEFAULT_MONO_NORMAL = new Color(0, 255, 0); // Green
|
||||
public static final Color DEFAULT_MONO_INTENSIFY = new Color(255, 0, 0); // Red
|
||||
public static final Color DEFAULT_MONO_PROTECTED = new Color(0, 255, 255); // Cyan / Turquoise
|
||||
public static final Color DEFAULT_MONO_PROTECTED_HIGH = new Color(255, 255, 255); // White
|
||||
|
||||
// Default Background
|
||||
public static final Color DEFAULT_BG_COLOR = Color.BLACK;
|
||||
@@ -138,6 +149,9 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
setupCursorBlink();
|
||||
|
||||
blockSelectMode = haus.nightmare.j3270.config.Settings.getBlockSelectMode();
|
||||
crosshairRulerEnabled = haus.nightmare.j3270.config.Settings.getCrosshairRuler();
|
||||
String cStyle = haus.nightmare.j3270.config.Settings.getCursorStyle();
|
||||
cursorStyle = "UNDERLINE".equalsIgnoreCase(cStyle) ? CursorStyle.UNDERLINE : CursorStyle.BLOCK;
|
||||
|
||||
// Handle mouse clicks to position cursor, selection, and grab focus
|
||||
MouseAdapter mouseHandler = new MouseAdapter() {
|
||||
@@ -1075,6 +1089,9 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
setupKeyBindings();
|
||||
setupFont();
|
||||
blockSelectMode = haus.nightmare.j3270.config.Settings.getBlockSelectMode();
|
||||
crosshairRulerEnabled = haus.nightmare.j3270.config.Settings.getCrosshairRuler();
|
||||
String cStyle = haus.nightmare.j3270.config.Settings.getCursorStyle();
|
||||
cursorStyle = "UNDERLINE".equalsIgnoreCase(cStyle) ? CursorStyle.UNDERLINE : CursorStyle.BLOCK;
|
||||
revalidate();
|
||||
repaint();
|
||||
}
|
||||
@@ -1103,13 +1120,18 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
}
|
||||
|
||||
private void setupCursorBlink() {
|
||||
blinkTimer = new Timer(530, e -> {
|
||||
blinkTimer = new Timer(500, e -> {
|
||||
cursorVisible = !cursorVisible;
|
||||
textBlinkVisible = !textBlinkVisible;
|
||||
repaint();
|
||||
});
|
||||
blinkTimer.start();
|
||||
}
|
||||
|
||||
public Telnet3270Client getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public void setClient(Telnet3270Client client) {
|
||||
this.client = client;
|
||||
if (client != null) {
|
||||
@@ -1247,7 +1269,18 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
int cols = sb.getDisplayCols();
|
||||
boolean isColorModel = client.getConfig().getModel().isColor();
|
||||
|
||||
// Draw Vector Graphics Plane under text if present
|
||||
// Layer 0: Optional Wallpaper / Background Image
|
||||
if (hodWallpaper != null) {
|
||||
int gridW = cols * cellWidth;
|
||||
int gridH = rows * cellHeight;
|
||||
hodWallpaper.paint(this, g2, ox, oy, gridW, gridH);
|
||||
} else if (wallpaperImage != null) {
|
||||
int gridW = cols * cellWidth;
|
||||
int gridH = rows * cellHeight;
|
||||
g2.drawImage(wallpaperImage, ox, oy, gridW, gridH, null);
|
||||
}
|
||||
|
||||
// Layer 1: Draw Vector Graphics Plane under text if present
|
||||
if (client.getGraphicsPlane() != null && client.getGraphicsPlane().hasContent()) {
|
||||
int gridW = cols * cellWidth;
|
||||
int gridH = rows * cellHeight;
|
||||
@@ -1269,6 +1302,22 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
}
|
||||
}
|
||||
|
||||
// Layer 2: Optional Crosshair Ruler under character glyphs
|
||||
if (crosshairRulerEnabled && client.getConnectionState().isFullSession()) {
|
||||
int curAddr = sb.getDisplayCursorAddress();
|
||||
int curRow = curAddr / cols;
|
||||
int curCol = curAddr % cols;
|
||||
int cx = ox + curCol * cellWidth;
|
||||
int cy = oy + curRow * cellHeight;
|
||||
int gridW = cols * cellWidth;
|
||||
int gridH = rows * cellHeight;
|
||||
|
||||
g2.setColor(CROSSHAIR_RULER_COLOR);
|
||||
g2.fillRect(ox, cy, gridW, cellHeight);
|
||||
g2.fillRect(cx, oy, cellWidth, gridH);
|
||||
}
|
||||
|
||||
// Layer 3: Character / Text Plane
|
||||
byte currentFA = 0;
|
||||
ExtendedAttribute currentFieldEa = null;
|
||||
|
||||
@@ -1296,7 +1345,7 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine foreground color
|
||||
// Determine foreground and background colors
|
||||
if (isColorModel) {
|
||||
fgColor = getColorForAttribute(ea, currentFieldEa, currentFA);
|
||||
bgColor = getBackgroundForAttribute(ea, currentFieldEa);
|
||||
@@ -1305,6 +1354,9 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
bgColor = this.bgColor;
|
||||
}
|
||||
|
||||
int rawBg = ea.bg != 0 ? (ea.bg & 0xFF) : (currentFieldEa != null ? (currentFieldEa.bg & 0xFF) : 0);
|
||||
boolean bgIsExplicit = (rawBg >= 0xF0 && rawBg <= 0xFF && rawBg != HOST_COLOR_NEUTRAL_BLACK && rawBg != HOST_COLOR_BLACK);
|
||||
|
||||
// Graphics rendition
|
||||
byte gr = ea.gr != 0 ? ea.gr : (currentFieldEa != null ? currentFieldEa.gr : 0);
|
||||
if (gr != 0) {
|
||||
@@ -1334,22 +1386,26 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
|
||||
if (reverse) {
|
||||
Color tmp = fgColor;
|
||||
fgColor = bgColor;
|
||||
fgColor = (rawBg == 0) ? this.bgColor : bgColor;
|
||||
bgColor = tmp;
|
||||
}
|
||||
|
||||
if (!bgColor.equals(this.bgColor) || reverse) {
|
||||
g2.setColor(bgColor);
|
||||
g2.fillRect(x, y, cellWidth, cellHeight);
|
||||
} else if (bgIsExplicit || !bgColor.equals(this.bgColor)) {
|
||||
g2.setColor(bgColor);
|
||||
g2.fillRect(x, y, cellWidth, cellHeight);
|
||||
}
|
||||
|
||||
// Check text blink (500ms cycle)
|
||||
boolean blink = (gr & GR_BLINK) != 0;
|
||||
boolean suppressGlyph = blink && !textBlinkVisible;
|
||||
|
||||
// Draw character or Programmed Symbol
|
||||
int cs = (ea.cs != 0) ? (ea.cs & 0xFF) : (currentFieldEa != null ? (currentFieldEa.cs & 0xFF) : 0);
|
||||
boolean drawnAsPs = false;
|
||||
if (cs >= 0x40 && client.getProgramSymbolManager() != null) {
|
||||
if (cs >= 0x40 && client.getProgramSymbolManager() != null && !suppressGlyph) {
|
||||
haus.nightmare.lib3270j.graphics.ProgramSymbolSet.SymbolSlot slot = client.getProgramSymbolManager().getSymbol(cs, ea.ec & 0xFF);
|
||||
if (slot != null) {
|
||||
int symBg = (!bgColor.equals(this.bgColor) || reverse) ? bgColor.getRGB() : 0;
|
||||
int symBg = (bgIsExplicit || reverse) ? bgColor.getRGB() : 0;
|
||||
java.awt.image.BufferedImage img = slot.getScaledImage(cellWidth, cellHeight, fgColor.getRGB(), symBg);
|
||||
if (img != null) {
|
||||
g2.drawImage(img, x, y, null);
|
||||
@@ -1358,7 +1414,7 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
}
|
||||
}
|
||||
|
||||
if (!drawnAsPs) {
|
||||
if (!drawnAsPs && !suppressGlyph) {
|
||||
char ch = ea.ucs4;
|
||||
if (ch > 0x20 && ch != 0xFF) {
|
||||
Font f = bold ? boldTerminalFont : terminalFont;
|
||||
@@ -1385,7 +1441,7 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
g2.fillRect(x, y, cellWidth, cellHeight);
|
||||
}
|
||||
|
||||
// Draw selection highlight
|
||||
// Draw selection highlight (40% alpha blend over cell)
|
||||
if (isCellSelected(row, col)) {
|
||||
g2.setColor(SELECTION_COLOR);
|
||||
g2.fillRect(x, y, cellWidth, cellHeight);
|
||||
@@ -1394,9 +1450,15 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
}
|
||||
|
||||
// Draw Graphic Cursor if active
|
||||
if (client.getGocaDecoder() != null && client.getGocaDecoder().isGraphicsCursorActive() && client.getGraphicsPlane() != null) {
|
||||
int gocaX = client.getGocaDecoder().getGraphicCursorX();
|
||||
int gocaY = client.getGocaDecoder().getGraphicCursorY();
|
||||
boolean isGraphicCursor = (client.getGocaDecoder() != null && client.getGocaDecoder().isGraphicsCursorActive())
|
||||
|| (client.getGraphicsPlane() != null && client.getGraphicsPlane().isGraphicCursorAttached());
|
||||
if (isGraphicCursor && client.getGraphicsPlane() != null) {
|
||||
int gocaX = (client.getGocaDecoder() != null && client.getGocaDecoder().isGraphicsCursorActive())
|
||||
? client.getGocaDecoder().getGraphicCursorX()
|
||||
: client.getGraphicsPlane().getGraphicCursorX();
|
||||
int gocaY = (client.getGocaDecoder() != null && client.getGocaDecoder().isGraphicsCursorActive())
|
||||
? client.getGocaDecoder().getGraphicCursorY()
|
||||
: client.getGraphicsPlane().getGraphicCursorY();
|
||||
int canvasPx = client.getGraphicsPlane().mapX(gocaX);
|
||||
int canvasPy = client.getGraphicsPlane().mapY(gocaY);
|
||||
int gridW = cols * cellWidth;
|
||||
@@ -1408,12 +1470,19 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
|
||||
g2.setColor(Color.WHITE);
|
||||
g2.setXORMode(Color.BLACK);
|
||||
g2.drawLine(px - 6, py, px + 6, py);
|
||||
g2.drawLine(px, py - 6, px, py + 6);
|
||||
int shape = client.getGraphicsPlane().getHodCursorShape();
|
||||
if (shape == 2) {
|
||||
// Shape 2: Box cursor per HoD
|
||||
g2.drawRect(px - 3, py - 3, 6, 6);
|
||||
} else {
|
||||
// Shape 1 (default): Crosshair per HoD
|
||||
g2.drawLine(px - 8, py, px + 8, py);
|
||||
g2.drawLine(px, py - 8, px, py + 8);
|
||||
}
|
||||
g2.setPaintMode();
|
||||
}
|
||||
|
||||
// Draw 3270 text cursor
|
||||
// Draw 3270 text cursor with alpha blending (Block / Underline)
|
||||
if (cursorVisible && client.getConnectionState().isFullSession()) {
|
||||
int curAddr = sb.getDisplayCursorAddress();
|
||||
int curRow = curAddr / cols;
|
||||
@@ -1421,13 +1490,79 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
int cx = ox + curCol * cellWidth;
|
||||
int cy = oy + curRow * cellHeight;
|
||||
|
||||
boolean isUnderline = (cursorStyle == CursorStyle.UNDERLINE) ||
|
||||
(client.getInputProcessor() != null && client.getInputProcessor().isInsertMode());
|
||||
g2.setColor(CURSOR_COLOR);
|
||||
g2.setXORMode(bgColor);
|
||||
g2.fillRect(cx, cy, cellWidth, cellHeight);
|
||||
g2.setPaintMode();
|
||||
if (isUnderline) {
|
||||
int ulH = Math.max(2, cellHeight / 6);
|
||||
g2.fillRect(cx, cy + cellHeight - ulH, cellWidth, ulH);
|
||||
} else {
|
||||
g2.fillRect(cx, cy, cellWidth, cellHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isCrosshairRulerEnabled() {
|
||||
return crosshairRulerEnabled;
|
||||
}
|
||||
|
||||
public void setCrosshairRulerEnabled(boolean enabled) {
|
||||
this.crosshairRulerEnabled = enabled;
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void toggleCrosshairRuler() {
|
||||
setCrosshairRulerEnabled(!crosshairRulerEnabled);
|
||||
}
|
||||
|
||||
public CursorStyle getCursorStyle() {
|
||||
return cursorStyle;
|
||||
}
|
||||
|
||||
public void setCursorStyle(CursorStyle style) {
|
||||
this.cursorStyle = (style != null) ? style : CursorStyle.BLOCK;
|
||||
repaint();
|
||||
}
|
||||
|
||||
public Image getWallpaperImage() {
|
||||
return wallpaperImage;
|
||||
}
|
||||
|
||||
public void setWallpaperImage(Image wallpaperImage) {
|
||||
this.wallpaperImage = wallpaperImage;
|
||||
if (wallpaperImage != null) {
|
||||
if (this.hodWallpaper == null) {
|
||||
this.hodWallpaper = new haus.nightmare.lib3270j.graphics.HODWallpaper(wallpaperImage, haus.nightmare.lib3270j.graphics.HODWallpaper.HOD_STRETCH);
|
||||
} else {
|
||||
this.hodWallpaper.setImage(wallpaperImage);
|
||||
}
|
||||
} else {
|
||||
this.hodWallpaper = null;
|
||||
}
|
||||
repaint();
|
||||
}
|
||||
|
||||
public haus.nightmare.lib3270j.graphics.HODWallpaper getHodWallpaper() {
|
||||
return hodWallpaper;
|
||||
}
|
||||
|
||||
public void setHodWallpaper(haus.nightmare.lib3270j.graphics.HODWallpaper wallpaper) {
|
||||
this.hodWallpaper = wallpaper;
|
||||
if (wallpaper != null) {
|
||||
this.wallpaperImage = wallpaper.getHODImage();
|
||||
}
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void setWallpaperMode(int displayMode) {
|
||||
if (this.hodWallpaper == null && this.wallpaperImage != null) {
|
||||
this.hodWallpaper = new haus.nightmare.lib3270j.graphics.HODWallpaper(this.wallpaperImage, displayMode);
|
||||
} else if (this.hodWallpaper != null) {
|
||||
this.hodWallpaper.setDisplay(displayMode);
|
||||
}
|
||||
repaint();
|
||||
}
|
||||
|
||||
private Color getColorForAttribute(ExtendedAttribute ea, ExtendedAttribute currentFieldEa, byte currentFA) {
|
||||
int fg = ea.fg != 0 ? (ea.fg & 0xFF)
|
||||
: (currentFieldEa != null && currentFieldEa.fg != 0 ? (currentFieldEa.fg & 0xFF) : 0);
|
||||
@@ -1435,7 +1570,7 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
return hostColors[fg - 0xf0];
|
||||
}
|
||||
if (faIsProtected(currentFA & 0xFF)) {
|
||||
return faIsHigh(currentFA & 0xFF) ? hostColors[HOST_COLOR_WHITE] : hostColors[HOST_COLOR_BLUE];
|
||||
return faIsHigh(currentFA & 0xFF) ? hostColors[HOST_COLOR_WHITE] : hostColors[HOST_COLOR_TURQUOISE];
|
||||
}
|
||||
return faIsHigh(currentFA & 0xFF) ? hostColors[HOST_COLOR_RED] : hostColors[HOST_COLOR_GREEN];
|
||||
}
|
||||
|
||||
@@ -244,6 +244,19 @@ public final class ThemeManager {
|
||||
return t == UITheme.DARK ? new Color(255, 190, 70) : new Color(180, 100, 0);
|
||||
}
|
||||
|
||||
// Host On-Demand OIA Category Colors (oSI, oII, oAI, oEI, oOB)
|
||||
public static final Color HOD_OIA_STATUS_SYS_AVAIL = new Color(120, 144, 240); // oSI: CUSTOMBLUE
|
||||
public static final Color HOD_OIA_INPUT_INHIBITED = new Color(255, 255, 255); // oII: White
|
||||
public static final Color HOD_OIA_ATTENTION_WARN = new Color(255, 255, 0); // oAI: Yellow
|
||||
public static final Color HOD_OIA_COMM_CHECK_ERROR = new Color(255, 0, 0); // oEI: Red
|
||||
public static final Color HOD_OIA_BG_BLACK = new Color(0, 0, 0); // oOB: Black
|
||||
|
||||
public static Color getOiaStatusSysAvail() { return HOD_OIA_STATUS_SYS_AVAIL; }
|
||||
public static Color getOiaInputInhibited() { return HOD_OIA_INPUT_INHIBITED; }
|
||||
public static Color getOiaAttention() { return HOD_OIA_ATTENTION_WARN; }
|
||||
public static Color getOiaCommCheck() { return HOD_OIA_COMM_CHECK_ERROR; }
|
||||
public static Color getOiaBackground() { return HOD_OIA_BG_BLACK; }
|
||||
|
||||
// =========================================================================
|
||||
// Button Variants & Color helpers
|
||||
// =========================================================================
|
||||
|
||||
@@ -58,12 +58,26 @@ public class MenuBarShortcutsTest {
|
||||
assertNotNull(fileMenu, "File menu should exist");
|
||||
assertNotNull(editMenu, "Edit menu should exist");
|
||||
|
||||
// Verify View Menu items use ALT_DOWN_MASK
|
||||
// Verify View Menu items
|
||||
Map<String, JMenuItem> viewItems = collectMenuItems(viewMenu);
|
||||
assertAcceleratorUsesAlt(viewItems.get("Font Size +"), KeyEvent.VK_EQUALS);
|
||||
assertAcceleratorUsesAlt(viewItems.get("Font Size -"), KeyEvent.VK_MINUS);
|
||||
assertAcceleratorUsesAlt(viewItems.get("Reset Font"), KeyEvent.VK_0);
|
||||
|
||||
// Crosshair Ruler must use Alt+Shift+R
|
||||
JMenuItem rulerItem = viewItems.get("Crosshair Ruler");
|
||||
assertNotNull(rulerItem, "Crosshair Ruler menu item must exist");
|
||||
KeyStroke rulerKs = rulerItem.getAccelerator();
|
||||
assertNotNull(rulerKs, "Crosshair Ruler should have an accelerator");
|
||||
assertEquals(KeyEvent.VK_R, rulerKs.getKeyCode(), "Key code mismatch for Crosshair Ruler");
|
||||
int rulerMods = rulerKs.getModifiers();
|
||||
assertTrue((rulerMods & (KeyEvent.ALT_DOWN_MASK | KeyEvent.ALT_MASK)) != 0,
|
||||
"Crosshair Ruler accelerator must have ALT modifier");
|
||||
assertTrue((rulerMods & (KeyEvent.SHIFT_DOWN_MASK | KeyEvent.SHIFT_MASK)) != 0,
|
||||
"Crosshair Ruler accelerator must have SHIFT modifier");
|
||||
assertEquals(0, rulerMods & (KeyEvent.CTRL_DOWN_MASK | KeyEvent.CTRL_MASK | KeyEvent.META_DOWN_MASK | KeyEvent.META_MASK),
|
||||
"Crosshair Ruler accelerator must NOT have CTRL or META modifiers");
|
||||
|
||||
// Verify Actions Menu items use ALT_DOWN_MASK
|
||||
Map<String, JMenuItem> actionItems = collectMenuItems(actionsMenu);
|
||||
assertAcceleratorUsesAlt(actionItems.get("Send Enter"), KeyEvent.VK_ENTER);
|
||||
@@ -75,6 +89,10 @@ public class MenuBarShortcutsTest {
|
||||
assertAcceleratorUsesAlt(actionItems.get("Cursor Select"), KeyEvent.VK_Q);
|
||||
assertAcceleratorUsesAlt(actionItems.get("Toggle Light Pen (Alt+L)"), KeyEvent.VK_L);
|
||||
assertAcceleratorUsesAlt(actionItems.get("File Transfer..."), KeyEvent.VK_T);
|
||||
|
||||
// Ensure Crosshair Ruler and Reset do not collide
|
||||
assertNotEquals(rulerKs, actionItems.get("Reset").getAccelerator(),
|
||||
"Crosshair Ruler and Reset accelerators must not collide");
|
||||
} finally {
|
||||
// Dispose frame
|
||||
app.dispose();
|
||||
@@ -89,6 +107,8 @@ public class MenuBarShortcutsTest {
|
||||
int mods = ks.getModifiers();
|
||||
assertTrue((mods & (KeyEvent.ALT_DOWN_MASK | KeyEvent.ALT_MASK)) != 0,
|
||||
"Menu item " + item.getText() + " accelerator must have ALT modifier");
|
||||
assertEquals(0, mods & (KeyEvent.SHIFT_DOWN_MASK | KeyEvent.SHIFT_MASK),
|
||||
"Menu item " + item.getText() + " accelerator must NOT have SHIFT modifier");
|
||||
assertEquals(0, mods & (KeyEvent.CTRL_DOWN_MASK | KeyEvent.CTRL_MASK | KeyEvent.META_DOWN_MASK | KeyEvent.META_MASK),
|
||||
"Menu item " + item.getText() + " accelerator must NOT have CTRL or META modifiers");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
package haus.nightmare.j3270.ui;
|
||||
|
||||
import haus.nightmare.j3270.config.Settings;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.HeadlessException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Verification test suite for Phase 1 UI Overlays and Color Alignment.
|
||||
* Covers items 1.1, 1.2, and 1.5 of the PhasedUpdates specification.
|
||||
*/
|
||||
public class Phase1UiOverlayTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("Item 1.1 & 1.2: TerminalPanel host and mono colors match IBM Host On-Demand specs")
|
||||
public void testTerminalPanelColorAlignment() {
|
||||
// Index 10: Orange (255, 162, 0)
|
||||
assertEquals(new Color(255, 162, 0), TerminalPanel.DEFAULT_HOST_COLORS[10]);
|
||||
|
||||
// Index 14: Mustard (160, 160, 0)
|
||||
assertEquals(new Color(160, 160, 0), TerminalPanel.DEFAULT_HOST_COLORS[14]);
|
||||
|
||||
// Index 1: Blue (120, 144, 240)
|
||||
assertEquals(new Color(120, 144, 240), TerminalPanel.DEFAULT_HOST_COLORS[1]);
|
||||
|
||||
// Base 4-Color mono defaults
|
||||
assertEquals(new Color(0, 255, 0), TerminalPanel.DEFAULT_MONO_NORMAL);
|
||||
assertEquals(new Color(255, 0, 0), TerminalPanel.DEFAULT_MONO_INTENSIFY);
|
||||
assertEquals(new Color(0, 255, 255), TerminalPanel.DEFAULT_MONO_PROTECTED);
|
||||
assertEquals(new Color(255, 255, 255), TerminalPanel.DEFAULT_MONO_PROTECTED_HIGH);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Item 1.5: ThemeManager HoD OIA category colors match specification")
|
||||
public void testThemeManagerOiaColors() {
|
||||
// oSI: Status / System Available -> CUSTOMBLUE (120, 144, 240)
|
||||
assertEquals(new Color(120, 144, 240), ThemeManager.getOiaStatusSysAvail());
|
||||
|
||||
// oII: Input Inhibited / X SYSTEM -> White (255, 255, 255)
|
||||
assertEquals(new Color(255, 255, 255), ThemeManager.getOiaInputInhibited());
|
||||
|
||||
// oAI: Attention / Reminders / Message Waiting -> Yellow (255, 255, 0)
|
||||
assertEquals(new Color(255, 255, 0), ThemeManager.getOiaAttention());
|
||||
|
||||
// oEI: Error Checks / Comm Check -> Red (255, 0, 0)
|
||||
assertEquals(new Color(255, 0, 0), ThemeManager.getOiaCommCheck());
|
||||
|
||||
// oOB: OIA Separator / Background -> Black (0, 0, 0)
|
||||
assertEquals(new Color(0, 0, 0), ThemeManager.getOiaBackground());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Item 1.5: TerminalPanel Crosshair Ruler and Cursor Style controls")
|
||||
public void testTerminalPanelRulerAndCursorStyle() {
|
||||
try {
|
||||
TerminalPanel panel = new TerminalPanel();
|
||||
|
||||
// Default cursor style is BLOCK
|
||||
assertEquals(TerminalPanel.CursorStyle.BLOCK, panel.getCursorStyle());
|
||||
panel.setCursorStyle(TerminalPanel.CursorStyle.UNDERLINE);
|
||||
assertEquals(TerminalPanel.CursorStyle.UNDERLINE, panel.getCursorStyle());
|
||||
|
||||
// Crosshair ruler toggling
|
||||
assertFalse(panel.isCrosshairRulerEnabled());
|
||||
panel.setCrosshairRulerEnabled(true);
|
||||
assertTrue(panel.isCrosshairRulerEnabled());
|
||||
panel.toggleCrosshairRuler();
|
||||
assertFalse(panel.isCrosshairRulerEnabled());
|
||||
|
||||
// Wallpaper image setter
|
||||
assertNull(panel.getWallpaperImage());
|
||||
} catch (HeadlessException e) {
|
||||
// Handled gracefully in headless CI environments
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Settings persistence for Crosshair Ruler and Cursor Style")
|
||||
public void testSettingsPersistence() {
|
||||
boolean originalRuler = Settings.getCrosshairRuler();
|
||||
String originalCursor = Settings.getCursorStyle();
|
||||
|
||||
try {
|
||||
Settings.setCrosshairRuler(true);
|
||||
assertTrue(Settings.getCrosshairRuler());
|
||||
|
||||
Settings.setCrosshairRuler(false);
|
||||
assertFalse(Settings.getCrosshairRuler());
|
||||
|
||||
Settings.setCursorStyle("UNDERLINE");
|
||||
assertEquals("UNDERLINE", Settings.getCursorStyle());
|
||||
|
||||
Settings.setCursorStyle("BLOCK");
|
||||
assertEquals("BLOCK", Settings.getCursorStyle());
|
||||
} finally {
|
||||
Settings.setCrosshairRuler(originalRuler);
|
||||
Settings.setCursorStyle(originalCursor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package haus.nightmare.j3270.ui;
|
||||
|
||||
import haus.nightmare.lib3270j.graphics.GocaConstants;
|
||||
import haus.nightmare.lib3270j.graphics.GraphicsPlane;
|
||||
import haus.nightmare.lib3270j.graphics.HODWallpaper;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.HeadlessException;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Verification test suite for Phase 2 UI Vector Graphics, Wallpaper, and Cursor Overlays.
|
||||
*/
|
||||
public class Phase2UiGraphicsTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("Test TerminalPanel Wallpaper Integration (Tile, Center, Stretch)")
|
||||
public void testTerminalPanelWallpaperModes() {
|
||||
try {
|
||||
TerminalPanel panel = new TerminalPanel();
|
||||
assertNull(panel.getHodWallpaper());
|
||||
assertNull(panel.getWallpaperImage());
|
||||
|
||||
BufferedImage img = new BufferedImage(32, 32, BufferedImage.TYPE_INT_ARGB);
|
||||
panel.setWallpaperImage(img);
|
||||
assertNotNull(panel.getWallpaperImage());
|
||||
assertNotNull(panel.getHodWallpaper());
|
||||
assertEquals(HODWallpaper.HOD_STRETCH, panel.getHodWallpaper().getDisplay());
|
||||
|
||||
panel.setWallpaperMode(HODWallpaper.HOD_TILE);
|
||||
assertEquals(HODWallpaper.HOD_TILE, panel.getHodWallpaper().getDisplay());
|
||||
|
||||
panel.setWallpaperMode(HODWallpaper.HOD_CENTER);
|
||||
assertEquals(HODWallpaper.HOD_CENTER, panel.getHodWallpaper().getDisplay());
|
||||
|
||||
HODWallpaper customWp = new HODWallpaper(img, HODWallpaper.HOD_TILE);
|
||||
panel.setHodWallpaper(customWp);
|
||||
assertSame(customWp, panel.getHodWallpaper());
|
||||
|
||||
// Clear wallpaper
|
||||
panel.setWallpaperImage(null);
|
||||
assertNull(panel.getHodWallpaper());
|
||||
assertNull(panel.getWallpaperImage());
|
||||
} catch (HeadlessException e) {
|
||||
// Handled gracefully in headless environments
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Test Graphics Plane Cursor Synchronization and Rendering")
|
||||
public void testGraphicsCursorOverlay() {
|
||||
try {
|
||||
TerminalPanel panel = new TerminalPanel();
|
||||
panel.setSize(new Dimension(800, 600));
|
||||
panel.setClient(new haus.nightmare.lib3270j.Telnet3270Client(new haus.nightmare.lib3270j.ConnectionConfig("localhost", 23)));
|
||||
|
||||
GraphicsPlane plane = panel.getClient() != null ? panel.getClient().getGraphicsPlane() : null;
|
||||
if (plane != null) {
|
||||
// Attach graphics cursor
|
||||
plane.attachGraphicCursor(100, 100);
|
||||
plane.setHodCursorShape(GocaConstants.GCURSOR_SHAPE_CROSSHAIR);
|
||||
assertTrue(plane.isGraphicCursorAttached());
|
||||
assertEquals(100, plane.getGraphicCursorX());
|
||||
assertEquals(100, plane.getGraphicCursorY());
|
||||
assertEquals(1, plane.getHodCursorShape());
|
||||
|
||||
// Paint component to verify no exceptions during rendering
|
||||
BufferedImage offscreen = new BufferedImage(800, 600, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2 = offscreen.createGraphics();
|
||||
panel.paint(g2);
|
||||
g2.dispose();
|
||||
|
||||
// Change cursor shape to box
|
||||
plane.setHodCursorShape(GocaConstants.GCURSOR_SHAPE_BOX);
|
||||
assertEquals(2, plane.getHodCursorShape());
|
||||
offscreen = new BufferedImage(800, 600, BufferedImage.TYPE_INT_ARGB);
|
||||
g2 = offscreen.createGraphics();
|
||||
panel.paint(g2);
|
||||
g2.dispose();
|
||||
|
||||
// Detach graphics cursor
|
||||
plane.detachGraphicCursor();
|
||||
assertFalse(plane.isGraphicCursorAttached());
|
||||
}
|
||||
} catch (HeadlessException e) {
|
||||
// Handled gracefully in headless environments
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user