Cleaning the UI and organizing
This commit is contained in:
@@ -6,6 +6,7 @@ import haus.nightmare.lib3270j.graphics.GraphicsMode;
|
|||||||
import haus.nightmare.lib3270j.listener.ConnectionListener;
|
import haus.nightmare.lib3270j.listener.ConnectionListener;
|
||||||
import haus.nightmare.lib3270j.listener.ScreenUpdateListener;
|
import haus.nightmare.lib3270j.listener.ScreenUpdateListener;
|
||||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||||
|
import haus.nightmare.lib3270j.charset.CodePageRegistry;
|
||||||
import haus.nightmare.j3270.ui.*;
|
import haus.nightmare.j3270.ui.*;
|
||||||
import haus.nightmare.j3270.ft.FileTransfer;
|
import haus.nightmare.j3270.ft.FileTransfer;
|
||||||
import haus.nightmare.j3270.ft.FileTransferDialog;
|
import haus.nightmare.j3270.ft.FileTransferDialog;
|
||||||
@@ -78,11 +79,18 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
private JCheckBoxMenuItem aplModeItem;
|
private JCheckBoxMenuItem aplModeItem;
|
||||||
private JCheckBoxMenuItem insertModeItem;
|
private JCheckBoxMenuItem insertModeItem;
|
||||||
private JCheckBoxMenuItem fourColorItem;
|
private JCheckBoxMenuItem fourColorItem;
|
||||||
private JCheckBoxMenuItem numLockItem;
|
|
||||||
private JCheckBoxMenuItem autoSkipItem;
|
|
||||||
private JCheckBoxMenuItem insertOffAidItem;
|
|
||||||
private JCheckBoxMenuItem statusBarItem;
|
private JCheckBoxMenuItem statusBarItem;
|
||||||
|
|
||||||
|
// Connection menu items
|
||||||
|
private JMenuItem disconnectMenuItem;
|
||||||
|
private JMenu hostTypeMenu;
|
||||||
|
private ButtonGroup hostTypeGroup;
|
||||||
|
private JMenu codePageMenu;
|
||||||
|
private ButtonGroup codePageGroup;
|
||||||
|
private JMenu graphicsModeMenu;
|
||||||
|
private ButtonGroup graphicsModeGroup;
|
||||||
|
private JMenu savedHostsMenu;
|
||||||
|
|
||||||
public J3270App() {
|
public J3270App() {
|
||||||
super("j3270 — Java TN3270 Terminal Emulator");
|
super("j3270 — Java TN3270 Terminal Emulator");
|
||||||
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
|
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
|
||||||
@@ -105,6 +113,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
if (client != null) {
|
if (client != null) {
|
||||||
statusBar.updateStatus();
|
statusBar.updateStatus();
|
||||||
syncModeMenuItems();
|
syncModeMenuItems();
|
||||||
|
syncConnectionMenuItems();
|
||||||
if (!isModalDialogActive() && KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow() == this) {
|
if (!isModalDialogActive() && KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow() == this) {
|
||||||
if (!terminalPanel.hasFocus()) {
|
if (!terminalPanel.hasFocus()) {
|
||||||
terminalPanel.requestFocusInWindow();
|
terminalPanel.requestFocusInWindow();
|
||||||
@@ -197,9 +206,6 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
if (aplModeItem != null) aplModeItem.setSelected(terminalPanel.isAplMode());
|
if (aplModeItem != null) aplModeItem.setSelected(terminalPanel.isAplMode());
|
||||||
if (insertModeItem != null) insertModeItem.setSelected(terminalPanel.isInsertMode());
|
if (insertModeItem != null) insertModeItem.setSelected(terminalPanel.isInsertMode());
|
||||||
if (fourColorItem != null) fourColorItem.setSelected(Settings.getFourColorOverride());
|
if (fourColorItem != null) fourColorItem.setSelected(Settings.getFourColorOverride());
|
||||||
if (numLockItem != null) numLockItem.setSelected(Settings.getNumericFieldLock());
|
|
||||||
if (autoSkipItem != null) autoSkipItem.setSelected(Settings.getAutoSkipEnabled());
|
|
||||||
if (insertOffAidItem != null) insertOffAidItem.setSelected(Settings.getInsertOffOnAid());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onThemeChanged(UITheme theme) {
|
private void onThemeChanged(UITheme theme) {
|
||||||
@@ -217,9 +223,6 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
|
|
||||||
// 1. File menu
|
// 1. File menu
|
||||||
JMenu fileMenu = createMenu("File");
|
JMenu fileMenu = createMenu("File");
|
||||||
fileMenu.add(createMenuItem("Connect...", KeyEvent.VK_N, this::showConnectDialog));
|
|
||||||
fileMenu.add(createMenuItem("Disconnect", KeyEvent.VK_D, this::disconnect));
|
|
||||||
fileMenu.addSeparator();
|
|
||||||
fileMenu.add(createMenuItem("Close Window", KeyEvent.VK_W, this::closeWindow));
|
fileMenu.add(createMenuItem("Close Window", KeyEvent.VK_W, this::closeWindow));
|
||||||
fileMenu.addSeparator();
|
fileMenu.addSeparator();
|
||||||
fileMenu.add(createMenuItem("Save Screen As...", -1, () -> ScreenExporter.showExportDialog(this, client, terminalPanel)));
|
fileMenu.add(createMenuItem("Save Screen As...", -1, () -> ScreenExporter.showExportDialog(this, client, terminalPanel)));
|
||||||
@@ -240,12 +243,13 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
// 2. Connection menu
|
// 2. Connection menu
|
||||||
JMenu connectionMenu = createMenu("Connection");
|
JMenu connectionMenu = createMenu("Connection");
|
||||||
connectionMenu.add(createMenuItem("Connect...", KeyEvent.VK_N, this::showConnectDialog));
|
connectionMenu.add(createMenuItem("Connect...", KeyEvent.VK_N, this::showConnectDialog));
|
||||||
connectionMenu.add(createMenuItem("Disconnect", KeyEvent.VK_D, this::disconnect));
|
disconnectMenuItem = createMenuItem("Disconnect", KeyEvent.VK_D, this::disconnect);
|
||||||
|
connectionMenu.add(disconnectMenuItem);
|
||||||
connectionMenu.addSeparator();
|
connectionMenu.addSeparator();
|
||||||
|
|
||||||
// Host Type Submenu (CMS/TSO/CICS)
|
// Host Type Submenu (CMS/TSO/CICS)
|
||||||
JMenu hostTypeMenu = createMenu("Host Type");
|
hostTypeMenu = createMenu("Host Type");
|
||||||
ButtonGroup hostTypeGroup = new ButtonGroup();
|
hostTypeGroup = new ButtonGroup();
|
||||||
for (haus.nightmare.lib3270j.ft.FTConfig.HostType ht : haus.nightmare.lib3270j.ft.FTConfig.HostType.values()) {
|
for (haus.nightmare.lib3270j.ft.FTConfig.HostType ht : haus.nightmare.lib3270j.ft.FTConfig.HostType.values()) {
|
||||||
JRadioButtonMenuItem htItem = new JRadioButtonMenuItem(ht.name());
|
JRadioButtonMenuItem htItem = new JRadioButtonMenuItem(ht.name());
|
||||||
ThemeManager.styleMenuItem(htItem);
|
ThemeManager.styleMenuItem(htItem);
|
||||||
@@ -254,16 +258,44 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
hostTypeMenu.add(htItem);
|
hostTypeMenu.add(htItem);
|
||||||
}
|
}
|
||||||
connectionMenu.add(hostTypeMenu);
|
connectionMenu.add(hostTypeMenu);
|
||||||
|
|
||||||
|
// Code Page Submenu
|
||||||
|
codePageMenu = createMenu("Code Page");
|
||||||
|
codePageGroup = new ButtonGroup();
|
||||||
|
for (String cpItem : CodePageRegistry.COMMON_CODE_PAGES) {
|
||||||
|
String cpId = cpItem.split(" -")[0].trim();
|
||||||
|
JRadioButtonMenuItem cpMi = new JRadioButtonMenuItem(cpItem);
|
||||||
|
cpMi.putClientProperty("cpId", cpId);
|
||||||
|
ThemeManager.styleMenuItem(cpMi);
|
||||||
|
codePageGroup.add(cpMi);
|
||||||
|
cpMi.addActionListener(e -> changeCodePage(cpId));
|
||||||
|
codePageMenu.add(cpMi);
|
||||||
|
}
|
||||||
|
connectionMenu.add(codePageMenu);
|
||||||
|
|
||||||
|
// Graphics Mode Submenu
|
||||||
|
graphicsModeMenu = createMenu("Graphics Mode");
|
||||||
|
graphicsModeGroup = new ButtonGroup();
|
||||||
|
for (GraphicsMode gm : GraphicsMode.values()) {
|
||||||
|
JRadioButtonMenuItem gmMi = new JRadioButtonMenuItem(gm.getDescription());
|
||||||
|
gmMi.putClientProperty("mode", gm);
|
||||||
|
ThemeManager.styleMenuItem(gmMi);
|
||||||
|
graphicsModeGroup.add(gmMi);
|
||||||
|
gmMi.addActionListener(e -> changeGraphicsMode(gm));
|
||||||
|
graphicsModeMenu.add(gmMi);
|
||||||
|
}
|
||||||
|
connectionMenu.add(graphicsModeMenu);
|
||||||
|
|
||||||
connectionMenu.addSeparator();
|
connectionMenu.addSeparator();
|
||||||
|
|
||||||
// Saved Connections Submenu
|
// Saved Connections Submenu
|
||||||
JMenu savedHostsMenu = createMenu("Saved Connections");
|
savedHostsMenu = createMenu("Saved Connections");
|
||||||
connectionMenu.add(savedHostsMenu);
|
connectionMenu.add(savedHostsMenu);
|
||||||
|
|
||||||
connectionMenu.addMenuListener(new javax.swing.event.MenuListener() {
|
connectionMenu.addMenuListener(new javax.swing.event.MenuListener() {
|
||||||
@Override
|
@Override
|
||||||
public void menuSelected(javax.swing.event.MenuEvent e) {
|
public void menuSelected(javax.swing.event.MenuEvent e) {
|
||||||
updateConnectionMenu(hostTypeMenu, hostTypeGroup, savedHostsMenu);
|
updateConnectionMenu();
|
||||||
}
|
}
|
||||||
@Override public void menuDeselected(javax.swing.event.MenuEvent e) {}
|
@Override public void menuDeselected(javax.swing.event.MenuEvent e) {}
|
||||||
@Override public void menuCanceled(javax.swing.event.MenuEvent e) {}
|
@Override public void menuCanceled(javax.swing.event.MenuEvent e) {}
|
||||||
@@ -286,7 +318,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
editMenu.add(createMenuItem("Find Previous", KeyEvent.VK_G, this::findPrevious, true));
|
editMenu.add(createMenuItem("Find Previous", KeyEvent.VK_G, this::findPrevious, true));
|
||||||
menuBar.add(editMenu);
|
menuBar.add(editMenu);
|
||||||
|
|
||||||
// 3. View menu
|
// 4. View menu
|
||||||
JMenu viewMenu = createMenu("View");
|
JMenu viewMenu = createMenu("View");
|
||||||
viewMenu.add(createMenuItem("Font Size +", KeyEvent.VK_EQUALS, () -> adjustFontSize(2), true));
|
viewMenu.add(createMenuItem("Font Size +", KeyEvent.VK_EQUALS, () -> adjustFontSize(2), true));
|
||||||
viewMenu.add(createMenuItem("Font Size -", KeyEvent.VK_MINUS, () -> adjustFontSize(-2), true));
|
viewMenu.add(createMenuItem("Font Size -", KeyEvent.VK_MINUS, () -> adjustFontSize(-2), true));
|
||||||
@@ -319,6 +351,14 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
themeMenu.add(lightThemeItem);
|
themeMenu.add(lightThemeItem);
|
||||||
|
|
||||||
viewMenu.add(themeMenu);
|
viewMenu.add(themeMenu);
|
||||||
|
|
||||||
|
fourColorItem = new JCheckBoxMenuItem("Base 4-Color Override", Settings.getFourColorOverride());
|
||||||
|
ThemeManager.styleMenuItem(fourColorItem);
|
||||||
|
fourColorItem.addActionListener(e -> {
|
||||||
|
Settings.setFourColorOverride(fourColorItem.isSelected());
|
||||||
|
terminalPanel.repaint();
|
||||||
|
});
|
||||||
|
viewMenu.add(fourColorItem);
|
||||||
viewMenu.addSeparator();
|
viewMenu.addSeparator();
|
||||||
|
|
||||||
statusBarItem = new JCheckBoxMenuItem("Status Bar", statusBar != null ? statusBar.isVisible() : Settings.getStatusBarVisible());
|
statusBarItem = new JCheckBoxMenuItem("Status Bar", statusBar != null ? statusBar.isVisible() : Settings.getStatusBarVisible());
|
||||||
@@ -359,57 +399,11 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
viewMenu.add(cursorMenu);
|
viewMenu.add(cursorMenu);
|
||||||
viewMenu.addSeparator();
|
viewMenu.addSeparator();
|
||||||
|
|
||||||
// CodePage Submenu
|
|
||||||
JMenu cpMenu = createMenu("Code Page");
|
|
||||||
String[] codePages = {
|
|
||||||
"037 - US / Canada / Brazil",
|
|
||||||
"1047 - IBM Open Systems / z/OS Unix",
|
|
||||||
"500 - International Latin-1",
|
|
||||||
"273 - Germany / Austria",
|
|
||||||
"277 - Denmark / Norway",
|
|
||||||
"278 - Sweden / Finland",
|
|
||||||
"280 - Italy",
|
|
||||||
"284 - Spain / Latin America",
|
|
||||||
"285 - United Kingdom",
|
|
||||||
"297 - France",
|
|
||||||
"870 - Eastern Europe / Latin-2",
|
|
||||||
"871 - Iceland",
|
|
||||||
"875 - Greece (Greek)",
|
|
||||||
"1026 - Turkey (Turkish)",
|
|
||||||
"1140 - US / Canada (Euro \u20AC)",
|
|
||||||
"1141 - Germany / Austria (Euro \u20AC)",
|
|
||||||
"1148 - International (Euro \u20AC)",
|
|
||||||
"930 - Japanese Katakana Mixed DBCS",
|
|
||||||
"939 - Japanese Latin Mixed DBCS",
|
|
||||||
"935 - Simplified Chinese Mixed DBCS",
|
|
||||||
"937 - Traditional Chinese Mixed DBCS",
|
|
||||||
"933 - Korean Mixed DBCS"
|
|
||||||
};
|
|
||||||
for (String cpItem : codePages) {
|
|
||||||
String cpId = cpItem.split(" -")[0].trim();
|
|
||||||
JMenuItem cpMi = new JMenuItem(cpItem);
|
|
||||||
ThemeManager.styleMenuItem(cpMi);
|
|
||||||
cpMi.addActionListener(e -> changeCodePage(cpId));
|
|
||||||
cpMenu.add(cpMi);
|
|
||||||
}
|
|
||||||
viewMenu.add(cpMenu);
|
|
||||||
|
|
||||||
// Graphics Mode Submenu
|
|
||||||
JMenu gfxMenu = createMenu("Graphics Mode");
|
|
||||||
for (GraphicsMode gm : GraphicsMode.values()) {
|
|
||||||
JMenuItem gmMi = new JMenuItem(gm.name());
|
|
||||||
ThemeManager.styleMenuItem(gmMi);
|
|
||||||
gmMi.addActionListener(e -> changeGraphicsMode(gm));
|
|
||||||
gfxMenu.add(gmMi);
|
|
||||||
}
|
|
||||||
viewMenu.add(gfxMenu);
|
|
||||||
|
|
||||||
viewMenu.addSeparator();
|
|
||||||
viewMenu.add(createMenuItem("Screen History...", KeyEvent.VK_H, this::showScreenHistoryDialog, true));
|
viewMenu.add(createMenuItem("Screen History...", KeyEvent.VK_H, this::showScreenHistoryDialog, true));
|
||||||
viewMenu.add(createMenuItem("Field Inspector...", -1, this::showFieldInspectorDialog));
|
viewMenu.add(createMenuItem("Field Inspector...", -1, this::showFieldInspectorDialog));
|
||||||
menuBar.add(viewMenu);
|
menuBar.add(viewMenu);
|
||||||
|
|
||||||
// 4. Modes menu
|
// 5. Modes menu
|
||||||
JMenu modesMenu = createMenu("Modes");
|
JMenu modesMenu = createMenu("Modes");
|
||||||
docModeItem = new JCheckBoxMenuItem("Document Mode (DOC)", terminalPanel.isDocMode());
|
docModeItem = new JCheckBoxMenuItem("Document Mode (DOC)", terminalPanel.isDocMode());
|
||||||
ThemeManager.styleMenuItem(docModeItem);
|
ThemeManager.styleMenuItem(docModeItem);
|
||||||
@@ -435,40 +429,6 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
insertModeItem.addActionListener(e -> terminalPanel.toggleInsertMode());
|
insertModeItem.addActionListener(e -> terminalPanel.toggleInsertMode());
|
||||||
modesMenu.add(insertModeItem);
|
modesMenu.add(insertModeItem);
|
||||||
|
|
||||||
modesMenu.addSeparator();
|
|
||||||
|
|
||||||
fourColorItem = new JCheckBoxMenuItem("Base 4-Color Override", Settings.getFourColorOverride());
|
|
||||||
ThemeManager.styleMenuItem(fourColorItem);
|
|
||||||
fourColorItem.addActionListener(e -> {
|
|
||||||
Settings.setFourColorOverride(fourColorItem.isSelected());
|
|
||||||
terminalPanel.repaint();
|
|
||||||
});
|
|
||||||
modesMenu.add(fourColorItem);
|
|
||||||
|
|
||||||
numLockItem = new JCheckBoxMenuItem("Numeric Field Lock", Settings.getNumericFieldLock());
|
|
||||||
ThemeManager.styleMenuItem(numLockItem);
|
|
||||||
numLockItem.addActionListener(e -> {
|
|
||||||
Settings.setNumericFieldLock(numLockItem.isSelected());
|
|
||||||
terminalPanel.applyModeSettings();
|
|
||||||
});
|
|
||||||
modesMenu.add(numLockItem);
|
|
||||||
|
|
||||||
autoSkipItem = new JCheckBoxMenuItem("Auto-Skip Across Fields", Settings.getAutoSkipEnabled());
|
|
||||||
ThemeManager.styleMenuItem(autoSkipItem);
|
|
||||||
autoSkipItem.addActionListener(e -> {
|
|
||||||
Settings.setAutoSkipEnabled(autoSkipItem.isSelected());
|
|
||||||
terminalPanel.applyModeSettings();
|
|
||||||
});
|
|
||||||
modesMenu.add(autoSkipItem);
|
|
||||||
|
|
||||||
insertOffAidItem = new JCheckBoxMenuItem("Reset Insert on AID Key", Settings.getInsertOffOnAid());
|
|
||||||
ThemeManager.styleMenuItem(insertOffAidItem);
|
|
||||||
insertOffAidItem.addActionListener(e -> {
|
|
||||||
Settings.setInsertOffOnAid(insertOffAidItem.isSelected());
|
|
||||||
terminalPanel.applyModeSettings();
|
|
||||||
});
|
|
||||||
modesMenu.add(insertOffAidItem);
|
|
||||||
|
|
||||||
modesMenu.addMenuListener(new javax.swing.event.MenuListener() {
|
modesMenu.addMenuListener(new javax.swing.event.MenuListener() {
|
||||||
@Override
|
@Override
|
||||||
public void menuSelected(javax.swing.event.MenuEvent e) {
|
public void menuSelected(javax.swing.event.MenuEvent e) {
|
||||||
@@ -479,7 +439,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
});
|
});
|
||||||
menuBar.add(modesMenu);
|
menuBar.add(modesMenu);
|
||||||
|
|
||||||
// 5. Actions menu
|
// 6. Actions menu
|
||||||
JMenu actionsMenu = createMenu("Actions");
|
JMenu actionsMenu = createMenu("Actions");
|
||||||
actionsMenu.add(createMenuItem("Send Enter", KeyEvent.VK_ENTER, () -> {
|
actionsMenu.add(createMenuItem("Send Enter", KeyEvent.VK_ENTER, () -> {
|
||||||
if (client != null && client.getConnectionState().isFullSession()) {
|
if (client != null && client.getConnectionState().isFullSession()) {
|
||||||
@@ -525,11 +485,10 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
actionsMenu.addSeparator();
|
actionsMenu.addSeparator();
|
||||||
actionsMenu.add(createMenuItem("Run Keystrokes / Script...", -1, this::showScriptDialog));
|
actionsMenu.add(createMenuItem("Run Keystrokes / Script...", -1, this::showScriptDialog));
|
||||||
actionsMenu.add(createMenuItem("Macro Runtime (<HASCRIPT>)...", KeyEvent.VK_M, this::showMacroDialog, true));
|
actionsMenu.add(createMenuItem("Macro Runtime (<HASCRIPT>)...", KeyEvent.VK_M, this::showMacroDialog, true));
|
||||||
actionsMenu.add(createMenuItem("Screen History...", KeyEvent.VK_H, this::showScreenHistoryDialog, true));
|
|
||||||
actionsMenu.add(createMenuItem("File Transfer...", KeyEvent.VK_T, this::showFileTransferDialog, true));
|
actionsMenu.add(createMenuItem("File Transfer...", KeyEvent.VK_T, this::showFileTransferDialog, true));
|
||||||
menuBar.add(actionsMenu);
|
menuBar.add(actionsMenu);
|
||||||
|
|
||||||
// 5. Help menu
|
// 7. Help menu
|
||||||
JMenu helpMenu = createMenu("Help");
|
JMenu helpMenu = createMenu("Help");
|
||||||
helpMenu.add(createMenuItem("Key Mappings", -1, this::showKeyMappings));
|
helpMenu.add(createMenuItem("Key Mappings", -1, this::showKeyMappings));
|
||||||
helpMenu.add(createMenuItem("Mnemonic Keystroke Reference", -1, this::showMnemonicHelp));
|
helpMenu.add(createMenuItem("Mnemonic Keystroke Reference", -1, this::showMnemonicHelp));
|
||||||
@@ -537,6 +496,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
helpMenu.add(createMenuItem("About", -1, this::showAbout));
|
helpMenu.add(createMenuItem("About", -1, this::showAbout));
|
||||||
menuBar.add(helpMenu);
|
menuBar.add(helpMenu);
|
||||||
|
|
||||||
|
syncConnectionMenuItems();
|
||||||
setJMenuBar(menuBar);
|
setJMenuBar(menuBar);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -708,6 +668,9 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
private void changeCodePage(String cpId) {
|
private void changeCodePage(String cpId) {
|
||||||
if (client != null) {
|
if (client != null) {
|
||||||
client.setCodePage(cpId);
|
client.setCodePage(cpId);
|
||||||
|
if (client.getConfig() != null) {
|
||||||
|
client.getConfig().setCodePage(cpId);
|
||||||
|
}
|
||||||
client.getScreenBuffer().translateToUnicode();
|
client.getScreenBuffer().translateToUnicode();
|
||||||
terminalPanel.refreshScreen();
|
terminalPanel.refreshScreen();
|
||||||
statusBar.updateStatus();
|
statusBar.updateStatus();
|
||||||
@@ -716,6 +679,12 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void changeGraphicsMode(GraphicsMode mode) {
|
private void changeGraphicsMode(GraphicsMode mode) {
|
||||||
|
if (isConnected()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (client != null && client.getConfig() != null) {
|
||||||
|
client.getConfig().setGraphicsMode(mode);
|
||||||
|
}
|
||||||
if (client != null && client.getDataStreamProcessor() != null) {
|
if (client != null && client.getDataStreamProcessor() != null) {
|
||||||
client.getDataStreamProcessor().getQueryReplyBuilder().setGraphicsMode(mode);
|
client.getDataStreamProcessor().getQueryReplyBuilder().setGraphicsMode(mode);
|
||||||
terminalPanel.repaint();
|
terminalPanel.repaint();
|
||||||
@@ -857,45 +826,103 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateConnectionMenu(JMenu hostTypeMenu, ButtonGroup hostTypeGroup, JMenu savedHostsMenu) {
|
public void syncConnectionMenuItems() {
|
||||||
haus.nightmare.lib3270j.ft.FTConfig.HostType currentHt = haus.nightmare.lib3270j.ft.FTConfig.HostType.TSO;
|
boolean connected = isConnected();
|
||||||
if (client != null && client.getConfig() != null) {
|
if (disconnectMenuItem != null) {
|
||||||
currentHt = client.getConfig().getFtHostType();
|
disconnectMenuItem.setEnabled(connected);
|
||||||
} else {
|
|
||||||
String def = Settings.getDefaultHostType();
|
|
||||||
try {
|
|
||||||
if (def != null) currentHt = haus.nightmare.lib3270j.ft.FTConfig.HostType.valueOf(def.trim().toUpperCase());
|
|
||||||
} catch (Exception ignored) {}
|
|
||||||
}
|
}
|
||||||
|
if (graphicsModeMenu != null) {
|
||||||
|
graphicsModeMenu.setEnabled(!connected);
|
||||||
|
graphicsModeMenu.setToolTipText(connected
|
||||||
|
? "Graphics Mode is negotiated at connection time and cannot be changed while connected."
|
||||||
|
: null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < hostTypeMenu.getItemCount(); i++) {
|
public JMenu getGraphicsModeMenu() {
|
||||||
JMenuItem item = hostTypeMenu.getItem(i);
|
return graphicsModeMenu;
|
||||||
if (item instanceof JRadioButtonMenuItem) {
|
}
|
||||||
JRadioButtonMenuItem rb = (JRadioButtonMenuItem) item;
|
|
||||||
if (rb.getText().equalsIgnoreCase(currentHt.name())) {
|
public JMenu getCodePageMenu() {
|
||||||
rb.setSelected(true);
|
return codePageMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateConnectionMenu() {
|
||||||
|
syncConnectionMenuItems();
|
||||||
|
|
||||||
|
if (hostTypeMenu != null) {
|
||||||
|
haus.nightmare.lib3270j.ft.FTConfig.HostType currentHt = haus.nightmare.lib3270j.ft.FTConfig.HostType.TSO;
|
||||||
|
if (client != null && client.getConfig() != null) {
|
||||||
|
currentHt = client.getConfig().getFtHostType();
|
||||||
|
} else {
|
||||||
|
String def = Settings.getDefaultHostType();
|
||||||
|
try {
|
||||||
|
if (def != null) currentHt = haus.nightmare.lib3270j.ft.FTConfig.HostType.valueOf(def.trim().toUpperCase());
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < hostTypeMenu.getItemCount(); i++) {
|
||||||
|
JMenuItem item = hostTypeMenu.getItem(i);
|
||||||
|
if (item instanceof JRadioButtonMenuItem) {
|
||||||
|
JRadioButtonMenuItem rb = (JRadioButtonMenuItem) item;
|
||||||
|
if (rb.getText().equalsIgnoreCase(currentHt.name())) {
|
||||||
|
rb.setSelected(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
savedHostsMenu.removeAll();
|
if (codePageMenu != null) {
|
||||||
java.util.List<haus.nightmare.j3270.storage.SavedHost> savedList = haus.nightmare.j3270.storage.HostStorage.getSavedHosts();
|
String currentCp = (client != null && client.getConfig() != null && client.getConfig().getCodePage() != null)
|
||||||
if (savedList.isEmpty()) {
|
? client.getConfig().getCodePage()
|
||||||
JMenuItem noneItem = new JMenuItem("(No Saved Connections)");
|
: Settings.getCodePage();
|
||||||
noneItem.setEnabled(false);
|
for (int i = 0; i < codePageMenu.getItemCount(); i++) {
|
||||||
ThemeManager.styleMenuItem(noneItem);
|
JMenuItem item = codePageMenu.getItem(i);
|
||||||
savedHostsMenu.add(noneItem);
|
if (item instanceof JRadioButtonMenuItem) {
|
||||||
} else {
|
JRadioButtonMenuItem rb = (JRadioButtonMenuItem) item;
|
||||||
for (haus.nightmare.j3270.storage.SavedHost sh : savedList) {
|
Object id = rb.getClientProperty("cpId");
|
||||||
String label = sh.getName();
|
if (id != null && id.toString().equalsIgnoreCase(currentCp)) {
|
||||||
if (label == null || label.trim().isEmpty()) {
|
rb.setSelected(true);
|
||||||
label = sh.getHost() + ":" + sh.getPort();
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (graphicsModeMenu != null) {
|
||||||
|
GraphicsMode currentGfx = (client != null && client.getConfig() != null && client.getConfig().getGraphicsMode() != null)
|
||||||
|
? client.getConfig().getGraphicsMode()
|
||||||
|
: Settings.getGraphicsMode();
|
||||||
|
for (int i = 0; i < graphicsModeMenu.getItemCount(); i++) {
|
||||||
|
JMenuItem item = graphicsModeMenu.getItem(i);
|
||||||
|
if (item instanceof JRadioButtonMenuItem) {
|
||||||
|
JRadioButtonMenuItem rb = (JRadioButtonMenuItem) item;
|
||||||
|
if (rb.getClientProperty("mode") == currentGfx) {
|
||||||
|
rb.setSelected(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (savedHostsMenu != null) {
|
||||||
|
savedHostsMenu.removeAll();
|
||||||
|
java.util.List<haus.nightmare.j3270.storage.SavedHost> savedList = haus.nightmare.j3270.storage.HostStorage.getSavedHosts();
|
||||||
|
if (savedList.isEmpty()) {
|
||||||
|
JMenuItem noneItem = new JMenuItem("(No Saved Connections)");
|
||||||
|
noneItem.setEnabled(false);
|
||||||
|
ThemeManager.styleMenuItem(noneItem);
|
||||||
|
savedHostsMenu.add(noneItem);
|
||||||
|
} else {
|
||||||
|
for (haus.nightmare.j3270.storage.SavedHost sh : savedList) {
|
||||||
|
String label = sh.getName();
|
||||||
|
if (label == null || label.trim().isEmpty()) {
|
||||||
|
label = sh.getHost() + ":" + sh.getPort();
|
||||||
|
}
|
||||||
|
String ht = (sh.getHostType() != null && !sh.getHostType().trim().isEmpty()) ? sh.getHostType().trim() : "TSO";
|
||||||
|
JMenuItem hostItem = new JMenuItem(label + " [" + ht + "]");
|
||||||
|
ThemeManager.styleMenuItem(hostItem);
|
||||||
|
hostItem.addActionListener(ev -> connect(sh.toConnectionConfig(), true));
|
||||||
|
savedHostsMenu.add(hostItem);
|
||||||
}
|
}
|
||||||
String ht = (sh.getHostType() != null && !sh.getHostType().trim().isEmpty()) ? sh.getHostType().trim() : "TSO";
|
|
||||||
JMenuItem hostItem = new JMenuItem(label + " [" + ht + "]");
|
|
||||||
ThemeManager.styleMenuItem(hostItem);
|
|
||||||
hostItem.addActionListener(ev -> connect(sh.toConnectionConfig(), true));
|
|
||||||
savedHostsMenu.add(hostItem);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1166,6 +1193,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
terminalPanel.setClient(null);
|
terminalPanel.setClient(null);
|
||||||
statusBar.setClient(null, terminalPanel);
|
statusBar.setClient(null, terminalPanel);
|
||||||
syncModeMenuItems();
|
syncModeMenuItems();
|
||||||
|
syncConnectionMenuItems();
|
||||||
terminalPanel.repaint();
|
terminalPanel.repaint();
|
||||||
setTitle("j3270 — Java TN3270 Terminal Emulator");
|
setTitle("j3270 — Java TN3270 Terminal Emulator");
|
||||||
};
|
};
|
||||||
@@ -1212,6 +1240,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
public void onConnectionStateChanged(ConnectionState oldState, ConnectionState newState) {
|
public void onConnectionStateChanged(ConnectionState oldState, ConnectionState newState) {
|
||||||
log.info("Connection state: " + oldState + " -> " + newState);
|
log.info("Connection state: " + oldState + " -> " + newState);
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
syncConnectionMenuItems();
|
||||||
statusBar.updateStatus();
|
statusBar.updateStatus();
|
||||||
terminalPanel.repaint();
|
terminalPanel.repaint();
|
||||||
|
|
||||||
@@ -1378,20 +1407,32 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
JOptionPane.showMessageDialog(this, sp, "Mnemonic Keystroke Reference", JOptionPane.INFORMATION_MESSAGE);
|
JOptionPane.showMessageDialog(this, sp, "Mnemonic Keystroke Reference", JOptionPane.INFORMATION_MESSAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the descriptive text for the About dialog and version reporting.
|
||||||
|
*/
|
||||||
|
public static String getAboutText() {
|
||||||
|
return "j3270 v" + Version.getVersion() + " — Java TN3270 Terminal Emulator\n" +
|
||||||
|
"lib3270j v" + haus.nightmare.lib3270j.Version.getVersion() + "\n\n" +
|
||||||
|
"A pure Java reimplementation of x3270 & IBM Host On-Demand ECL.\n\n" +
|
||||||
|
"Features:\n" +
|
||||||
|
"• TN3270 & TN3270E (RFC 2355, SSCP-LU, NVT)\n" +
|
||||||
|
"• Models 2-5 & Dynamic Geometry\n" +
|
||||||
|
"• IBM 3287/3286 Printer Session Emulation (SCS & LU3)\n" +
|
||||||
|
"• IND$FILE CUT & DFT File Transfer + Host Catalog Query\n" +
|
||||||
|
"• GDDM/GOCA Vector Graphics & Programmed Symbols\n" +
|
||||||
|
"• 40+ Pluggable EBCDIC Codepages & DBCS Support\n" +
|
||||||
|
"• ECL Presentation Space Search, Inspector & Scripting";
|
||||||
|
}
|
||||||
|
|
||||||
private void showAbout() {
|
private void showAbout() {
|
||||||
JOptionPane.showMessageDialog(this,
|
String aboutText = getAboutText();
|
||||||
"j3270 — Java TN3270 Terminal Emulator\n\n" +
|
Image iconImg = AppIcon.getDefaultIcon();
|
||||||
"A pure Java reimplementation of x3270 & IBM Host On-Demand ECL.\n" +
|
if (iconImg != null) {
|
||||||
"lib3270j v0.2.0\n\n" +
|
ImageIcon icon = new ImageIcon(iconImg.getScaledInstance(64, 64, Image.SCALE_SMOOTH));
|
||||||
"Features:\n" +
|
JOptionPane.showMessageDialog(this, aboutText, "About j3270", JOptionPane.INFORMATION_MESSAGE, icon);
|
||||||
"• TN3270 & TN3270E (RFC 2355, SSCP-LU, NVT)\n" +
|
} else {
|
||||||
"• Models 2-5 & Dynamic Geometry\n" +
|
JOptionPane.showMessageDialog(this, aboutText, "About j3270", JOptionPane.INFORMATION_MESSAGE);
|
||||||
"• IBM 3287/3286 Printer Session Emulation (SCS & LU3)\n" +
|
}
|
||||||
"• IND$FILE CUT & DFT File Transfer + Host Catalog Query\n" +
|
|
||||||
"• GDDM/GOCA Vector Graphics & Programmed Symbols\n" +
|
|
||||||
"• 22+ Pluggable EBCDIC Codepages & DBCS Support\n" +
|
|
||||||
"• ECL Presentation Space Search, Inspector & Scripting",
|
|
||||||
"About j3270", JOptionPane.INFORMATION_MESSAGE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1497,6 +1538,30 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void printHelp() {
|
||||||
|
System.out.println("j3270 " + Version.getVersion() + " (lib3270j " + haus.nightmare.lib3270j.Version.getVersion() + ")");
|
||||||
|
System.out.println("Usage: java -jar j3270.jar [options] [host [port [model]]]");
|
||||||
|
System.out.println();
|
||||||
|
System.out.println("Options:");
|
||||||
|
System.out.println(" -v, --version Print version information and exit");
|
||||||
|
System.out.println(" -h, --help Print this help message and exit");
|
||||||
|
System.out.println(" -d, --debug Enable verbose debug logging to j3270.log");
|
||||||
|
System.out.println(" -s, --tls, --ssl Enable TLS/SSL encryption");
|
||||||
|
System.out.println(" -k, --insecure,");
|
||||||
|
System.out.println(" --no-verify-cert Disable TLS certificate verification");
|
||||||
|
System.out.println(" -p, -P, --no-tn3270e Disable TN3270E extended protocol negotiation");
|
||||||
|
System.out.println(" --tn3270e Enforce TN3270E negotiation");
|
||||||
|
System.out.println(" --auto-sys-unlock Auto unlock keyboard on host EOR");
|
||||||
|
System.out.println(" --no-auto-sys-unlock Require explicit Host RESET/unlock");
|
||||||
|
System.out.println(" -ht, --host-type <type> Host type: TSO, VM, CMS, CICS");
|
||||||
|
System.out.println(" -g, --graphics <mode> Graphics mode: NONE, VECTOR, PROGRAMMED_SYMBOLS, FULL");
|
||||||
|
System.out.println(" -c, --config <file> Load INI configuration file");
|
||||||
|
System.out.println(" --proxy <url> Proxy URL (e.g. socks5://host:1080, http://host:8080)");
|
||||||
|
System.out.println(" --profile <name> Connect using saved profile name or ID");
|
||||||
|
System.out.println();
|
||||||
|
System.out.println("Terminal Models: 2 (24x80), 3 (32x80), 4 (43x80, default), 5 (27x132), dynamic (0)");
|
||||||
|
}
|
||||||
|
|
||||||
// ========== Main ==========
|
// ========== Main ==========
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
@@ -1514,7 +1579,13 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
java.util.List<String> remainingArgs = new java.util.ArrayList<>();
|
java.util.List<String> remainingArgs = new java.util.ArrayList<>();
|
||||||
for (int i = 0; i < args.length; i++) {
|
for (int i = 0; i < args.length; i++) {
|
||||||
String arg = args[i];
|
String arg = args[i];
|
||||||
if ("-d".equals(arg) || "--debug".equals(arg)) {
|
if ("-v".equals(arg) || "--version".equals(arg)) {
|
||||||
|
System.out.println("j3270 " + Version.getVersion() + " (lib3270j " + haus.nightmare.lib3270j.Version.getVersion() + ")");
|
||||||
|
System.exit(0);
|
||||||
|
} else if ("-h".equals(arg) || "--help".equals(arg)) {
|
||||||
|
printHelp();
|
||||||
|
System.exit(0);
|
||||||
|
} else if ("-d".equals(arg) || "--debug".equals(arg)) {
|
||||||
debug = true;
|
debug = true;
|
||||||
} else if ("-s".equals(arg) || "--tls".equals(arg) || "--ssl".equals(arg)) {
|
} else if ("-s".equals(arg) || "--tls".equals(arg) || "--ssl".equals(arg)) {
|
||||||
cliTls = true;
|
cliTls = true;
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package haus.nightmare.j3270;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version information for the j3270 application.
|
||||||
|
*/
|
||||||
|
public final class Version {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hardcoded fallback release version matching build.gradle and pom coordinates.
|
||||||
|
*/
|
||||||
|
public static final String VERSION = "1.2.4";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Canonical application name.
|
||||||
|
*/
|
||||||
|
public static final String NAME = "j3270";
|
||||||
|
|
||||||
|
private Version() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the runtime version string of the j3270 application.
|
||||||
|
* Inspects the package implementation version first (populated when loaded from a JAR),
|
||||||
|
* and falls back to the compile-time {@link #VERSION} constant if null or blank.
|
||||||
|
*
|
||||||
|
* @return the version string (e.g. "1.2.4")
|
||||||
|
*/
|
||||||
|
public static String getVersion() {
|
||||||
|
Package pkg = Version.class.getPackage();
|
||||||
|
if (pkg != null) {
|
||||||
|
String implVer = pkg.getImplementationVersion();
|
||||||
|
if (implVer != null && !implVer.isBlank()) {
|
||||||
|
return implVer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return VERSION;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import haus.nightmare.j3270.storage.HostStorage;
|
|||||||
import haus.nightmare.j3270.storage.SavedHost;
|
import haus.nightmare.j3270.storage.SavedHost;
|
||||||
import haus.nightmare.lib3270j.ConnectionConfig;
|
import haus.nightmare.lib3270j.ConnectionConfig;
|
||||||
import haus.nightmare.lib3270j.TerminalModel;
|
import haus.nightmare.lib3270j.TerminalModel;
|
||||||
|
import haus.nightmare.lib3270j.charset.CodePageRegistry;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.border.EmptyBorder;
|
import javax.swing.border.EmptyBorder;
|
||||||
@@ -278,51 +279,7 @@ public class ConnectDialog extends JDialog {
|
|||||||
contentPanel.add(cpLabel, gbc);
|
contentPanel.add(cpLabel, gbc);
|
||||||
gbc.gridx = 1;
|
gbc.gridx = 1;
|
||||||
gbc.weightx = 1.0;
|
gbc.weightx = 1.0;
|
||||||
String[] commonCodePages = {
|
codePageCombo = new JComboBox<>(CodePageRegistry.COMMON_CODE_PAGES);
|
||||||
"037 - US / Canada / Brazil",
|
|
||||||
"1047 - IBM Open Systems / z/OS Unix",
|
|
||||||
"500 - International Latin-1",
|
|
||||||
"273 - Germany / Austria",
|
|
||||||
"277 - Denmark / Norway",
|
|
||||||
"278 - Sweden / Finland",
|
|
||||||
"280 - Italy",
|
|
||||||
"284 - Spain / Latin America",
|
|
||||||
"285 - United Kingdom",
|
|
||||||
"297 - France",
|
|
||||||
"870 - Eastern Europe / Latin-2",
|
|
||||||
"871 - Iceland",
|
|
||||||
"875 - Greece (Greek)",
|
|
||||||
"1026 - Turkey (Turkish Latin-5)",
|
|
||||||
"1155 - Turkey (Turkish Latin-5 Euro \u20AC)",
|
|
||||||
"905 - Turkey (Turkish Latin-3)",
|
|
||||||
"420 - Arabic Bilingual",
|
|
||||||
"424 - Hebrew (with Latin)",
|
|
||||||
"803 - Hebrew Character Set",
|
|
||||||
"838 - Thai Extended",
|
|
||||||
"1160 - Thai Euro \u20AC",
|
|
||||||
"1025 - Cyrillic Multilingual",
|
|
||||||
"1123 - Cyrillic Ukraine",
|
|
||||||
"1154 - Cyrillic Euro \u20AC",
|
|
||||||
"880 - Cyrillic (Russian)",
|
|
||||||
"1140 - US / Canada (Euro \u20AC)",
|
|
||||||
"1141 - Germany / Austria (Euro \u20AC)",
|
|
||||||
"1142 - Denmark / Norway (Euro \u20AC)",
|
|
||||||
"1143 - Sweden / Finland (Euro \u20AC)",
|
|
||||||
"1144 - Italy (Euro \u20AC)",
|
|
||||||
"1145 - Spain / Latin America (Euro \u20AC)",
|
|
||||||
"1146 - United Kingdom (Euro \u20AC)",
|
|
||||||
"1147 - France (Euro \u20AC)",
|
|
||||||
"1148 - International (Euro \u20AC)",
|
|
||||||
"1149 - Iceland (Euro \u20AC)",
|
|
||||||
"930 - Japanese Katakana Mixed DBCS",
|
|
||||||
"939 - Japanese Latin Mixed DBCS",
|
|
||||||
"935 - Simplified Chinese Mixed DBCS",
|
|
||||||
"937 - Traditional Chinese Mixed DBCS",
|
|
||||||
"1388 - Simplified Chinese Extended DBCS",
|
|
||||||
"1371 - Traditional Chinese Extended DBCS",
|
|
||||||
"933 - Korean Mixed DBCS"
|
|
||||||
};
|
|
||||||
codePageCombo = new JComboBox<>(commonCodePages);
|
|
||||||
codePageCombo.setFont(ThemeManager.getMonospacedUiFont());
|
codePageCombo.setFont(ThemeManager.getMonospacedUiFont());
|
||||||
ThemeManager.styleComboBox(codePageCombo);
|
ThemeManager.styleComboBox(codePageCombo);
|
||||||
setInitialCodePage(haus.nightmare.j3270.config.Settings.getCodePage());
|
setInitialCodePage(haus.nightmare.j3270.config.Settings.getCodePage());
|
||||||
|
|||||||
@@ -1998,7 +1998,7 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable, I
|
|||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
Container top = getTopLevelAncestor();
|
Container top = getTopLevelAncestor();
|
||||||
if (top instanceof JFrame) {
|
if (top instanceof JFrame) {
|
||||||
((JFrame) top).setTitle(title != null && !title.isEmpty() ? ("j3270 - " + title) : "j3270");
|
((JFrame) top).setTitle(title != null && !title.isEmpty() ? ("j3270 — " + title) : "j3270 — Java TN3270 Terminal Emulator");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,213 @@
|
|||||||
|
package haus.nightmare.j3270;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.charset.CodePage;
|
||||||
|
import haus.nightmare.lib3270j.charset.CodePageRegistry;
|
||||||
|
import haus.nightmare.lib3270j.graphics.GraphicsMode;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.HeadlessException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verification test suite for Phase 31:
|
||||||
|
* Help > About Dialogue Version Reporting and j3270 Interface Inconsistency Fixes (v1.2.4).
|
||||||
|
*/
|
||||||
|
public class Phase31AboutAndInterfaceTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Phase 31.1: Dedicated Version classes report v1.2.4 single source of truth")
|
||||||
|
public void testVersionClasses() {
|
||||||
|
// lib3270j
|
||||||
|
assertEquals("1.2.4", haus.nightmare.lib3270j.Version.VERSION);
|
||||||
|
assertEquals("lib3270j", haus.nightmare.lib3270j.Version.NAME);
|
||||||
|
assertEquals("1.2.4", haus.nightmare.lib3270j.Version.getVersion());
|
||||||
|
|
||||||
|
// j3270
|
||||||
|
assertEquals("1.2.4", haus.nightmare.j3270.Version.VERSION);
|
||||||
|
assertEquals("j3270", haus.nightmare.j3270.Version.NAME);
|
||||||
|
assertEquals("1.2.4", haus.nightmare.j3270.Version.getVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Phase 31.2: Help > About dialogue text properly reports j3270 v1.2.4 and lib3270j v1.2.4")
|
||||||
|
public void testAboutTextContent() {
|
||||||
|
String aboutText = J3270App.getAboutText();
|
||||||
|
assertNotNull(aboutText, "About text must not be null");
|
||||||
|
|
||||||
|
// Verify version reports
|
||||||
|
assertTrue(aboutText.contains("j3270 v1.2.4"), "About text must report j3270 v1.2.4");
|
||||||
|
assertTrue(aboutText.contains("lib3270j v1.2.4"), "About text must report lib3270j v1.2.4");
|
||||||
|
|
||||||
|
// Verify stale pre-release versions are eradicated
|
||||||
|
assertFalse(aboutText.contains("0.2.0"), "About text must not contain stale version 0.2.0");
|
||||||
|
assertFalse(aboutText.contains("0.1.0"), "About text must not contain stale version 0.1.0");
|
||||||
|
|
||||||
|
// Verify 40+ codepages reporting
|
||||||
|
assertTrue(aboutText.contains("40+ Pluggable EBCDIC Codepages & DBCS Support"),
|
||||||
|
"About text must report 40+ Pluggable EBCDIC Codepages");
|
||||||
|
assertFalse(aboutText.contains("22+"), "About text must not report stale 22+ codepages");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Phase 31.3: CodePageRegistry.COMMON_CODE_PAGES defines 40+ supported codepages that all resolve")
|
||||||
|
public void testCommonCodePagesRegistration() {
|
||||||
|
String[] common = CodePageRegistry.COMMON_CODE_PAGES;
|
||||||
|
assertNotNull(common, "COMMON_CODE_PAGES must not be null");
|
||||||
|
assertTrue(common.length >= 40, "COMMON_CODE_PAGES must define at least 40 entries, found: " + common.length);
|
||||||
|
|
||||||
|
for (String entry : common) {
|
||||||
|
String cpId = entry.split(" -")[0].trim();
|
||||||
|
CodePage cp = CodePageRegistry.resolveCodePage(cpId);
|
||||||
|
assertNotNull(cp, "Codepage identifier '" + cpId + "' from entry '" + entry + "' must resolve in CodePageRegistry");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Phase 31.4: Menu bar consistency: Code Page & Graphics Mode in Connection menu, Screen History in View")
|
||||||
|
public void testMenuBarConsistency() {
|
||||||
|
J3270App app;
|
||||||
|
try {
|
||||||
|
app = new J3270App();
|
||||||
|
} catch (HeadlessException e) {
|
||||||
|
// Headless environment without X11/display
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
JMenuBar mb = app.getJMenuBar();
|
||||||
|
assertNotNull(mb, "JMenuBar must exist");
|
||||||
|
|
||||||
|
JMenu viewMenu = null;
|
||||||
|
JMenu actionsMenu = null;
|
||||||
|
JMenu fileMenu = null;
|
||||||
|
JMenu connectionMenu = null;
|
||||||
|
|
||||||
|
for (int i = 0; i < mb.getMenuCount(); i++) {
|
||||||
|
JMenu m = mb.getMenu(i);
|
||||||
|
if (m != null) {
|
||||||
|
if ("View".equals(m.getText())) viewMenu = m;
|
||||||
|
else if ("Actions".equals(m.getText())) actionsMenu = m;
|
||||||
|
else if ("File".equals(m.getText())) fileMenu = m;
|
||||||
|
else if ("Connection".equals(m.getText())) connectionMenu = m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertNotNull(viewMenu, "View menu must exist");
|
||||||
|
assertNotNull(actionsMenu, "Actions menu must exist");
|
||||||
|
assertNotNull(fileMenu, "File menu must exist");
|
||||||
|
assertNotNull(connectionMenu, "Connection menu must exist");
|
||||||
|
|
||||||
|
// Verify Connect... and Disconnect are in Connection menu and NOT in File menu
|
||||||
|
boolean connectInConnection = false;
|
||||||
|
boolean disconnectInConnection = false;
|
||||||
|
for (int i = 0; i < connectionMenu.getItemCount(); i++) {
|
||||||
|
JMenuItem item = connectionMenu.getItem(i);
|
||||||
|
if (item != null) {
|
||||||
|
if ("Connect...".equals(item.getText())) connectInConnection = true;
|
||||||
|
if ("Disconnect".equals(item.getText())) disconnectInConnection = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(connectInConnection, "Connect... must be in Connection menu");
|
||||||
|
assertTrue(disconnectInConnection, "Disconnect must be in Connection menu");
|
||||||
|
|
||||||
|
boolean connectInFile = false;
|
||||||
|
boolean disconnectInFile = false;
|
||||||
|
for (int i = 0; i < fileMenu.getItemCount(); i++) {
|
||||||
|
JMenuItem item = fileMenu.getItem(i);
|
||||||
|
if (item != null) {
|
||||||
|
if ("Connect...".equals(item.getText())) connectInFile = true;
|
||||||
|
if ("Disconnect".equals(item.getText())) disconnectInFile = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertFalse(connectInFile, "Connect... must NOT be in File menu");
|
||||||
|
assertFalse(disconnectInFile, "Disconnect must NOT be in File menu");
|
||||||
|
|
||||||
|
// Verify Screen History is in View menu
|
||||||
|
boolean screenHistoryInView = false;
|
||||||
|
for (int i = 0; i < viewMenu.getItemCount(); i++) {
|
||||||
|
JMenuItem item = viewMenu.getItem(i);
|
||||||
|
if (item != null && "Screen History...".equals(item.getText())) {
|
||||||
|
screenHistoryInView = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(screenHistoryInView, "Screen History... must be present in View menu");
|
||||||
|
|
||||||
|
// Verify Screen History is NOT duplicated in Actions menu
|
||||||
|
boolean screenHistoryInActions = false;
|
||||||
|
for (int i = 0; i < actionsMenu.getItemCount(); i++) {
|
||||||
|
JMenuItem item = actionsMenu.getItem(i);
|
||||||
|
if (item != null && "Screen History...".equals(item.getText())) {
|
||||||
|
screenHistoryInActions = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertFalse(screenHistoryInActions, "Screen History... must NOT be duplicated in Actions menu");
|
||||||
|
|
||||||
|
// Verify Code Page and Graphics Mode are in Connection menu, NOT View menu
|
||||||
|
JMenu codePageSubmenu = null;
|
||||||
|
JMenu graphicsModeSubmenu = null;
|
||||||
|
for (int i = 0; i < connectionMenu.getItemCount(); i++) {
|
||||||
|
JMenuItem item = connectionMenu.getItem(i);
|
||||||
|
if (item != null) {
|
||||||
|
if ("Code Page".equals(item.getText()) && item instanceof JMenu) {
|
||||||
|
codePageSubmenu = (JMenu) item;
|
||||||
|
}
|
||||||
|
if ("Graphics Mode".equals(item.getText()) && item instanceof JMenu) {
|
||||||
|
graphicsModeSubmenu = (JMenu) item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertNotNull(codePageSubmenu, "Connection > Code Page submenu must exist");
|
||||||
|
assertNotNull(graphicsModeSubmenu, "Connection > Graphics Mode submenu must exist");
|
||||||
|
assertEquals(CodePageRegistry.COMMON_CODE_PAGES.length, codePageSubmenu.getItemCount(),
|
||||||
|
"Connection > Code Page submenu must contain all " + CodePageRegistry.COMMON_CODE_PAGES.length + " common codepages");
|
||||||
|
assertEquals(GraphicsMode.values().length, graphicsModeSubmenu.getItemCount(),
|
||||||
|
"Connection > Graphics Mode submenu must contain all " + GraphicsMode.values().length + " graphics modes");
|
||||||
|
|
||||||
|
// Graphics Mode must be enabled when disconnected
|
||||||
|
assertFalse(app.isConnected(), "App should not be connected by default");
|
||||||
|
assertTrue(graphicsModeSubmenu.isEnabled(), "Graphics Mode submenu should be enabled when disconnected");
|
||||||
|
|
||||||
|
// Verify Code Page and Graphics Mode are NOT in View menu
|
||||||
|
for (int i = 0; i < viewMenu.getItemCount(); i++) {
|
||||||
|
JMenuItem item = viewMenu.getItem(i);
|
||||||
|
if (item != null) {
|
||||||
|
assertNotEquals("Code Page", item.getText(), "Code Page must NOT be in View menu");
|
||||||
|
assertNotEquals("Graphics Mode", item.getText(), "Graphics Mode must NOT be in View menu");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify Base 4-Color Override is in View menu
|
||||||
|
boolean base4InView = false;
|
||||||
|
for (int i = 0; i < viewMenu.getItemCount(); i++) {
|
||||||
|
JMenuItem item = viewMenu.getItem(i);
|
||||||
|
if (item != null && "Base 4-Color Override".equals(item.getText())) {
|
||||||
|
base4InView = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(base4InView, "Base 4-Color Override must be present in View menu");
|
||||||
|
|
||||||
|
// Verify Modes menu contains strictly the 4 interactive operator modes
|
||||||
|
JMenu modesMenu = null;
|
||||||
|
for (int i = 0; i < mb.getMenuCount(); i++) {
|
||||||
|
JMenu m = mb.getMenu(i);
|
||||||
|
if (m != null && "Modes".equals(m.getText())) {
|
||||||
|
modesMenu = m;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertNotNull(modesMenu, "Modes menu must exist");
|
||||||
|
assertEquals(4, modesMenu.getItemCount(), "Modes menu should contain exactly 4 interactive operator modes");
|
||||||
|
assertEquals("Document Mode (DOC)", modesMenu.getItem(0).getText());
|
||||||
|
assertEquals("Word Wrap Mode", modesMenu.getItem(1).getText());
|
||||||
|
assertEquals("APL Keyboard Mode", modesMenu.getItem(2).getText());
|
||||||
|
assertEquals("Insert Mode", modesMenu.getItem(3).getText());
|
||||||
|
} finally {
|
||||||
|
app.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package haus.nightmare.lib3270j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version information for lib3270j.
|
||||||
|
*/
|
||||||
|
public final class Version {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hardcoded fallback release version matching build.gradle and pom coordinates.
|
||||||
|
*/
|
||||||
|
public static final String VERSION = "1.2.4";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Canonical library name.
|
||||||
|
*/
|
||||||
|
public static final String NAME = "lib3270j";
|
||||||
|
|
||||||
|
private Version() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the runtime version string of lib3270j.
|
||||||
|
* Inspects the package implementation version first (populated when loaded from a JAR),
|
||||||
|
* and falls back to the compile-time {@link #VERSION} constant if null or blank.
|
||||||
|
*
|
||||||
|
* @return the version string (e.g. "1.2.4")
|
||||||
|
*/
|
||||||
|
public static String getVersion() {
|
||||||
|
Package pkg = Version.class.getPackage();
|
||||||
|
if (pkg != null) {
|
||||||
|
String implVer = pkg.getImplementationVersion();
|
||||||
|
if (implVer != null && !implVer.isBlank()) {
|
||||||
|
return implVer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return VERSION;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,58 @@ public class CodePageRegistry {
|
|||||||
private static final Map<String, CodePage> CODE_PAGES = new LinkedHashMap<>();
|
private static final Map<String, CodePage> CODE_PAGES = new LinkedHashMap<>();
|
||||||
private static final Map<String, String> ALIASES = new HashMap<>();
|
private static final Map<String, String> ALIASES = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard human-readable codepage option labels for UI dropdowns and menus.
|
||||||
|
* Covers core SBCS, all 10 Euro variants (1140-1149), Extended SBCS (Arabic,
|
||||||
|
* Hebrew, Thai, Cyrillic, Turkish), and Mixed/Extended DBCS.
|
||||||
|
*/
|
||||||
|
public static final String[] COMMON_CODE_PAGES = {
|
||||||
|
"037 - US / Canada / Brazil",
|
||||||
|
"1047 - IBM Open Systems / z/OS Unix",
|
||||||
|
"500 - International Latin-1",
|
||||||
|
"273 - Germany / Austria",
|
||||||
|
"277 - Denmark / Norway",
|
||||||
|
"278 - Sweden / Finland",
|
||||||
|
"280 - Italy",
|
||||||
|
"284 - Spain / Latin America",
|
||||||
|
"285 - United Kingdom",
|
||||||
|
"297 - France",
|
||||||
|
"870 - Eastern Europe / Latin-2",
|
||||||
|
"871 - Iceland",
|
||||||
|
"875 - Greece (Greek)",
|
||||||
|
"1026 - Turkey (Turkish Latin-5)",
|
||||||
|
"1155 - Turkey (Turkish Latin-5 Euro \u20AC)",
|
||||||
|
"905 - Turkey (Turkish Latin-3)",
|
||||||
|
"420 - Arabic Bilingual",
|
||||||
|
"424 - Hebrew (with Latin)",
|
||||||
|
"803 - Hebrew Character Set",
|
||||||
|
"838 - Thai Extended",
|
||||||
|
"1160 - Thai Euro \u20AC",
|
||||||
|
"1025 - Cyrillic Multilingual",
|
||||||
|
"1123 - Cyrillic Ukraine",
|
||||||
|
"1154 - Cyrillic Euro \u20AC",
|
||||||
|
"880 - Cyrillic (Russian)",
|
||||||
|
"1140 - US / Canada (Euro \u20AC)",
|
||||||
|
"1141 - Germany / Austria (Euro \u20AC)",
|
||||||
|
"1142 - Denmark / Norway (Euro \u20AC)",
|
||||||
|
"1143 - Sweden / Finland (Euro \u20AC)",
|
||||||
|
"1144 - Italy (Euro \u20AC)",
|
||||||
|
"1145 - Spain / Latin America (Euro \u20AC)",
|
||||||
|
"1146 - United Kingdom (Euro \u20AC)",
|
||||||
|
"1147 - France (Euro \u20AC)",
|
||||||
|
"1148 - International (Euro \u20AC)",
|
||||||
|
"1149 - Iceland (Euro \u20AC)",
|
||||||
|
"290 - Japanese Katakana SBCS",
|
||||||
|
"1027 - Japanese Latin SBCS",
|
||||||
|
"930 - Japanese Katakana Mixed DBCS",
|
||||||
|
"939 - Japanese Latin Mixed DBCS",
|
||||||
|
"935 - Simplified Chinese Mixed DBCS",
|
||||||
|
"937 - Traditional Chinese Mixed DBCS",
|
||||||
|
"1388 - Simplified Chinese Extended DBCS",
|
||||||
|
"1371 - Traditional Chinese Extended DBCS",
|
||||||
|
"933 - Korean Mixed DBCS"
|
||||||
|
};
|
||||||
|
|
||||||
static {
|
static {
|
||||||
// Register core SBCS codepages
|
// Register core SBCS codepages
|
||||||
register(new Cp037());
|
register(new Cp037());
|
||||||
|
|||||||
Reference in New Issue
Block a user