Compare commits
22 Commits
3526e682a1
...
v1.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
db6043cb39
|
|||
|
161364a95f
|
|||
|
a56aa5f059
|
|||
|
7b5bc7ec11
|
|||
|
a9956298e2
|
|||
|
a43969da08
|
|||
|
e18d2f436f
|
|||
|
27976dd31f
|
|||
|
a3c4b95379
|
|||
|
3a576b79b4
|
|||
|
40ebd40fe2
|
|||
|
f310d39fc1
|
|||
|
a44ea00d3a
|
|||
|
037ad1f941
|
|||
|
79258c0c44
|
|||
|
ff5dfda343
|
|||
|
8654b72d8c
|
|||
|
2e9b6d325a
|
|||
|
540a8dfd1d
|
|||
|
ebc0b5ae6f
|
|||
|
bcfd4ba2e0
|
|||
|
2584f4289f
|
@@ -1,13 +0,0 @@
|
|||||||
# Fixed Bugs
|
|
||||||
- Complete failure when using `cp term conmode 3270` under VM:
|
|
||||||
Fixed in InputProcessor by sending raw line-mode EBCDIC character data in SSCP-LU mode instead of 3270 AID headers and 1920-byte buffer dumps, and correctly processing subsequent 3270 stream transitions upon CONMODE 3270.
|
|
||||||
|
|
||||||
- Unable to start a 2nd transfer after first completed ("A transfer is already in progress"):
|
|
||||||
Fixed in FTDft by signaling completion upon handling `TR_CLOSE_REQ` / host completion messages, and adding state reset in FileTransfer.
|
|
||||||
|
|
||||||
- IND$FILE CMS and TSO:
|
|
||||||
Fixed command formatting options handling (empty parenthesis removal for CMS binary/default modes and option spacing) and added Query Reply filtering for DFT/DDM mode.
|
|
||||||
|
|
||||||
- Local keyboard input and cursor updates not rendering in terminal:
|
|
||||||
Fixed in ScreenBuffer, InputProcessor, and TerminalPanel by ensuring display snapshot and cursor address are updated synchronously on user input operations (typing, backspace, delete, cursor movement, erase) so the presentation layer immediately renders user keystrokes.
|
|
||||||
|
|
||||||
@@ -10,8 +10,10 @@ allprojects {
|
|||||||
subprojects {
|
subprojects {
|
||||||
apply plugin: 'java'
|
apply plugin: 'java'
|
||||||
|
|
||||||
|
java {
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
|
|||||||
+1
-4
@@ -118,10 +118,7 @@ public class TestRunner {
|
|||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request()
|
LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request()
|
||||||
.selectors(
|
.selectors(
|
||||||
selectPackage("haus.nightmare.lib3270j.graphics"),
|
selectPackage("haus.nightmare")
|
||||||
selectPackage("haus.nightmare.lib3270j.datastream"),
|
|
||||||
selectPackage("haus.nightmare.lib3270j.screen"),
|
|
||||||
selectPackage("haus.nightmare.lib3270j.protocol")
|
|
||||||
)
|
)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
package haus.nightmare.j3270;
|
package haus.nightmare.j3270;
|
||||||
|
|
||||||
import haus.nightmare.lib3270j.*;
|
import haus.nightmare.lib3270j.*;
|
||||||
|
import haus.nightmare.lib3270j.ecl.ECLConstants;
|
||||||
|
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.j3270.ui.ConnectDialog;
|
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||||
import haus.nightmare.j3270.ui.StatusBar;
|
import haus.nightmare.j3270.ui.*;
|
||||||
import haus.nightmare.j3270.ui.TerminalPanel;
|
|
||||||
import haus.nightmare.j3270.ft.FileTransfer;
|
import haus.nightmare.j3270.ft.FileTransfer;
|
||||||
import haus.nightmare.j3270.ft.FileTransferDialog;
|
import haus.nightmare.j3270.ft.FileTransferDialog;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
import java.awt.datatransfer.DataFlavor;
|
||||||
import java.awt.event.*;
|
import java.awt.event.*;
|
||||||
|
import java.awt.print.PrinterJob;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.logging.*;
|
import java.util.logging.*;
|
||||||
|
|
||||||
@@ -36,14 +39,22 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
private FileTransfer fileTransfer;
|
private FileTransfer fileTransfer;
|
||||||
private final java.util.concurrent.atomic.AtomicBoolean screenUpdatePending = new java.util.concurrent.atomic.AtomicBoolean(false);
|
private final java.util.concurrent.atomic.AtomicBoolean screenUpdatePending = new java.util.concurrent.atomic.AtomicBoolean(false);
|
||||||
|
|
||||||
|
// Dialog instances
|
||||||
|
private FindDialog findDialog;
|
||||||
|
private ScriptDialog scriptDialog;
|
||||||
|
private FieldInspectorDialog fieldInspectorDialog;
|
||||||
|
private PrinterSessionDialog printerSessionDialog;
|
||||||
|
|
||||||
public J3270App() {
|
public J3270App() {
|
||||||
super("j3270 — Java TN3270 Terminal Emulator");
|
super("j3270 — Java TN3270 Terminal Emulator");
|
||||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||||
setBackground(new Color(10, 10, 10));
|
setBackground(Color.BLACK);
|
||||||
|
|
||||||
buildUI();
|
buildUI();
|
||||||
buildMenuBar();
|
buildMenuBar();
|
||||||
|
|
||||||
|
ThemeManager.addThemeChangeListener(this::onThemeChanged);
|
||||||
|
|
||||||
pack();
|
pack();
|
||||||
setLocationRelativeTo(null);
|
setLocationRelativeTo(null);
|
||||||
setMinimumSize(new Dimension(640, 400));
|
setMinimumSize(new Dimension(640, 400));
|
||||||
@@ -52,7 +63,6 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
refreshTimer = new Timer(100, e -> {
|
refreshTimer = new Timer(100, e -> {
|
||||||
if (client != null) {
|
if (client != null) {
|
||||||
statusBar.updateStatus();
|
statusBar.updateStatus();
|
||||||
// Keep focus on terminal panel when window is active
|
|
||||||
if (isActive() && !terminalPanel.hasFocus()) {
|
if (isActive() && !terminalPanel.hasFocus()) {
|
||||||
terminalPanel.requestFocusInWindow();
|
terminalPanel.requestFocusInWindow();
|
||||||
}
|
}
|
||||||
@@ -66,12 +76,14 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
disconnect();
|
disconnect();
|
||||||
if (refreshTimer != null)
|
if (refreshTimer != null)
|
||||||
refreshTimer.stop();
|
refreshTimer.stop();
|
||||||
|
if (printerSessionDialog != null) {
|
||||||
|
printerSessionDialog.dispose();
|
||||||
|
}
|
||||||
terminalPanel.dispose();
|
terminalPanel.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void windowActivated(WindowEvent e) {
|
public void windowActivated(WindowEvent e) {
|
||||||
// When window gains focus, push to terminal panel
|
|
||||||
terminalPanel.requestFocusInWindow();
|
terminalPanel.requestFocusInWindow();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -79,27 +91,36 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
|
|
||||||
private void buildUI() {
|
private void buildUI() {
|
||||||
terminalPanel = new TerminalPanel();
|
terminalPanel = new TerminalPanel();
|
||||||
|
|
||||||
// Status bar
|
|
||||||
statusBar = new StatusBar();
|
statusBar = new StatusBar();
|
||||||
|
|
||||||
// Layout
|
|
||||||
getContentPane().setLayout(new BorderLayout());
|
getContentPane().setLayout(new BorderLayout());
|
||||||
getContentPane().setBackground(new Color(10, 10, 10));
|
getContentPane().setBackground(Color.BLACK);
|
||||||
getContentPane().add(terminalPanel, BorderLayout.CENTER);
|
getContentPane().add(terminalPanel, BorderLayout.CENTER);
|
||||||
getContentPane().add(statusBar, BorderLayout.SOUTH);
|
getContentPane().add(statusBar, BorderLayout.SOUTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onThemeChanged(UITheme theme) {
|
||||||
|
buildMenuBar();
|
||||||
|
statusBar.applyTheme(theme);
|
||||||
|
ThemeManager.applyThemeToWindow(this);
|
||||||
|
terminalPanel.repaint();
|
||||||
|
}
|
||||||
|
|
||||||
private void buildMenuBar() {
|
private void buildMenuBar() {
|
||||||
JMenuBar menuBar = new JMenuBar();
|
JMenuBar menuBar = new JMenuBar();
|
||||||
menuBar.setBackground(new Color(30, 30, 30));
|
ThemeManager.styleMenuBar(menuBar);
|
||||||
menuBar.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, new Color(50, 50, 50)));
|
|
||||||
|
|
||||||
// File menu
|
int shortcutMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
|
||||||
|
|
||||||
|
// 1. File menu
|
||||||
JMenu fileMenu = createMenu("File");
|
JMenu fileMenu = createMenu("File");
|
||||||
fileMenu.add(createMenuItem("Connect...", KeyEvent.VK_N, this::showConnectDialog));
|
fileMenu.add(createMenuItem("Connect...", KeyEvent.VK_N, this::showConnectDialog));
|
||||||
fileMenu.add(createMenuItem("Disconnect", KeyEvent.VK_D, this::disconnect));
|
fileMenu.add(createMenuItem("Disconnect", KeyEvent.VK_D, this::disconnect));
|
||||||
fileMenu.addSeparator();
|
fileMenu.addSeparator();
|
||||||
|
fileMenu.add(createMenuItem("Save Screen As...", -1, () -> ScreenExporter.showExportDialog(this, client, terminalPanel)));
|
||||||
|
fileMenu.add(createMenuItem("3287 Printer Session...", -1, this::showPrinterSessionDialog));
|
||||||
|
fileMenu.add(createMenuItem("Print Screen...", KeyEvent.VK_P, this::printScreen));
|
||||||
|
fileMenu.addSeparator();
|
||||||
fileMenu.add(createMenuItem("Settings...", -1, this::showSettingsDialog));
|
fileMenu.add(createMenuItem("Settings...", -1, this::showSettingsDialog));
|
||||||
fileMenu.addSeparator();
|
fileMenu.addSeparator();
|
||||||
fileMenu.add(createMenuItem("Quit", KeyEvent.VK_Q, () -> {
|
fileMenu.add(createMenuItem("Quit", KeyEvent.VK_Q, () -> {
|
||||||
@@ -108,39 +129,157 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
}));
|
}));
|
||||||
menuBar.add(fileMenu);
|
menuBar.add(fileMenu);
|
||||||
|
|
||||||
// View menu
|
// 2. Edit menu
|
||||||
|
JMenu editMenu = createMenu("Edit");
|
||||||
|
editMenu.add(createMenuItem("Copy", KeyEvent.VK_C, () -> terminalPanel.copySelection()));
|
||||||
|
editMenu.add(createMenuItem("Paste", KeyEvent.VK_V, () -> terminalPanel.pasteClipboard()));
|
||||||
|
editMenu.add(createMenuItem("Paste with Line Wrap...", -1, this::showPasteLineWrapDialog));
|
||||||
|
editMenu.addSeparator();
|
||||||
|
editMenu.add(createMenuItem("Select All", KeyEvent.VK_A, () -> terminalPanel.selectAll()));
|
||||||
|
editMenu.add(createMenuItem("Clear Selection", -1, () -> terminalPanel.clearSelection()));
|
||||||
|
editMenu.addSeparator();
|
||||||
|
editMenu.add(createMenuItem("Find on Screen...", KeyEvent.VK_F, this::showFindDialog));
|
||||||
|
editMenu.add(createMenuItem("Find Next", KeyEvent.VK_G, this::findNext));
|
||||||
|
editMenu.add(createMenuItem("Find Previous", KeyEvent.VK_G, this::findPrevious, true));
|
||||||
|
menuBar.add(editMenu);
|
||||||
|
|
||||||
|
// 3. View menu
|
||||||
JMenu viewMenu = createMenu("View");
|
JMenu viewMenu = createMenu("View");
|
||||||
viewMenu.add(createMenuItem("Font Size +", KeyEvent.VK_EQUALS, () -> adjustFontSize(2)));
|
viewMenu.add(createMenuItem("Font Size +", KeyEvent.VK_EQUALS, () -> adjustFontSize(2), true));
|
||||||
viewMenu.add(createMenuItem("Font Size -", KeyEvent.VK_MINUS, () -> adjustFontSize(-2)));
|
viewMenu.add(createMenuItem("Font Size -", KeyEvent.VK_MINUS, () -> adjustFontSize(-2), true));
|
||||||
viewMenu.add(createMenuItem("Reset Font", KeyEvent.VK_0, () -> {
|
viewMenu.add(createMenuItem("Reset Font", KeyEvent.VK_0, () -> {
|
||||||
terminalPanel.setFontSize(16);
|
terminalPanel.setFontSize(16);
|
||||||
terminalPanel.guardedPack();
|
terminalPanel.guardedPack();
|
||||||
}));
|
}, true));
|
||||||
|
viewMenu.addSeparator();
|
||||||
|
|
||||||
|
// UI Theme Submenu
|
||||||
|
JMenu themeMenu = createMenu("UI Theme");
|
||||||
|
ButtonGroup themeGroup = new ButtonGroup();
|
||||||
|
|
||||||
|
JRadioButtonMenuItem darkThemeItem = new JRadioButtonMenuItem("Dark Mode", ThemeManager.isDark());
|
||||||
|
ThemeManager.styleMenuItem(darkThemeItem);
|
||||||
|
darkThemeItem.addActionListener(e -> {
|
||||||
|
haus.nightmare.j3270.config.Settings.setJavaUiTheme(UITheme.DARK);
|
||||||
|
ThemeManager.setTheme(UITheme.DARK);
|
||||||
|
});
|
||||||
|
themeGroup.add(darkThemeItem);
|
||||||
|
themeMenu.add(darkThemeItem);
|
||||||
|
|
||||||
|
JRadioButtonMenuItem lightThemeItem = new JRadioButtonMenuItem("Light Mode", ThemeManager.isLight());
|
||||||
|
ThemeManager.styleMenuItem(lightThemeItem);
|
||||||
|
lightThemeItem.addActionListener(e -> {
|
||||||
|
haus.nightmare.j3270.config.Settings.setJavaUiTheme(UITheme.LIGHT);
|
||||||
|
ThemeManager.setTheme(UITheme.LIGHT);
|
||||||
|
});
|
||||||
|
themeGroup.add(lightThemeItem);
|
||||||
|
themeMenu.add(lightThemeItem);
|
||||||
|
|
||||||
|
viewMenu.add(themeMenu);
|
||||||
|
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("Field Inspector...", -1, this::showFieldInspectorDialog));
|
||||||
menuBar.add(viewMenu);
|
menuBar.add(viewMenu);
|
||||||
|
|
||||||
// Actions menu
|
// 4. Actions menu
|
||||||
JMenu actionsMenu = createMenu("Actions");
|
JMenu actionsMenu = createMenu("Actions");
|
||||||
|
actionsMenu.add(createMenuItem("Send Enter", KeyEvent.VK_ENTER, () -> {
|
||||||
|
if (client != null && client.getConnectionState().isFullSession()) {
|
||||||
|
client.sendEnter();
|
||||||
|
terminalPanel.repaint();
|
||||||
|
}
|
||||||
|
}, true));
|
||||||
actionsMenu.add(createMenuItem("Clear", KeyEvent.VK_K, () -> {
|
actionsMenu.add(createMenuItem("Clear", KeyEvent.VK_K, () -> {
|
||||||
if (client != null)
|
if (client != null)
|
||||||
client.sendClear();
|
client.sendClear();
|
||||||
terminalPanel.repaint();
|
terminalPanel.repaint();
|
||||||
}));
|
}, true));
|
||||||
actionsMenu.add(createMenuItem("Reset", KeyEvent.VK_R, () -> {
|
actionsMenu.add(createMenuItem("Reset", KeyEvent.VK_R, () -> {
|
||||||
if (client != null)
|
if (client != null)
|
||||||
client.reset();
|
client.reset();
|
||||||
terminalPanel.repaint();
|
terminalPanel.repaint();
|
||||||
}));
|
}, true));
|
||||||
|
actionsMenu.add(createMenuItem("Erase Input", KeyEvent.VK_E, () -> {
|
||||||
|
if (client != null && client.getConnectionState().isFullSession()) {
|
||||||
|
client.eraseInput();
|
||||||
|
terminalPanel.repaint();
|
||||||
|
}
|
||||||
|
}, true));
|
||||||
|
actionsMenu.add(createMenuItem("Attention", KeyEvent.VK_A, () -> {
|
||||||
|
if (client != null && client.isConnected()) client.attn();
|
||||||
|
}, true));
|
||||||
|
actionsMenu.add(createMenuItem("System Request", KeyEvent.VK_S, () -> {
|
||||||
|
if (client != null && client.isConnected()) {
|
||||||
|
client.sysReq();
|
||||||
|
terminalPanel.repaint();
|
||||||
|
}
|
||||||
|
}, true));
|
||||||
|
actionsMenu.add(createMenuItem("Cursor Select", KeyEvent.VK_Q, () -> {
|
||||||
|
if (client != null && client.getConnectionState().isFullSession()) {
|
||||||
|
client.cursorSelect();
|
||||||
|
terminalPanel.repaint();
|
||||||
|
}
|
||||||
|
}, true));
|
||||||
actionsMenu.addSeparator();
|
actionsMenu.addSeparator();
|
||||||
actionsMenu.add(createMenuItem("Toggle Light Pen (Alt+L)", KeyEvent.VK_L, () -> {
|
actionsMenu.add(createMenuItem("Toggle Light Pen (Alt+L)", KeyEvent.VK_L, () -> {
|
||||||
terminalPanel.toggleLightPen();
|
terminalPanel.toggleLightPen();
|
||||||
}, true));
|
}, true));
|
||||||
actionsMenu.addSeparator();
|
actionsMenu.addSeparator();
|
||||||
actionsMenu.add(createMenuItem("File Transfer...", KeyEvent.VK_T, this::showFileTransferDialog));
|
actionsMenu.add(createMenuItem("Run Keystrokes / Script...", -1, this::showScriptDialog));
|
||||||
|
actionsMenu.add(createMenuItem("File Transfer...", KeyEvent.VK_T, this::showFileTransferDialog, true));
|
||||||
menuBar.add(actionsMenu);
|
menuBar.add(actionsMenu);
|
||||||
|
|
||||||
// Help menu
|
// 5. 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.addSeparator();
|
||||||
helpMenu.add(createMenuItem("About", -1, this::showAbout));
|
helpMenu.add(createMenuItem("About", -1, this::showAbout));
|
||||||
menuBar.add(helpMenu);
|
menuBar.add(helpMenu);
|
||||||
|
|
||||||
@@ -149,14 +288,12 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
|
|
||||||
private JMenu createMenu(String name) {
|
private JMenu createMenu(String name) {
|
||||||
JMenu menu = new JMenu(name);
|
JMenu menu = new JMenu(name);
|
||||||
menu.setForeground(new Color(200, 200, 200));
|
return ThemeManager.styleMenu(menu);
|
||||||
return menu;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private JMenuItem createMenuItem(String name, int mnemonic, Runnable action, boolean useAlt) {
|
private JMenuItem createMenuItem(String name, int mnemonic, Runnable action, boolean useAlt) {
|
||||||
JMenuItem item = new JMenuItem(name);
|
JMenuItem item = new JMenuItem(name);
|
||||||
item.setBackground(new Color(40, 40, 40));
|
ThemeManager.styleMenuItem(item);
|
||||||
item.setForeground(new Color(200, 200, 200));
|
|
||||||
if (mnemonic > 0) {
|
if (mnemonic > 0) {
|
||||||
int modifier = useAlt ? KeyEvent.ALT_DOWN_MASK : Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
|
int modifier = useAlt ? KeyEvent.ALT_DOWN_MASK : Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
|
||||||
item.setAccelerator(KeyStroke.getKeyStroke(mnemonic, modifier));
|
item.setAccelerator(KeyStroke.getKeyStroke(mnemonic, modifier));
|
||||||
@@ -169,6 +306,164 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
return createMenuItem(name, mnemonic, action, false);
|
return createMenuItem(name, mnemonic, action, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== Dialog Openers ==========
|
||||||
|
|
||||||
|
private void showFindDialog() {
|
||||||
|
if (client == null) {
|
||||||
|
JOptionPane.showMessageDialog(this, "Connect to a host before using Find.", "Not Connected", JOptionPane.WARNING_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (findDialog == null) {
|
||||||
|
findDialog = new FindDialog(this, client, terminalPanel);
|
||||||
|
}
|
||||||
|
findDialog.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void findNext() {
|
||||||
|
if (client == null) return;
|
||||||
|
String last = FindDialog.getLastSearchText();
|
||||||
|
if (last == null || last.isEmpty()) {
|
||||||
|
showFindDialog();
|
||||||
|
} else {
|
||||||
|
ScreenBuffer sb = client.getScreenBuffer();
|
||||||
|
int curAddr = sb.getCursorAddress();
|
||||||
|
int cols = sb.getDisplayCols();
|
||||||
|
int curRow = curAddr / cols;
|
||||||
|
int curCol = (curAddr % cols + 1) % cols;
|
||||||
|
if (curCol == 0) curRow = (curRow + 1) % sb.getDisplayRows();
|
||||||
|
|
||||||
|
int found = client.getPS().searchString(last, curRow, curCol, ECLConstants.SEARCH_FORWARD, !FindDialog.getLastMatchCase());
|
||||||
|
if (found >= 0) {
|
||||||
|
sb.setCursorAddress(found);
|
||||||
|
terminalPanel.setSearchHighlight(found, last.length());
|
||||||
|
terminalPanel.repaint();
|
||||||
|
} else {
|
||||||
|
Toolkit.getDefaultToolkit().beep();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void findPrevious() {
|
||||||
|
if (client == null) return;
|
||||||
|
String last = FindDialog.getLastSearchText();
|
||||||
|
if (last == null || last.isEmpty()) {
|
||||||
|
showFindDialog();
|
||||||
|
} else {
|
||||||
|
ScreenBuffer sb = client.getScreenBuffer();
|
||||||
|
int curAddr = sb.getCursorAddress();
|
||||||
|
int cols = sb.getDisplayCols();
|
||||||
|
int curRow = curAddr / cols;
|
||||||
|
int curCol = (curAddr % cols - 1 + cols) % cols;
|
||||||
|
if (curCol == cols - 1) curRow = (curRow - 1 + sb.getDisplayRows()) % sb.getDisplayRows();
|
||||||
|
|
||||||
|
int found = client.getPS().searchString(last, curRow, curCol, ECLConstants.SEARCH_BACKWARD, !FindDialog.getLastMatchCase());
|
||||||
|
if (found >= 0) {
|
||||||
|
sb.setCursorAddress(found);
|
||||||
|
terminalPanel.setSearchHighlight(found, last.length());
|
||||||
|
terminalPanel.repaint();
|
||||||
|
} else {
|
||||||
|
Toolkit.getDefaultToolkit().beep();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showScriptDialog() {
|
||||||
|
if (client == null) {
|
||||||
|
JOptionPane.showMessageDialog(this, "Connect to a host before running scripts.", "Not Connected", JOptionPane.WARNING_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (scriptDialog == null) {
|
||||||
|
scriptDialog = new ScriptDialog(this, client, terminalPanel);
|
||||||
|
}
|
||||||
|
scriptDialog.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showFieldInspectorDialog() {
|
||||||
|
if (client == null) {
|
||||||
|
JOptionPane.showMessageDialog(this, "Connect to a host before inspecting fields.", "Not Connected", JOptionPane.WARNING_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fieldInspectorDialog == null) {
|
||||||
|
fieldInspectorDialog = new FieldInspectorDialog(this, client, terminalPanel);
|
||||||
|
} else {
|
||||||
|
fieldInspectorDialog.refreshFields();
|
||||||
|
}
|
||||||
|
fieldInspectorDialog.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showPrinterSessionDialog() {
|
||||||
|
if (printerSessionDialog == null) {
|
||||||
|
String host = (client != null) ? client.getConfig().getHost() : lastHost;
|
||||||
|
int port = (client != null) ? client.getConfig().getPort() : lastPort;
|
||||||
|
boolean tls = (client != null) ? client.getConfig().isUseTls() : false;
|
||||||
|
printerSessionDialog = new PrinterSessionDialog(this, host, port, tls);
|
||||||
|
}
|
||||||
|
printerSessionDialog.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showPasteLineWrapDialog() {
|
||||||
|
if (client == null || !client.getConnectionState().isFullSession()) {
|
||||||
|
JOptionPane.showMessageDialog(this, "Connect to a 3270 session before pasting.", "Not Connected", JOptionPane.WARNING_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String text = (String) Toolkit.getDefaultToolkit().getSystemClipboard().getData(DataFlavor.stringFlavor);
|
||||||
|
if (text == null || text.isEmpty()) {
|
||||||
|
JOptionPane.showMessageDialog(this, "Clipboard is empty.", "Clipboard", JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int cols = client.getScreenBuffer().getDisplayCols();
|
||||||
|
String endColStr = JOptionPane.showInputDialog(this, "Enter right margin column (1-" + cols + ") for wrapping [0 = no right margin]:", "80");
|
||||||
|
if (endColStr != null) {
|
||||||
|
int endCol = 0;
|
||||||
|
try {
|
||||||
|
endCol = Integer.parseInt(endColStr.trim());
|
||||||
|
} catch (NumberFormatException ignored) {}
|
||||||
|
terminalPanel.pasteLineWrap(text, endCol, true);
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
JOptionPane.showMessageDialog(this, "Could not read clipboard: " + ex.getMessage(), "Paste Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void changeCodePage(String cpId) {
|
||||||
|
if (client != null) {
|
||||||
|
client.setCodePage(cpId);
|
||||||
|
client.getScreenBuffer().translateToUnicode();
|
||||||
|
terminalPanel.refreshScreen();
|
||||||
|
statusBar.updateStatus();
|
||||||
|
}
|
||||||
|
haus.nightmare.j3270.config.Settings.setCodePage(cpId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void changeGraphicsMode(GraphicsMode mode) {
|
||||||
|
if (client != null && client.getDataStreamProcessor() != null) {
|
||||||
|
client.getDataStreamProcessor().getQueryReplyBuilder().setGraphicsMode(mode);
|
||||||
|
terminalPanel.repaint();
|
||||||
|
}
|
||||||
|
haus.nightmare.j3270.config.Settings.setGraphicsMode(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void printScreen() {
|
||||||
|
if (client == null) {
|
||||||
|
JOptionPane.showMessageDialog(this, "Connect to a host before printing screen.", "Not Connected", JOptionPane.WARNING_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PrinterJob job = PrinterJob.getPrinterJob();
|
||||||
|
job.setJobName("j3270 Screen Capture");
|
||||||
|
job.setPrintable(terminalPanel);
|
||||||
|
if (job.printDialog()) {
|
||||||
|
try {
|
||||||
|
job.print();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
JOptionPane.showMessageDialog(this, "Print error: " + ex.getMessage(), "Print Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void showFileTransferDialog() {
|
private void showFileTransferDialog() {
|
||||||
if (client == null) {
|
if (client == null) {
|
||||||
JOptionPane.showMessageDialog(this, "Connect to a host before using File Transfer.",
|
JOptionPane.showMessageDialog(this, "Connect to a host before using File Transfer.",
|
||||||
@@ -187,7 +482,6 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
monitor.setMillisToDecideToPopup(0);
|
monitor.setMillisToDecideToPopup(0);
|
||||||
monitor.setMillisToPopup(0);
|
monitor.setMillisToPopup(0);
|
||||||
|
|
||||||
// Start a timer to check for manual cancellation
|
|
||||||
Timer cancelCheckTimer = new Timer(500, e -> {
|
Timer cancelCheckTimer = new Timer(500, e -> {
|
||||||
if (monitor != null && monitor.isCanceled() && fileTransfer != null) {
|
if (monitor != null && monitor.isCanceled() && fileTransfer != null) {
|
||||||
fileTransfer.cancel();
|
fileTransfer.cancel();
|
||||||
@@ -208,7 +502,6 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
public void onBytesTransferred(long bytes) {
|
public void onBytesTransferred(long bytes) {
|
||||||
if (monitor != null) {
|
if (monitor != null) {
|
||||||
monitor.setNote(bytes + " bytes transferred");
|
monitor.setNote(bytes + " bytes transferred");
|
||||||
// We don't always know max size in IND$FILE, so we just pulse/update note
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,6 +535,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
dialog.setInitialPort(initialPort);
|
dialog.setInitialPort(initialPort);
|
||||||
dialog.setInitialTls(haus.nightmare.j3270.config.Settings.getAutoConnectTls());
|
dialog.setInitialTls(haus.nightmare.j3270.config.Settings.getAutoConnectTls());
|
||||||
dialog.setInitialVerifyCert(haus.nightmare.j3270.config.Settings.getAutoConnectVerifyCert());
|
dialog.setInitialVerifyCert(haus.nightmare.j3270.config.Settings.getAutoConnectVerifyCert());
|
||||||
|
dialog.setInitialCodePage(haus.nightmare.j3270.config.Settings.getCodePage());
|
||||||
dialog.setVisible(true);
|
dialog.setVisible(true);
|
||||||
|
|
||||||
if (dialog.isConfirmed()) {
|
if (dialog.isConfirmed()) {
|
||||||
@@ -265,16 +559,14 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
lastHost = config.getHost();
|
lastHost = config.getHost();
|
||||||
lastPort = config.getPort();
|
lastPort = config.getPort();
|
||||||
|
|
||||||
// Save for auto-connect
|
|
||||||
haus.nightmare.j3270.config.Settings.setAutoConnectHost(config.getHost());
|
haus.nightmare.j3270.config.Settings.setAutoConnectHost(config.getHost());
|
||||||
haus.nightmare.j3270.config.Settings.setAutoConnectPort(config.getPort());
|
haus.nightmare.j3270.config.Settings.setAutoConnectPort(config.getPort());
|
||||||
haus.nightmare.j3270.config.Settings.setAutoConnectTls(config.isUseTls());
|
haus.nightmare.j3270.config.Settings.setAutoConnectTls(config.isUseTls());
|
||||||
haus.nightmare.j3270.config.Settings.setAutoConnectVerifyCert(config.isTlsVerifyCert());
|
haus.nightmare.j3270.config.Settings.setAutoConnectVerifyCert(config.isTlsVerifyCert());
|
||||||
|
haus.nightmare.j3270.config.Settings.setCodePage(config.getCodePage());
|
||||||
|
|
||||||
// Disconnect existing connection
|
|
||||||
disconnect();
|
disconnect();
|
||||||
|
|
||||||
// Wire interactive certificate verifier if none configured
|
|
||||||
if (config.getCertificateVerifier() == null) {
|
if (config.getCertificateVerifier() == null) {
|
||||||
config.setCertificateVerifier((chain, authType, exception) ->
|
config.setCertificateVerifier((chain, authType, exception) ->
|
||||||
haus.nightmare.j3270.ui.UntrustedCertificateDialog.showPrompt(this, config.getHost(), config.getPort(), chain, exception)
|
haus.nightmare.j3270.ui.UntrustedCertificateDialog.showPrompt(this, config.getHost(), config.getPort(), chain, exception)
|
||||||
@@ -291,7 +583,6 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
String tlsIndicator = config.isUseTls() ? (config.isTlsVerifyCert() ? " [TLS]" : " [TLS/Unverified]") : "";
|
String tlsIndicator = config.isUseTls() ? (config.isTlsVerifyCert() ? " [TLS]" : " [TLS/Unverified]") : "";
|
||||||
setTitle("j3270 — " + config.getHost() + ":" + config.getPort() + tlsIndicator);
|
setTitle("j3270 — " + config.getHost() + ":" + config.getPort() + tlsIndicator);
|
||||||
|
|
||||||
// Resize window to match model
|
|
||||||
terminalPanel.guardedPack();
|
terminalPanel.guardedPack();
|
||||||
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
@@ -307,7 +598,6 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
}
|
}
|
||||||
}, "Connect-Thread").start();
|
}, "Connect-Thread").start();
|
||||||
|
|
||||||
// Focus the terminal panel
|
|
||||||
terminalPanel.requestFocusInWindow();
|
terminalPanel.requestFocusInWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,8 +620,6 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
int current = terminalPanel.getFontSize();
|
int current = terminalPanel.getFontSize();
|
||||||
int newSize = Math.max(8, Math.min(72, current + delta));
|
int newSize = Math.max(8, Math.min(72, current + delta));
|
||||||
terminalPanel.setFontSize(newSize);
|
terminalPanel.setFontSize(newSize);
|
||||||
// Pack after font change — setFontSize sets the resize guard
|
|
||||||
// to prevent the componentResized from re-triggering autoFitFont
|
|
||||||
terminalPanel.guardedPack();
|
terminalPanel.guardedPack();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,7 +632,6 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
statusBar.updateStatus();
|
statusBar.updateStatus();
|
||||||
terminalPanel.repaint();
|
terminalPanel.repaint();
|
||||||
|
|
||||||
// Auto-resize and focus on first full session
|
|
||||||
if (newState.isFullSession() && !oldState.isFullSession()) {
|
if (newState.isFullSession() && !oldState.isFullSession()) {
|
||||||
terminalPanel.guardedPack();
|
terminalPanel.guardedPack();
|
||||||
terminalPanel.requestFocusInWindow();
|
terminalPanel.requestFocusInWindow();
|
||||||
@@ -365,6 +652,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
if (deviceName != null) {
|
if (deviceName != null) {
|
||||||
setTitle("j3270 — " + lastHost + ":" + lastPort + " [" + deviceName + "]");
|
setTitle("j3270 — " + lastHost + ":" + lastPort + " [" + deviceName + "]");
|
||||||
}
|
}
|
||||||
|
statusBar.updateStatus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,10 +663,6 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
if (screenUpdatePending.compareAndSet(false, true)) {
|
if (screenUpdatePending.compareAndSet(false, true)) {
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
screenUpdatePending.set(false);
|
screenUpdatePending.set(false);
|
||||||
// During an active file transfer, let the CUT/DFT handler drive
|
|
||||||
// keyboard state. In x3270, ft_cut_data() runs before WCC
|
|
||||||
// keyboard-restore is applied — the keyboard stays locked for the
|
|
||||||
// entire CUT transfer.
|
|
||||||
if (fileTransfer != null) {
|
if (fileTransfer != null) {
|
||||||
fileTransfer.onScreenUpdated();
|
fileTransfer.onScreenUpdated();
|
||||||
}
|
}
|
||||||
@@ -400,6 +684,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
public void onScreenSizeChanged(int rows, int cols) {
|
public void onScreenSizeChanged(int rows, int cols) {
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
terminalPanel.guardedPack();
|
terminalPanel.guardedPack();
|
||||||
|
statusBar.updateStatus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,30 +706,75 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
"Insert — Toggle insert mode\n" +
|
"Insert — Toggle insert mode\n" +
|
||||||
"Escape — Reset\n" +
|
"Escape — Reset\n" +
|
||||||
"PageUp/Down — PF7/PF8\n" +
|
"PageUp/Down — PF7/PF8\n" +
|
||||||
"Alt+C — Clear\n" +
|
"Alt+C / Alt+K — Clear\n" +
|
||||||
"Alt+1/2/3 — PA1/PA2/PA3\n" +
|
"Alt+E — Erase Input\n" +
|
||||||
"Cmd+D — Disconnect\n" +
|
"Alt+A — Attention\n" +
|
||||||
"Cmd+Q — Quit\n" +
|
"Alt+S — System Request\n" +
|
||||||
"Cmd+=/-/0 — Font size +/-/reset";
|
"Alt+Q — Cursor Select\n" +
|
||||||
|
"Alt+L — Toggle Light Pen\n" +
|
||||||
|
"Alt+T — File Transfer\n" +
|
||||||
|
"Alt+=/-/0 — Font size +/-/reset\n" +
|
||||||
|
"Cmd/Ctrl+F — Find on Screen\n" +
|
||||||
|
"Cmd/Ctrl+G / F3— Find Next\n" +
|
||||||
|
"Cmd/Ctrl+D — Disconnect\n" +
|
||||||
|
"Cmd/Ctrl+Q — Quit";
|
||||||
|
|
||||||
JTextArea area = new JTextArea(text);
|
JTextArea area = new JTextArea(text);
|
||||||
area.setEditable(false);
|
area.setEditable(false);
|
||||||
area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 13));
|
area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 13));
|
||||||
area.setBackground(new Color(30, 30, 30));
|
ThemeManager.styleTextArea(area);
|
||||||
area.setForeground(new Color(200, 200, 200));
|
|
||||||
JScrollPane sp = new JScrollPane(area);
|
JScrollPane sp = new JScrollPane(area);
|
||||||
sp.setPreferredSize(new Dimension(360, 400));
|
ThemeManager.styleScrollPane(sp);
|
||||||
|
sp.setPreferredSize(new Dimension(380, 430));
|
||||||
JOptionPane.showMessageDialog(this, sp, "Key Mappings", JOptionPane.INFORMATION_MESSAGE);
|
JOptionPane.showMessageDialog(this, sp, "Key Mappings", JOptionPane.INFORMATION_MESSAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showMnemonicHelp() {
|
||||||
|
String text = "IBM Host On-Demand ECL Mnemonic Tokens:\n\n" +
|
||||||
|
"[enter] — Send Enter key\n" +
|
||||||
|
"[tab] — Tab to next field\n" +
|
||||||
|
"[backtab] — Back-tab to previous field\n" +
|
||||||
|
"[clear] — Clear screen\n" +
|
||||||
|
"[reset] — Reset keyboard lock\n" +
|
||||||
|
"[eraseeof] — Erase to End-of-Field\n" +
|
||||||
|
"[eraseinput] — Erase all unprotected fields\n" +
|
||||||
|
"[newline] — Advance to next line\n" +
|
||||||
|
"[dup] — Insert Duplicate order (0x1C)\n" +
|
||||||
|
"[fm] — Insert Field Mark order (0x1E)\n" +
|
||||||
|
"[attn] — Send Attention signal\n" +
|
||||||
|
"[sysreq] — System Request\n" +
|
||||||
|
"[cursel] — Cursor Select\n" +
|
||||||
|
"[pf1] - [pf24] — PF1 through PF24 keys\n" +
|
||||||
|
"[pa1] - [pa3] — PA1 through PA3 keys\n" +
|
||||||
|
"[up]/[down] — Cursor up / down\n" +
|
||||||
|
"[left]/[right] — Cursor left / right\n" +
|
||||||
|
"[home] — Cursor to home\n\n" +
|
||||||
|
"Example Script:\n" +
|
||||||
|
"LOGON APPLID(TSO)[enter]USER[tab]PASSWORD[enter][pf3]";
|
||||||
|
|
||||||
|
JTextArea area = new JTextArea(text);
|
||||||
|
area.setEditable(false);
|
||||||
|
area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 13));
|
||||||
|
ThemeManager.styleTextArea(area);
|
||||||
|
JScrollPane sp = new JScrollPane(area);
|
||||||
|
ThemeManager.styleScrollPane(sp);
|
||||||
|
sp.setPreferredSize(new Dimension(440, 430));
|
||||||
|
JOptionPane.showMessageDialog(this, sp, "Mnemonic Keystroke Reference", JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
}
|
||||||
|
|
||||||
private void showAbout() {
|
private void showAbout() {
|
||||||
JOptionPane.showMessageDialog(this,
|
JOptionPane.showMessageDialog(this,
|
||||||
"j3270 — Java TN3270 Terminal Emulator\n\n" +
|
"j3270 — Java TN3270 Terminal Emulator\n\n" +
|
||||||
"A Java reimplementation of x3270.\n" +
|
"A pure Java reimplementation of x3270 & IBM Host On-Demand ECL.\n" +
|
||||||
"lib3270j v0.1.0\n\n" +
|
"lib3270j v0.2.0\n\n" +
|
||||||
"Supports: TN3270, TN3270E (RFC 2355)\n" +
|
"Features:\n" +
|
||||||
"Models: IBM 3278/3279 Models 2-5\n" +
|
"• TN3270 & TN3270E (RFC 2355, SSCP-LU, NVT)\n" +
|
||||||
"Colors, Extended Attributes, Query Replies",
|
"• 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" +
|
||||||
|
"• 22+ Pluggable EBCDIC Codepages & DBCS Support\n" +
|
||||||
|
"• ECL Presentation Space Search, Inspector & Scripting",
|
||||||
"About j3270", JOptionPane.INFORMATION_MESSAGE);
|
"About j3270", JOptionPane.INFORMATION_MESSAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,48 +785,49 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
boolean cliTls = false;
|
boolean cliTls = false;
|
||||||
boolean cliNoVerifyCert = false;
|
boolean cliNoVerifyCert = false;
|
||||||
Boolean cliTn3270e = null;
|
Boolean cliTn3270e = null;
|
||||||
haus.nightmare.lib3270j.graphics.GraphicsMode cliGraphicsMode = null;
|
GraphicsMode cliGraphicsMode = null;
|
||||||
String configFile = null;
|
String configFile = null;
|
||||||
|
|
||||||
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++) {
|
||||||
if ("--debug".equals(args[i]) || "-d".equals(args[i])) {
|
String arg = args[i];
|
||||||
|
if ("-d".equals(arg) || "--debug".equals(arg)) {
|
||||||
debug = true;
|
debug = true;
|
||||||
} else if ("--tls".equals(args[i]) || "--ssl".equals(args[i]) || "-s".equals(args[i])) {
|
} else if ("-s".equals(arg) || "--tls".equals(arg) || "--ssl".equals(arg)) {
|
||||||
cliTls = true;
|
cliTls = true;
|
||||||
} else if ("--no-verify-cert".equals(args[i]) || "--insecure".equals(args[i]) || "-k".equals(args[i])) {
|
} else if ("--no-verify-cert".equals(arg) || "--insecure".equals(arg) || "-k".equals(arg)) {
|
||||||
cliNoVerifyCert = true;
|
cliNoVerifyCert = true;
|
||||||
} else if ("--no-tn3270e".equals(args[i]) || "--plain-tn3270".equals(args[i]) || "--plain".equals(args[i]) || "-P".equals(args[i]) || "-p".equals(args[i]) || "--non-e".equals(args[i])) {
|
} else if ("--no-tn3270e".equals(arg) || "--plain-tn3270".equals(arg) || "--plain".equals(arg) || "-P".equals(arg) || "-p".equals(arg) || "--non-e".equals(arg)) {
|
||||||
cliTn3270e = false;
|
cliTn3270e = false;
|
||||||
} else if ("--tn3270e".equals(args[i])) {
|
} else if ("--tn3270e".equals(arg)) {
|
||||||
cliTn3270e = true;
|
cliTn3270e = true;
|
||||||
} else if (args[i].startsWith("--graphics=")) {
|
} else if (arg.startsWith("--graphics=")) {
|
||||||
cliGraphicsMode = haus.nightmare.lib3270j.graphics.GraphicsMode.fromString(args[i].substring(11));
|
cliGraphicsMode = GraphicsMode.fromString(arg.substring(11));
|
||||||
} else if (("--graphics".equals(args[i]) || "-g".equals(args[i])) && i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
} else if (("--graphics".equals(arg) || "-g".equals(arg)) && i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
||||||
cliGraphicsMode = haus.nightmare.lib3270j.graphics.GraphicsMode.fromString(args[++i]);
|
cliGraphicsMode = GraphicsMode.fromString(args[++i]);
|
||||||
} else if ("--no-graphics".equals(args[i])) {
|
} else if ("--no-graphics".equals(arg)) {
|
||||||
cliGraphicsMode = haus.nightmare.lib3270j.graphics.GraphicsMode.NONE;
|
cliGraphicsMode = GraphicsMode.NONE;
|
||||||
} else if (("-c".equals(args[i]) || "--config".equals(args[i])) && i + 1 < args.length) {
|
} else if (("-c".equals(arg) || "--config".equals(arg)) && i + 1 < args.length) {
|
||||||
configFile = args[++i];
|
configFile = args[++i];
|
||||||
|
} else if (arg.startsWith("-")) {
|
||||||
|
System.err.println("Unknown option: " + arg);
|
||||||
} else {
|
} else {
|
||||||
remainingArgs.add(args[i]);
|
remainingArgs.add(arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure logging for application packages
|
Level logLevel = debug ? Level.FINE : Level.INFO;
|
||||||
Level logLevel = debug ? Level.ALL : Level.INFO;
|
|
||||||
Logger globalRoot = Logger.getLogger("");
|
Logger globalRoot = Logger.getLogger("");
|
||||||
for (java.util.logging.Handler h : globalRoot.getHandlers()) {
|
for (java.util.logging.Handler h : globalRoot.getHandlers()) {
|
||||||
globalRoot.removeHandler(h);
|
globalRoot.removeHandler(h);
|
||||||
}
|
}
|
||||||
globalRoot.setLevel(Level.ALL);
|
|
||||||
|
|
||||||
java.util.logging.Filter appFilter = record -> {
|
java.util.logging.Filter appFilter = record -> record.getLoggerName() != null &&
|
||||||
String name = record.getLoggerName();
|
(record.getLoggerName().startsWith("haus.nightmare") || record.getLoggerName().startsWith("org.pubvm"));
|
||||||
return name != null && (name.startsWith("haus.nightmare.lib3270j") || name.startsWith("haus.nightmare.j3270"));
|
|
||||||
};
|
|
||||||
|
|
||||||
ConsoleHandler consoleHandler = new ConsoleHandler();
|
ConsoleHandler consoleHandler = new ConsoleHandler();
|
||||||
consoleHandler.setLevel(logLevel);
|
consoleHandler.setLevel(Level.ALL);
|
||||||
consoleHandler.setFormatter(new SimpleFormatter());
|
consoleHandler.setFormatter(new SimpleFormatter());
|
||||||
consoleHandler.setFilter(appFilter);
|
consoleHandler.setFilter(appFilter);
|
||||||
globalRoot.addHandler(consoleHandler);
|
globalRoot.addHandler(consoleHandler);
|
||||||
@@ -522,7 +853,6 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
System.err.println("Could not create j3270.log: " + e.getMessage());
|
System.err.println("Could not create j3270.log: " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load INI config file if specified
|
|
||||||
if (configFile != null) {
|
if (configFile != null) {
|
||||||
try {
|
try {
|
||||||
haus.nightmare.j3270.config.Settings.loadFromIniFile(configFile);
|
haus.nightmare.j3270.config.Settings.loadFromIniFile(configFile);
|
||||||
@@ -533,43 +863,40 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dark look and feel
|
|
||||||
try {
|
try {
|
||||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.fine("Could not set system look and feel");
|
log.fine("Could not set system look and feel");
|
||||||
}
|
}
|
||||||
|
ThemeManager.applyUIManagerDefaults(haus.nightmare.j3270.config.Settings.getJavaUiTheme());
|
||||||
|
ThemeManager.setTheme(haus.nightmare.j3270.config.Settings.getJavaUiTheme());
|
||||||
|
|
||||||
// Use native macOS menu bar if applicable
|
|
||||||
System.setProperty("apple.laf.useScreenMenuBar", "true");
|
System.setProperty("apple.laf.useScreenMenuBar", "true");
|
||||||
System.setProperty("apple.awt.application.name", "j3270");
|
System.setProperty("apple.awt.application.name", "j3270");
|
||||||
|
|
||||||
final boolean finalTls = cliTls;
|
final boolean finalTls = cliTls;
|
||||||
final boolean finalNoVerify = cliNoVerifyCert;
|
final boolean finalNoVerify = cliNoVerifyCert;
|
||||||
final Boolean finalTn3270e = cliTn3270e;
|
final Boolean finalTn3270e = cliTn3270e;
|
||||||
final haus.nightmare.lib3270j.graphics.GraphicsMode finalGraphicsMode = cliGraphicsMode;
|
final GraphicsMode finalGraphicsMode = cliGraphicsMode;
|
||||||
|
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
J3270App app = new J3270App();
|
J3270App app = new J3270App();
|
||||||
app.setVisible(true);
|
app.setVisible(true);
|
||||||
|
|
||||||
// If host:port given on command line, connect directly
|
|
||||||
if (!remainingArgs.isEmpty()) {
|
if (!remainingArgs.isEmpty()) {
|
||||||
String hostArg = remainingArgs.get(0);
|
String hostArg = remainingArgs.get(0);
|
||||||
int port = finalTls ? 992 : 23;
|
int port = finalTls ? 992 : 23;
|
||||||
if (remainingArgs.size() >= 2) {
|
if (remainingArgs.size() >= 2) {
|
||||||
try {
|
try {
|
||||||
port = Integer.parseInt(remainingArgs.get(1));
|
port = Integer.parseInt(remainingArgs.get(1));
|
||||||
} catch (NumberFormatException ignored) {
|
} catch (NumberFormatException ignored) {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
TerminalModel model = TerminalModel.IBM_3279_4;
|
TerminalModel model = TerminalModel.IBM_3279_4;
|
||||||
if (remainingArgs.size() >= 3) {
|
if (remainingArgs.size() >= 3) {
|
||||||
try {
|
try {
|
||||||
int modelNum = Integer.parseInt(remainingArgs.get(2));
|
int modelNum = Integer.parseInt(remainingArgs.get(2));
|
||||||
model = TerminalModel.forModel(modelNum, true);
|
model = TerminalModel.forModel(modelNum, true);
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ConnectionConfig config = ConnectionConfig.parseHostString(hostArg, port, model);
|
ConnectionConfig config = ConnectionConfig.parseHostString(hostArg, port, model);
|
||||||
if (finalTls) {
|
if (finalTls) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package haus.nightmare.j3270.config;
|
package haus.nightmare.j3270.config;
|
||||||
|
|
||||||
|
import haus.nightmare.j3270.ui.UITheme;
|
||||||
import java.util.prefs.Preferences;
|
import java.util.prefs.Preferences;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
@@ -17,6 +18,15 @@ public class Settings {
|
|||||||
AUTO_CONNECT
|
AUTO_CONNECT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static UITheme getJavaUiTheme() {
|
||||||
|
String themeStr = prefs.get("javaUiTheme", UITheme.DARK.name());
|
||||||
|
return UITheme.fromString(themeStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setJavaUiTheme(UITheme theme) {
|
||||||
|
prefs.put("javaUiTheme", (theme != null ? theme : UITheme.DARK).name());
|
||||||
|
}
|
||||||
|
|
||||||
public static String getFontFamily() {
|
public static String getFontFamily() {
|
||||||
return prefs.get("fontFamily", "Monospaced");
|
return prefs.get("fontFamily", "Monospaced");
|
||||||
}
|
}
|
||||||
@@ -95,6 +105,14 @@ public class Settings {
|
|||||||
prefs.put("graphicsMode", (mode != null ? mode : haus.nightmare.lib3270j.graphics.GraphicsMode.BOTH).name());
|
prefs.put("graphicsMode", (mode != null ? mode : haus.nightmare.lib3270j.graphics.GraphicsMode.BOTH).name());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getCodePage() {
|
||||||
|
return prefs.get("codePage", "037");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setCodePage(String codePage) {
|
||||||
|
prefs.put("codePage", (codePage != null && !codePage.trim().isEmpty()) ? codePage.trim() : "037");
|
||||||
|
}
|
||||||
|
|
||||||
public static Color getColorOverride(int index, Color defaultColor) {
|
public static Color getColorOverride(int index, Color defaultColor) {
|
||||||
String hex = prefs.get("color_" + index, null);
|
String hex = prefs.get("color_" + index, null);
|
||||||
try {
|
try {
|
||||||
@@ -221,6 +239,11 @@ public class Settings {
|
|||||||
switch (key) {
|
switch (key) {
|
||||||
case "fontFamily": setFontFamily(value); break;
|
case "fontFamily": setFontFamily(value); break;
|
||||||
case "fontSize": setFontSize(Integer.parseInt(value)); break;
|
case "fontSize": setFontSize(Integer.parseInt(value)); break;
|
||||||
|
case "javaUiTheme":
|
||||||
|
case "theme":
|
||||||
|
case "uiTheme":
|
||||||
|
setJavaUiTheme(UITheme.fromString(value));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
log.warning("Unknown appearance key: " + key);
|
log.warning("Unknown appearance key: " + key);
|
||||||
}
|
}
|
||||||
@@ -256,6 +279,11 @@ public class Settings {
|
|||||||
case "enableTn3270e":
|
case "enableTn3270e":
|
||||||
setAutoConnectTn3270e(Boolean.parseBoolean(value));
|
setAutoConnectTn3270e(Boolean.parseBoolean(value));
|
||||||
break;
|
break;
|
||||||
|
case "codePage":
|
||||||
|
case "codepage":
|
||||||
|
case "charset":
|
||||||
|
setCodePage(value);
|
||||||
|
break;
|
||||||
case "blockSelectMode": setBlockSelectMode(Boolean.parseBoolean(value)); break;
|
case "blockSelectMode": setBlockSelectMode(Boolean.parseBoolean(value)); break;
|
||||||
default:
|
default:
|
||||||
log.warning("Unknown behavior/connection key: " + key);
|
log.warning("Unknown behavior/connection key: " + key);
|
||||||
@@ -309,6 +337,7 @@ public class Settings {
|
|||||||
|
|
||||||
// [appearance]
|
// [appearance]
|
||||||
w.println("[appearance]");
|
w.println("[appearance]");
|
||||||
|
w.println("javaUiTheme = " + getJavaUiTheme().name());
|
||||||
w.println("fontFamily = " + getFontFamily());
|
w.println("fontFamily = " + getFontFamily());
|
||||||
w.println("fontSize = " + getFontSize());
|
w.println("fontSize = " + getFontSize());
|
||||||
w.println();
|
w.println();
|
||||||
@@ -324,6 +353,7 @@ public class Settings {
|
|||||||
w.println("autoConnectVerifyCert = " + getAutoConnectVerifyCert());
|
w.println("autoConnectVerifyCert = " + getAutoConnectVerifyCert());
|
||||||
w.println("autoConnectTn3270e = " + getAutoConnectTn3270e());
|
w.println("autoConnectTn3270e = " + getAutoConnectTn3270e());
|
||||||
}
|
}
|
||||||
|
w.println("codePage = " + getCodePage());
|
||||||
w.println("blockSelectMode = " + getBlockSelectMode());
|
w.println("blockSelectMode = " + getBlockSelectMode());
|
||||||
w.println();
|
w.println();
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,10 @@ public class FileTransfer implements FTCut.FTCutListener, FTDft.FTDftListener {
|
|||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Telnet3270Client getClient() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start a new file transfer with the given configuration.
|
* Start a new file transfer with the given configuration.
|
||||||
* @return null if started successfully, error message otherwise.
|
* @return null if started successfully, error message otherwise.
|
||||||
@@ -121,6 +125,21 @@ public class FileTransfer implements FTCut.FTCutListener, FTDft.FTDftListener {
|
|||||||
if (state == FTState.RUNNING || state == FTState.AWAIT_ACK) {
|
if (state == FTState.RUNNING || state == FTState.AWAIT_ACK) {
|
||||||
log.info("User cancelled transfer");
|
log.info("User cancelled transfer");
|
||||||
setState(FTState.ABORT_WAIT); // Signal handlers to abort at next chance
|
setState(FTState.ABORT_WAIT); // Signal handlers to abort at next chance
|
||||||
|
// Start a safety timeout in case the host does not send further packets to trigger abort
|
||||||
|
cancelTimeout();
|
||||||
|
timeoutTimer = new Timer("FTCancelTimeout", true);
|
||||||
|
timeoutTimer.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
if (state != FTState.NONE) {
|
||||||
|
log.warning("Transfer cancel timeout — force resetting state");
|
||||||
|
completeTransfer("Transfer cancelled by user.");
|
||||||
|
callback.onTransferAborted("Cancelled by user.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
} else if (state != FTState.NONE) {
|
} else if (state != FTState.NONE) {
|
||||||
log.info("Forcing cancel from state " + state);
|
log.info("Forcing cancel from state " + state);
|
||||||
completeTransfer("Transfer cancelled.");
|
completeTransfer("Transfer cancelled.");
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package haus.nightmare.j3270.ft;
|
package haus.nightmare.j3270.ft;
|
||||||
|
|
||||||
|
import haus.nightmare.j3270.ui.HostDirectoryDialog;
|
||||||
|
import haus.nightmare.j3270.ui.ThemeManager;
|
||||||
import haus.nightmare.lib3270j.ft.FTConfig;
|
import haus.nightmare.lib3270j.ft.FTConfig;
|
||||||
|
import haus.nightmare.lib3270j.ft.FTConstants;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.border.EmptyBorder;
|
import javax.swing.border.EmptyBorder;
|
||||||
@@ -18,6 +21,8 @@ public class FileTransferDialog extends JDialog {
|
|||||||
private JTextField localFileField;
|
private JTextField localFileField;
|
||||||
private JButton browseLocalButton;
|
private JButton browseLocalButton;
|
||||||
private JTextField hostFileField;
|
private JTextField hostFileField;
|
||||||
|
private JButton browseHostButton;
|
||||||
|
private JComboBox<Integer> mtuCombo;
|
||||||
|
|
||||||
// Options
|
// Options
|
||||||
private JRadioButton asciiRadio;
|
private JRadioButton asciiRadio;
|
||||||
@@ -40,7 +45,7 @@ public class FileTransferDialog extends JDialog {
|
|||||||
private JButton cancelButton;
|
private JButton cancelButton;
|
||||||
|
|
||||||
public FileTransferDialog(Frame owner, FileTransfer coordinator) {
|
public FileTransferDialog(Frame owner, FileTransfer coordinator) {
|
||||||
super(owner, "File Transfer (IND$FILE)", false); // non-modal to see progress
|
super(owner, "File Transfer (IND$FILE)", false);
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
this.coordinator = coordinator;
|
this.coordinator = coordinator;
|
||||||
|
|
||||||
@@ -76,7 +81,6 @@ public class FileTransferDialog extends JDialog {
|
|||||||
dirPanel.add(Box.createRigidArea(new Dimension(15, 0)));
|
dirPanel.add(Box.createRigidArea(new Dimension(15, 0)));
|
||||||
dirPanel.add(sendRadio);
|
dirPanel.add(sendRadio);
|
||||||
|
|
||||||
// Disable append/overwrite when sending
|
|
||||||
sendRadio.addActionListener(e -> updateOptionStates());
|
sendRadio.addActionListener(e -> updateOptionStates());
|
||||||
receiveRadio.addActionListener(e -> updateOptionStates());
|
receiveRadio.addActionListener(e -> updateOptionStates());
|
||||||
|
|
||||||
@@ -85,6 +89,7 @@ public class FileTransferDialog extends JDialog {
|
|||||||
// Local File
|
// Local File
|
||||||
localFileField = new JTextField(20);
|
localFileField = new JTextField(20);
|
||||||
browseLocalButton = new JButton("Browse...");
|
browseLocalButton = new JButton("Browse...");
|
||||||
|
ThemeManager.styleButton(browseLocalButton, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
browseLocalButton.addActionListener(e -> browseLocalFile());
|
browseLocalButton.addActionListener(e -> browseLocalFile());
|
||||||
JPanel localPanel = new JPanel(new BorderLayout(5, 0));
|
JPanel localPanel = new JPanel(new BorderLayout(5, 0));
|
||||||
localPanel.setOpaque(false);
|
localPanel.setOpaque(false);
|
||||||
@@ -94,7 +99,14 @@ public class FileTransferDialog extends JDialog {
|
|||||||
|
|
||||||
// Host File
|
// Host File
|
||||||
hostFileField = new JTextField(20);
|
hostFileField = new JTextField(20);
|
||||||
formPanel.add(createRow("Host File:", hostFileField));
|
browseHostButton = new JButton("Browse Host...");
|
||||||
|
ThemeManager.styleButton(browseHostButton, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
browseHostButton.addActionListener(e -> browseHostDirectory());
|
||||||
|
JPanel hostPanel = new JPanel(new BorderLayout(5, 0));
|
||||||
|
hostPanel.setOpaque(false);
|
||||||
|
hostPanel.add(hostFileField, BorderLayout.CENTER);
|
||||||
|
hostPanel.add(browseHostButton, BorderLayout.EAST);
|
||||||
|
formPanel.add(createRow("Host File:", hostPanel));
|
||||||
|
|
||||||
// Mode
|
// Mode
|
||||||
asciiRadio = new JRadioButton("ASCII");
|
asciiRadio = new JRadioButton("ASCII");
|
||||||
@@ -114,6 +126,11 @@ public class FileTransferDialog extends JDialog {
|
|||||||
|
|
||||||
formPanel.add(createRow("Transfer Mode:", modePanel));
|
formPanel.add(createRow("Transfer Mode:", modePanel));
|
||||||
|
|
||||||
|
// MTU Buffer Size
|
||||||
|
mtuCombo = new JComboBox<>(new Integer[]{2048, 4096, 8192, 16384, 32768});
|
||||||
|
mtuCombo.setSelectedItem(FTConstants.DFT_BUF);
|
||||||
|
formPanel.add(createRow("DFT Buffer Size:", mtuCombo));
|
||||||
|
|
||||||
// Options (checkboxes)
|
// Options (checkboxes)
|
||||||
crCheck = new JCheckBox("Add/Remove CR");
|
crCheck = new JCheckBox("Add/Remove CR");
|
||||||
crCheck.setSelected(true);
|
crCheck.setSelected(true);
|
||||||
@@ -140,10 +157,7 @@ public class FileTransferDialog extends JDialog {
|
|||||||
// Host-specific options panel (TSO dataset allocation)
|
// Host-specific options panel (TSO dataset allocation)
|
||||||
JPanel hostOptsPanel = new JPanel(new GridLayout(2, 4, 5, 5));
|
JPanel hostOptsPanel = new JPanel(new GridLayout(2, 4, 5, 5));
|
||||||
hostOptsPanel.setOpaque(false);
|
hostOptsPanel.setOpaque(false);
|
||||||
hostOptsPanel.setBorder(BorderFactory.createTitledBorder(
|
hostOptsPanel.setBorder(ThemeManager.createTitledBorder("TSO Allocation Options (Send Only)"));
|
||||||
BorderFactory.createLineBorder(new Color(60, 60, 60)),
|
|
||||||
"TSO Allocation Options (Send Only)"));
|
|
||||||
((javax.swing.border.TitledBorder)hostOptsPanel.getBorder()).setTitleColor(new Color(180, 180, 180));
|
|
||||||
|
|
||||||
hostOptsPanel.add(new JLabel("RECFM:"));
|
hostOptsPanel.add(new JLabel("RECFM:"));
|
||||||
recfmField = new JTextField(5);
|
recfmField = new JTextField(5);
|
||||||
@@ -167,10 +181,7 @@ public class FileTransferDialog extends JDialog {
|
|||||||
// CMS options
|
// CMS options
|
||||||
JPanel vmOptsPanel = new JPanel(new BorderLayout(5, 0));
|
JPanel vmOptsPanel = new JPanel(new BorderLayout(5, 0));
|
||||||
vmOptsPanel.setOpaque(false);
|
vmOptsPanel.setOpaque(false);
|
||||||
vmOptsPanel.setBorder(BorderFactory.createTitledBorder(
|
vmOptsPanel.setBorder(ThemeManager.createTitledBorder("CMS Options"));
|
||||||
BorderFactory.createLineBorder(new Color(60, 60, 60)),
|
|
||||||
"CMS Options"));
|
|
||||||
((javax.swing.border.TitledBorder)vmOptsPanel.getBorder()).setTitleColor(new Color(180, 180, 180));
|
|
||||||
|
|
||||||
optionsField = new JTextField(20);
|
optionsField = new JTextField(20);
|
||||||
vmOptsPanel.add(new JLabel("Additional Options: "), BorderLayout.WEST);
|
vmOptsPanel.add(new JLabel("Additional Options: "), BorderLayout.WEST);
|
||||||
@@ -186,9 +197,11 @@ public class FileTransferDialog extends JDialog {
|
|||||||
buttonPanel.setOpaque(false);
|
buttonPanel.setOpaque(false);
|
||||||
|
|
||||||
transferButton = new JButton("Start Transfer");
|
transferButton = new JButton("Start Transfer");
|
||||||
|
ThemeManager.styleButton(transferButton, ThemeManager.ButtonVariant.PRIMARY);
|
||||||
transferButton.addActionListener(e -> startTransfer());
|
transferButton.addActionListener(e -> startTransfer());
|
||||||
|
|
||||||
cancelButton = new JButton("Close");
|
cancelButton = new JButton("Close");
|
||||||
|
ThemeManager.styleButton(cancelButton, ThemeManager.ButtonVariant.CANCEL);
|
||||||
cancelButton.addActionListener(e -> dispose());
|
cancelButton.addActionListener(e -> dispose());
|
||||||
|
|
||||||
buttonPanel.add(cancelButton);
|
buttonPanel.add(cancelButton);
|
||||||
@@ -198,10 +211,7 @@ public class FileTransferDialog extends JDialog {
|
|||||||
|
|
||||||
setContentPane(mainPanel);
|
setContentPane(mainPanel);
|
||||||
|
|
||||||
// Theme styling for components
|
ThemeManager.applyThemeToWindow(this);
|
||||||
applyTheme(mainPanel);
|
|
||||||
|
|
||||||
// Initialize state
|
|
||||||
updateOptionStates();
|
updateOptionStates();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,14 +246,29 @@ public class FileTransferDialog extends JDialog {
|
|||||||
JFileChooser chooser = new JFileChooser();
|
JFileChooser chooser = new JFileChooser();
|
||||||
if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
|
if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
|
||||||
localFileField.setText(chooser.getSelectedFile().getAbsolutePath());
|
localFileField.setText(chooser.getSelectedFile().getAbsolutePath());
|
||||||
|
|
||||||
// Auto-fill host file if empty
|
|
||||||
if (hostFileField.getText().trim().isEmpty()) {
|
if (hostFileField.getText().trim().isEmpty()) {
|
||||||
hostFileField.setText(chooser.getSelectedFile().getName());
|
hostFileField.setText(chooser.getSelectedFile().getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void browseHostDirectory() {
|
||||||
|
FTConfig.HostType hostType = (FTConfig.HostType) hostTypeCombo.getSelectedItem();
|
||||||
|
String initialQuery = hostFileField.getText().trim();
|
||||||
|
HostDirectoryDialog dialog = new HostDirectoryDialog(this, coordinator.getClient(), hostType, initialQuery);
|
||||||
|
dialog.setVisible(true);
|
||||||
|
|
||||||
|
if (dialog.isConfirmed() && dialog.getSelectedHostFile() != null) {
|
||||||
|
hostFileField.setText(dialog.getSelectedHostFile());
|
||||||
|
if (localFileField.getText().trim().isEmpty()) {
|
||||||
|
String cleanName = dialog.getSelectedHostFile().replace("'", "").replace("\"", "");
|
||||||
|
int lastDot = cleanName.lastIndexOf('.');
|
||||||
|
if (lastDot > 0) cleanName = cleanName.substring(lastDot + 1);
|
||||||
|
localFileField.setText(cleanName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void startTransfer() {
|
private void startTransfer() {
|
||||||
if (localFileField.getText().trim().isEmpty() || hostFileField.getText().trim().isEmpty()) {
|
if (localFileField.getText().trim().isEmpty() || hostFileField.getText().trim().isEmpty()) {
|
||||||
JOptionPane.showMessageDialog(this, "Local and Host filenames are required.",
|
JOptionPane.showMessageDialog(this, "Local and Host filenames are required.",
|
||||||
@@ -262,6 +287,11 @@ public class FileTransferDialog extends JDialog {
|
|||||||
config.setAppend(appendCheck.isSelected());
|
config.setAppend(appendCheck.isSelected());
|
||||||
config.setOverwrite(overwriteCheck.isSelected());
|
config.setOverwrite(overwriteCheck.isSelected());
|
||||||
|
|
||||||
|
Integer mtu = (Integer) mtuCombo.getSelectedItem();
|
||||||
|
if (mtu != null) {
|
||||||
|
config.setDftBufferSize(mtu);
|
||||||
|
}
|
||||||
|
|
||||||
config.setRecfm(recfmField.getText().trim());
|
config.setRecfm(recfmField.getText().trim());
|
||||||
config.setLrecl(lreclField.getText().trim());
|
config.setLrecl(lreclField.getText().trim());
|
||||||
config.setBlksize(blksizeField.getText().trim());
|
config.setBlksize(blksizeField.getText().trim());
|
||||||
@@ -272,7 +302,6 @@ public class FileTransferDialog extends JDialog {
|
|||||||
if (error != null) {
|
if (error != null) {
|
||||||
JOptionPane.showMessageDialog(this, error, "Transfer Error", JOptionPane.ERROR_MESSAGE);
|
JOptionPane.showMessageDialog(this, error, "Transfer Error", JOptionPane.ERROR_MESSAGE);
|
||||||
} else {
|
} else {
|
||||||
// Success, close dialog (progress will be shown separately)
|
|
||||||
dispose();
|
dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ public class ConnectDialog extends JDialog {
|
|||||||
private JTextField portField;
|
private JTextField portField;
|
||||||
private JComboBox<TerminalModel> modelCombo;
|
private JComboBox<TerminalModel> modelCombo;
|
||||||
private JComboBox<haus.nightmare.lib3270j.graphics.GraphicsMode> graphicsCombo;
|
private JComboBox<haus.nightmare.lib3270j.graphics.GraphicsMode> graphicsCombo;
|
||||||
|
private JComboBox<String> codePageCombo;
|
||||||
private JTextField luField;
|
private JTextField luField;
|
||||||
private JCheckBox tlsCheckBox;
|
private JCheckBox tlsCheckBox;
|
||||||
private JCheckBox verifyCertCheckBox;
|
private JCheckBox verifyCertCheckBox;
|
||||||
@@ -33,24 +34,21 @@ public class ConnectDialog extends JDialog {
|
|||||||
private void buildUI() {
|
private void buildUI() {
|
||||||
JPanel mainPanel = new JPanel(new GridBagLayout());
|
JPanel mainPanel = new JPanel(new GridBagLayout());
|
||||||
mainPanel.setBorder(BorderFactory.createEmptyBorder(16, 16, 16, 16));
|
mainPanel.setBorder(BorderFactory.createEmptyBorder(16, 16, 16, 16));
|
||||||
mainPanel.setBackground(new Color(30, 30, 30));
|
|
||||||
GridBagConstraints gbc = new GridBagConstraints();
|
GridBagConstraints gbc = new GridBagConstraints();
|
||||||
gbc.insets = new Insets(4, 4, 4, 4);
|
gbc.insets = new Insets(5, 6, 5, 6);
|
||||||
gbc.fill = GridBagConstraints.HORIZONTAL;
|
gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
|
||||||
Font labelFont = new Font(Font.SANS_SERIF, Font.PLAIN, 14);
|
Font labelFont = new Font(Font.SANS_SERIF, Font.PLAIN, 13);
|
||||||
Color fg = new Color(200, 200, 200);
|
|
||||||
|
|
||||||
// Host
|
// Host
|
||||||
gbc.gridx = 0;
|
gbc.gridx = 0;
|
||||||
gbc.gridy = 0;
|
gbc.gridy = 0;
|
||||||
JLabel hostLabel = new JLabel("Host:");
|
JLabel hostLabel = new JLabel("Host:");
|
||||||
hostLabel.setForeground(fg);
|
|
||||||
hostLabel.setFont(labelFont);
|
hostLabel.setFont(labelFont);
|
||||||
mainPanel.add(hostLabel, gbc);
|
mainPanel.add(hostLabel, gbc);
|
||||||
gbc.gridx = 1;
|
gbc.gridx = 1;
|
||||||
gbc.weightx = 1.0;
|
gbc.weightx = 1.0;
|
||||||
hostField = createDarkField(20);
|
hostField = createField(20);
|
||||||
mainPanel.add(hostField, gbc);
|
mainPanel.add(hostField, gbc);
|
||||||
|
|
||||||
// Port
|
// Port
|
||||||
@@ -58,12 +56,11 @@ public class ConnectDialog extends JDialog {
|
|||||||
gbc.gridy = 1;
|
gbc.gridy = 1;
|
||||||
gbc.weightx = 0;
|
gbc.weightx = 0;
|
||||||
JLabel portLabel = new JLabel("Port:");
|
JLabel portLabel = new JLabel("Port:");
|
||||||
portLabel.setForeground(fg);
|
|
||||||
portLabel.setFont(labelFont);
|
portLabel.setFont(labelFont);
|
||||||
mainPanel.add(portLabel, gbc);
|
mainPanel.add(portLabel, gbc);
|
||||||
gbc.gridx = 1;
|
gbc.gridx = 1;
|
||||||
gbc.weightx = 1.0;
|
gbc.weightx = 1.0;
|
||||||
portField = createDarkField(6);
|
portField = createField(6);
|
||||||
portField.setText("23");
|
portField.setText("23");
|
||||||
mainPanel.add(portField, gbc);
|
mainPanel.add(portField, gbc);
|
||||||
|
|
||||||
@@ -72,16 +69,14 @@ public class ConnectDialog extends JDialog {
|
|||||||
gbc.gridy = 2;
|
gbc.gridy = 2;
|
||||||
gbc.weightx = 0;
|
gbc.weightx = 0;
|
||||||
JLabel modelLabel = new JLabel("Model:");
|
JLabel modelLabel = new JLabel("Model:");
|
||||||
modelLabel.setForeground(fg);
|
|
||||||
modelLabel.setFont(labelFont);
|
modelLabel.setFont(labelFont);
|
||||||
mainPanel.add(modelLabel, gbc);
|
mainPanel.add(modelLabel, gbc);
|
||||||
gbc.gridx = 1;
|
gbc.gridx = 1;
|
||||||
gbc.weightx = 1.0;
|
gbc.weightx = 1.0;
|
||||||
modelCombo = new JComboBox<>(TerminalModel.values());
|
modelCombo = new JComboBox<>(TerminalModel.values());
|
||||||
modelCombo.setSelectedItem(TerminalModel.IBM_3279_4);
|
modelCombo.setSelectedItem(TerminalModel.IBM_3279_4);
|
||||||
modelCombo.setBackground(new Color(45, 45, 45));
|
|
||||||
modelCombo.setForeground(fg);
|
|
||||||
modelCombo.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 13));
|
modelCombo.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 13));
|
||||||
|
ThemeManager.styleComboBox(modelCombo);
|
||||||
mainPanel.add(modelCombo, gbc);
|
mainPanel.add(modelCombo, gbc);
|
||||||
|
|
||||||
// LU Name
|
// LU Name
|
||||||
@@ -89,12 +84,11 @@ public class ConnectDialog extends JDialog {
|
|||||||
gbc.gridy = 3;
|
gbc.gridy = 3;
|
||||||
gbc.weightx = 0;
|
gbc.weightx = 0;
|
||||||
JLabel luLabel = new JLabel("LU Name:");
|
JLabel luLabel = new JLabel("LU Name:");
|
||||||
luLabel.setForeground(fg);
|
|
||||||
luLabel.setFont(labelFont);
|
luLabel.setFont(labelFont);
|
||||||
mainPanel.add(luLabel, gbc);
|
mainPanel.add(luLabel, gbc);
|
||||||
gbc.gridx = 1;
|
gbc.gridx = 1;
|
||||||
gbc.weightx = 1.0;
|
gbc.weightx = 1.0;
|
||||||
luField = createDarkField(12);
|
luField = createField(12);
|
||||||
mainPanel.add(luField, gbc);
|
mainPanel.add(luField, gbc);
|
||||||
|
|
||||||
// Graphics Mode
|
// Graphics Mode
|
||||||
@@ -102,27 +96,82 @@ public class ConnectDialog extends JDialog {
|
|||||||
gbc.gridy = 4;
|
gbc.gridy = 4;
|
||||||
gbc.weightx = 0;
|
gbc.weightx = 0;
|
||||||
JLabel graphicsLabel = new JLabel("Graphics:");
|
JLabel graphicsLabel = new JLabel("Graphics:");
|
||||||
graphicsLabel.setForeground(fg);
|
|
||||||
graphicsLabel.setFont(labelFont);
|
graphicsLabel.setFont(labelFont);
|
||||||
mainPanel.add(graphicsLabel, gbc);
|
mainPanel.add(graphicsLabel, gbc);
|
||||||
gbc.gridx = 1;
|
gbc.gridx = 1;
|
||||||
gbc.weightx = 1.0;
|
gbc.weightx = 1.0;
|
||||||
graphicsCombo = new JComboBox<>(haus.nightmare.lib3270j.graphics.GraphicsMode.values());
|
graphicsCombo = new JComboBox<>(haus.nightmare.lib3270j.graphics.GraphicsMode.values());
|
||||||
graphicsCombo.setSelectedItem(haus.nightmare.j3270.config.Settings.getGraphicsMode());
|
graphicsCombo.setSelectedItem(haus.nightmare.j3270.config.Settings.getGraphicsMode());
|
||||||
graphicsCombo.setBackground(new Color(45, 45, 45));
|
|
||||||
graphicsCombo.setForeground(fg);
|
|
||||||
graphicsCombo.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 13));
|
graphicsCombo.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 13));
|
||||||
|
ThemeManager.styleComboBox(graphicsCombo);
|
||||||
mainPanel.add(graphicsCombo, gbc);
|
mainPanel.add(graphicsCombo, gbc);
|
||||||
|
|
||||||
|
// Code Page
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = 5;
|
||||||
|
gbc.weightx = 0;
|
||||||
|
JLabel cpLabel = new JLabel("Code Page:");
|
||||||
|
cpLabel.setFont(labelFont);
|
||||||
|
mainPanel.add(cpLabel, gbc);
|
||||||
|
gbc.gridx = 1;
|
||||||
|
gbc.weightx = 1.0;
|
||||||
|
String[] commonCodePages = {
|
||||||
|
"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 Lowercase)",
|
||||||
|
"803 - Hebrew Old / Standard",
|
||||||
|
"838 - Thai",
|
||||||
|
"1160 - Thai (Euro \u20AC)",
|
||||||
|
"1025 - Cyrillic Multilingual",
|
||||||
|
"1123 - Cyrillic Ukraine",
|
||||||
|
"1154 - Cyrillic Multilingual (Euro \u20AC)",
|
||||||
|
"880 - Cyrillic Russian",
|
||||||
|
"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",
|
||||||
|
"1388 - Simplified Chinese Extended Mixed DBCS",
|
||||||
|
"937 - Traditional Chinese Mixed DBCS",
|
||||||
|
"1371 - Traditional Chinese Extended Mixed DBCS",
|
||||||
|
"933 - Korean Mixed DBCS"
|
||||||
|
};
|
||||||
|
codePageCombo = new JComboBox<>(commonCodePages);
|
||||||
|
codePageCombo.setEditable(true);
|
||||||
|
String currentCp = haus.nightmare.j3270.config.Settings.getCodePage();
|
||||||
|
for (String item : commonCodePages) {
|
||||||
|
if (item.startsWith(currentCp + " ") || item.equals(currentCp)) {
|
||||||
|
codePageCombo.setSelectedItem(item);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
codePageCombo.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 13));
|
||||||
|
ThemeManager.styleComboBox(codePageCombo);
|
||||||
|
mainPanel.add(codePageCombo, gbc);
|
||||||
|
|
||||||
// TLS / SSL Checkbox
|
// TLS / SSL Checkbox
|
||||||
gbc.gridx = 1;
|
gbc.gridx = 1;
|
||||||
gbc.gridy = 5;
|
gbc.gridy = 6;
|
||||||
gbc.weightx = 1.0;
|
gbc.weightx = 1.0;
|
||||||
tlsCheckBox = new JCheckBox("Enable TLS/SSL");
|
tlsCheckBox = new JCheckBox("Enable TLS/SSL");
|
||||||
tlsCheckBox.setBackground(new Color(30, 30, 30));
|
ThemeManager.styleCheckBox(tlsCheckBox);
|
||||||
tlsCheckBox.setForeground(fg);
|
|
||||||
tlsCheckBox.setFont(labelFont);
|
tlsCheckBox.setFont(labelFont);
|
||||||
tlsCheckBox.setFocusPainted(false);
|
|
||||||
tlsCheckBox.addActionListener(e -> {
|
tlsCheckBox.addActionListener(e -> {
|
||||||
boolean isTls = tlsCheckBox.isSelected();
|
boolean isTls = tlsCheckBox.isSelected();
|
||||||
verifyCertCheckBox.setEnabled(isTls);
|
verifyCertCheckBox.setEnabled(isTls);
|
||||||
@@ -137,40 +186,33 @@ public class ConnectDialog extends JDialog {
|
|||||||
|
|
||||||
// Verify Certificate Checkbox
|
// Verify Certificate Checkbox
|
||||||
gbc.gridx = 1;
|
gbc.gridx = 1;
|
||||||
gbc.gridy = 6;
|
gbc.gridy = 7;
|
||||||
verifyCertCheckBox = new JCheckBox("Verify Server Certificate");
|
verifyCertCheckBox = new JCheckBox("Verify Server Certificate");
|
||||||
verifyCertCheckBox.setBackground(new Color(30, 30, 30));
|
ThemeManager.styleCheckBox(verifyCertCheckBox);
|
||||||
verifyCertCheckBox.setForeground(new Color(160, 160, 160));
|
|
||||||
verifyCertCheckBox.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 13));
|
verifyCertCheckBox.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 13));
|
||||||
verifyCertCheckBox.setSelected(true);
|
verifyCertCheckBox.setSelected(true);
|
||||||
verifyCertCheckBox.setEnabled(false);
|
verifyCertCheckBox.setEnabled(false);
|
||||||
verifyCertCheckBox.setFocusPainted(false);
|
|
||||||
mainPanel.add(verifyCertCheckBox, gbc);
|
mainPanel.add(verifyCertCheckBox, gbc);
|
||||||
|
|
||||||
// TN3270E Checkbox
|
// TN3270E Checkbox
|
||||||
gbc.gridx = 1;
|
gbc.gridx = 1;
|
||||||
gbc.gridy = 7;
|
gbc.gridy = 8;
|
||||||
tn3270eCheckBox = new JCheckBox("Enable TN3270E (Extended 3270)");
|
tn3270eCheckBox = new JCheckBox("Enable TN3270E (Extended 3270)");
|
||||||
tn3270eCheckBox.setBackground(new Color(30, 30, 30));
|
ThemeManager.styleCheckBox(tn3270eCheckBox);
|
||||||
tn3270eCheckBox.setForeground(fg);
|
|
||||||
tn3270eCheckBox.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 13));
|
tn3270eCheckBox.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 13));
|
||||||
tn3270eCheckBox.setSelected(haus.nightmare.j3270.config.Settings.getAutoConnectTn3270e());
|
tn3270eCheckBox.setSelected(haus.nightmare.j3270.config.Settings.getAutoConnectTn3270e());
|
||||||
tn3270eCheckBox.setFocusPainted(false);
|
|
||||||
mainPanel.add(tn3270eCheckBox, gbc);
|
mainPanel.add(tn3270eCheckBox, gbc);
|
||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 8, 4));
|
||||||
buttonPanel.setBackground(new Color(30, 30, 30));
|
|
||||||
|
|
||||||
JButton connectBtn = new JButton("Connect");
|
JButton connectBtn = new JButton("Connect");
|
||||||
connectBtn.setBackground(new Color(50, 120, 50));
|
ThemeManager.styleButton(connectBtn, ThemeManager.ButtonVariant.PRIMARY);
|
||||||
connectBtn.setForeground(Color.WHITE);
|
|
||||||
connectBtn.setFont(labelFont);
|
connectBtn.setFont(labelFont);
|
||||||
connectBtn.addActionListener(e -> onConnect());
|
connectBtn.addActionListener(e -> onConnect());
|
||||||
|
|
||||||
JButton cancelBtn = new JButton("Cancel");
|
JButton cancelBtn = new JButton("Cancel");
|
||||||
cancelBtn.setBackground(new Color(60, 60, 60));
|
ThemeManager.styleButton(cancelBtn, ThemeManager.ButtonVariant.CANCEL);
|
||||||
cancelBtn.setForeground(fg);
|
|
||||||
cancelBtn.setFont(labelFont);
|
cancelBtn.setFont(labelFont);
|
||||||
cancelBtn.addActionListener(e -> {
|
cancelBtn.addActionListener(e -> {
|
||||||
confirmed = false;
|
confirmed = false;
|
||||||
@@ -181,25 +223,21 @@ public class ConnectDialog extends JDialog {
|
|||||||
buttonPanel.add(connectBtn);
|
buttonPanel.add(connectBtn);
|
||||||
|
|
||||||
gbc.gridx = 0;
|
gbc.gridx = 0;
|
||||||
gbc.gridy = 8;
|
gbc.gridy = 9;
|
||||||
gbc.gridwidth = 2;
|
gbc.gridwidth = 2;
|
||||||
mainPanel.add(buttonPanel, gbc);
|
mainPanel.add(buttonPanel, gbc);
|
||||||
|
|
||||||
setContentPane(mainPanel);
|
setContentPane(mainPanel);
|
||||||
|
ThemeManager.applyThemeToWindow(this);
|
||||||
|
|
||||||
// Enter key triggers connect
|
// Enter key triggers connect
|
||||||
getRootPane().setDefaultButton(connectBtn);
|
getRootPane().setDefaultButton(connectBtn);
|
||||||
}
|
}
|
||||||
|
|
||||||
private JTextField createDarkField(int cols) {
|
private JTextField createField(int cols) {
|
||||||
JTextField field = new JTextField(cols);
|
JTextField field = new JTextField(cols);
|
||||||
field.setBackground(new Color(45, 45, 45));
|
|
||||||
field.setForeground(new Color(200, 200, 200));
|
|
||||||
field.setCaretColor(new Color(200, 200, 200));
|
|
||||||
field.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
|
field.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
|
||||||
field.setBorder(BorderFactory.createCompoundBorder(
|
ThemeManager.styleTextField(field);
|
||||||
BorderFactory.createLineBorder(new Color(60, 60, 60)),
|
|
||||||
BorderFactory.createEmptyBorder(4, 6, 4, 6)));
|
|
||||||
return field;
|
return field;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,6 +262,18 @@ public class ConnectDialog extends JDialog {
|
|||||||
result.setLuName(lu);
|
result.setLuName(lu);
|
||||||
}
|
}
|
||||||
result.setGraphicsMode((haus.nightmare.lib3270j.graphics.GraphicsMode) graphicsCombo.getSelectedItem());
|
result.setGraphicsMode((haus.nightmare.lib3270j.graphics.GraphicsMode) graphicsCombo.getSelectedItem());
|
||||||
|
|
||||||
|
Object cpSelection = codePageCombo.getSelectedItem();
|
||||||
|
if (cpSelection != null) {
|
||||||
|
String cpStr = cpSelection.toString().trim();
|
||||||
|
int dash = cpStr.indexOf(" -");
|
||||||
|
if (dash > 0) {
|
||||||
|
cpStr = cpStr.substring(0, dash).trim();
|
||||||
|
}
|
||||||
|
result.setCodePage(cpStr);
|
||||||
|
haus.nightmare.j3270.config.Settings.setCodePage(cpStr);
|
||||||
|
}
|
||||||
|
|
||||||
result.setUseTls(tlsCheckBox.isSelected());
|
result.setUseTls(tlsCheckBox.isSelected());
|
||||||
result.setTlsVerifyCert(verifyCertCheckBox.isSelected());
|
result.setTlsVerifyCert(verifyCertCheckBox.isSelected());
|
||||||
result.setTn3270eEnabled(tn3270eCheckBox.isSelected());
|
result.setTn3270eEnabled(tn3270eCheckBox.isSelected());
|
||||||
@@ -260,4 +310,17 @@ public class ConnectDialog extends JDialog {
|
|||||||
public void setInitialTn3270e(boolean tn3270e) {
|
public void setInitialTn3270e(boolean tn3270e) {
|
||||||
tn3270eCheckBox.setSelected(tn3270e);
|
tn3270eCheckBox.setSelected(tn3270e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setInitialCodePage(String cp) {
|
||||||
|
if (cp != null && codePageCombo != null) {
|
||||||
|
for (int i = 0; i < codePageCombo.getItemCount(); i++) {
|
||||||
|
String item = codePageCombo.getItemAt(i);
|
||||||
|
if (item.startsWith(cp + " ") || item.equals(cp)) {
|
||||||
|
codePageCombo.setSelectedIndex(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
codePageCombo.setSelectedItem(cp);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,216 @@
|
|||||||
|
package haus.nightmare.j3270.ui;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||||
|
import haus.nightmare.lib3270j.ecl.ECLField;
|
||||||
|
import haus.nightmare.lib3270j.ecl.ECLFieldList;
|
||||||
|
import haus.nightmare.lib3270j.listener.ScreenUpdateListener;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import javax.swing.border.LineBorder;
|
||||||
|
import javax.swing.table.DefaultTableCellRenderer;
|
||||||
|
import javax.swing.table.DefaultTableModel;
|
||||||
|
import javax.swing.table.JTableHeader;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.WindowAdapter;
|
||||||
|
import java.awt.event.WindowEvent;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Live Field Inspector dialog displaying all fields in the 3270 Presentation Space (ECLFieldList).
|
||||||
|
*/
|
||||||
|
public class FieldInspectorDialog extends JDialog implements ScreenUpdateListener {
|
||||||
|
|
||||||
|
private final Telnet3270Client client;
|
||||||
|
private final TerminalPanel terminalPanel;
|
||||||
|
|
||||||
|
private DefaultTableModel tableModel;
|
||||||
|
private JTable table;
|
||||||
|
private JLabel countLabel;
|
||||||
|
|
||||||
|
public FieldInspectorDialog(Frame parent, Telnet3270Client client, TerminalPanel terminalPanel) {
|
||||||
|
super(parent, "3270 Presentation Space Field Inspector", false);
|
||||||
|
this.client = client;
|
||||||
|
this.terminalPanel = terminalPanel;
|
||||||
|
|
||||||
|
buildUI();
|
||||||
|
setSize(850, 420);
|
||||||
|
setLocationRelativeTo(parent);
|
||||||
|
|
||||||
|
if (client != null) {
|
||||||
|
client.addScreenUpdateListener(this);
|
||||||
|
refreshFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
addWindowListener(new WindowAdapter() {
|
||||||
|
@Override
|
||||||
|
public void windowClosing(WindowEvent e) {
|
||||||
|
if (client != null) {
|
||||||
|
// Screen update listener removed on close
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildUI() {
|
||||||
|
JPanel mainPanel = new JPanel(new BorderLayout(8, 8));
|
||||||
|
mainPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
|
||||||
|
|
||||||
|
// Top info bar
|
||||||
|
JPanel topPanel = new JPanel(new BorderLayout());
|
||||||
|
topPanel.setOpaque(false);
|
||||||
|
countLabel = new JLabel("0 fields detected on screen");
|
||||||
|
countLabel.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 13));
|
||||||
|
topPanel.add(countLabel, BorderLayout.WEST);
|
||||||
|
|
||||||
|
JButton refreshBtn = new JButton("Refresh");
|
||||||
|
ThemeManager.styleButton(refreshBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
refreshBtn.addActionListener(e -> refreshFields());
|
||||||
|
topPanel.add(refreshBtn, BorderLayout.EAST);
|
||||||
|
mainPanel.add(topPanel, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
// Table
|
||||||
|
String[] columns = {
|
||||||
|
"#", "Pos", "Start", "End", "Len", "Prot", "MDT", "Num", "Hi-Int", "Hidden", "Pen-Sel", "Content Text"
|
||||||
|
};
|
||||||
|
|
||||||
|
tableModel = new DefaultTableModel(columns, 0) {
|
||||||
|
@Override
|
||||||
|
public boolean isCellEditable(int row, int column) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
table = new JTable(tableModel);
|
||||||
|
ThemeManager.styleTable(table);
|
||||||
|
table.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
|
||||||
|
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||||
|
table.setRowHeight(20);
|
||||||
|
|
||||||
|
JTableHeader header = table.getTableHeader();
|
||||||
|
header.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 12));
|
||||||
|
|
||||||
|
// Column widths
|
||||||
|
int[] widths = {35, 45, 60, 60, 40, 45, 45, 45, 50, 55, 60, 260};
|
||||||
|
for (int i = 0; i < widths.length && i < table.getColumnCount(); i++) {
|
||||||
|
table.getColumnModel().getColumn(i).setPreferredWidth(widths[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Center align boolean columns
|
||||||
|
DefaultTableCellRenderer centerRenderer = new DefaultTableCellRenderer();
|
||||||
|
centerRenderer.setHorizontalAlignment(SwingConstants.CENTER);
|
||||||
|
for (int i = 5; i <= 10; i++) {
|
||||||
|
table.getColumnModel().getColumn(i).setCellRenderer(centerRenderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.getSelectionModel().addListSelectionListener(e -> {
|
||||||
|
if (!e.getValueIsAdjusting() && table.getSelectedRow() >= 0) {
|
||||||
|
selectFieldOnTerminal(table.getSelectedRow());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
JScrollPane scrollPane = new JScrollPane(table);
|
||||||
|
ThemeManager.styleScrollPane(scrollPane);
|
||||||
|
mainPanel.add(scrollPane, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
// Bottom panel
|
||||||
|
JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 6, 0));
|
||||||
|
bottomPanel.setOpaque(false);
|
||||||
|
|
||||||
|
JButton jumpBtn = new JButton("Jump to Selected Field");
|
||||||
|
ThemeManager.styleButton(jumpBtn, ThemeManager.ButtonVariant.PRIMARY);
|
||||||
|
jumpBtn.addActionListener(e -> {
|
||||||
|
int row = table.getSelectedRow();
|
||||||
|
if (row >= 0) {
|
||||||
|
selectFieldOnTerminal(row);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
JButton closeBtn = new JButton("Close");
|
||||||
|
ThemeManager.styleButton(closeBtn, ThemeManager.ButtonVariant.CANCEL);
|
||||||
|
closeBtn.addActionListener(e -> dispose());
|
||||||
|
|
||||||
|
bottomPanel.add(jumpBtn);
|
||||||
|
bottomPanel.add(closeBtn);
|
||||||
|
mainPanel.add(bottomPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
setContentPane(mainPanel);
|
||||||
|
ThemeManager.applyThemeToWindow(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void refreshFields() {
|
||||||
|
if (client == null || !client.getConnectionState().isFullSession()) {
|
||||||
|
tableModel.setRowCount(0);
|
||||||
|
countLabel.setText("Not connected or screen unformatted");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ECLFieldList fieldList = client.getFieldList();
|
||||||
|
List<ECLField> fields = fieldList.getFields();
|
||||||
|
|
||||||
|
int selectedRow = table.getSelectedRow();
|
||||||
|
tableModel.setRowCount(0);
|
||||||
|
|
||||||
|
int cols = client.getScreenBuffer().getDisplayCols();
|
||||||
|
for (int i = 0; i < fields.size(); i++) {
|
||||||
|
ECLField f = fields.get(i);
|
||||||
|
int startRow = f.getStartRow() + 1;
|
||||||
|
int startCol = f.getStartCol() + 1;
|
||||||
|
int endRow = f.getEndRow() + 1;
|
||||||
|
int endCol = f.getEndCol() + 1;
|
||||||
|
|
||||||
|
String text = f.getText();
|
||||||
|
if (f.isHidden()) {
|
||||||
|
text = "••••••••";
|
||||||
|
}
|
||||||
|
|
||||||
|
tableModel.addRow(new Object[]{
|
||||||
|
(i + 1),
|
||||||
|
f.getStart(),
|
||||||
|
String.format("%02d/%02d", startRow, startCol),
|
||||||
|
String.format("%02d/%02d", endRow, endCol),
|
||||||
|
f.getLength(),
|
||||||
|
f.isProtected() ? "YES" : "-",
|
||||||
|
f.isModified() ? "YES" : "-",
|
||||||
|
f.isNumeric() ? "YES" : "-",
|
||||||
|
f.isHighIntensity() ? "YES" : "-",
|
||||||
|
f.isHidden() ? "YES" : "-",
|
||||||
|
f.isPenSelectable() ? "YES" : "-",
|
||||||
|
text
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
countLabel.setText(fields.size() + " fields detected on screen (" + client.getScreenBuffer().getDisplayRows() + "x" + cols + ")");
|
||||||
|
if (selectedRow >= 0 && selectedRow < tableModel.getRowCount()) {
|
||||||
|
table.setRowSelectionInterval(selectedRow, selectedRow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectFieldOnTerminal(int rowIndex) {
|
||||||
|
if (client == null || rowIndex < 0) return;
|
||||||
|
List<ECLField> fields = client.getFieldList().getFields();
|
||||||
|
if (rowIndex < fields.size()) {
|
||||||
|
ECLField f = fields.get(rowIndex);
|
||||||
|
client.getScreenBuffer().setCursorAddress(f.getDataStart());
|
||||||
|
if (terminalPanel != null) {
|
||||||
|
terminalPanel.setSelectionRange(f.getDataStart(), f.getEnd());
|
||||||
|
terminalPanel.repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== ScreenUpdateListener ==========
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScreenUpdated() {
|
||||||
|
SwingUtilities.invokeLater(this::refreshFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScreenSizeChanged(int rows, int cols) {
|
||||||
|
SwingUtilities.invokeLater(this::refreshFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSoundAlarm() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,218 @@
|
|||||||
|
package haus.nightmare.j3270.ui;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||||
|
import haus.nightmare.lib3270j.ecl.ECLConstants;
|
||||||
|
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.KeyAdapter;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find dialog for searching text in the 3270 Presentation Space using ECLPS.
|
||||||
|
*/
|
||||||
|
public class FindDialog extends JDialog {
|
||||||
|
|
||||||
|
private final Telnet3270Client client;
|
||||||
|
private final TerminalPanel terminalPanel;
|
||||||
|
|
||||||
|
private JTextField searchField;
|
||||||
|
private JCheckBox matchCaseCheck;
|
||||||
|
private JRadioButton forwardRadio;
|
||||||
|
private JRadioButton backwardRadio;
|
||||||
|
private JLabel statusLabel;
|
||||||
|
|
||||||
|
private static String lastSearchText = "";
|
||||||
|
private static boolean lastMatchCase = false;
|
||||||
|
private static boolean lastForward = true;
|
||||||
|
|
||||||
|
public FindDialog(Frame parent, Telnet3270Client client, TerminalPanel terminalPanel) {
|
||||||
|
super(parent, "Find on Screen", false);
|
||||||
|
this.client = client;
|
||||||
|
this.terminalPanel = terminalPanel;
|
||||||
|
|
||||||
|
buildUI();
|
||||||
|
pack();
|
||||||
|
setLocationRelativeTo(parent);
|
||||||
|
setResizable(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildUI() {
|
||||||
|
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
|
||||||
|
mainPanel.setBorder(new EmptyBorder(12, 14, 12, 14));
|
||||||
|
|
||||||
|
// Form
|
||||||
|
JPanel formPanel = new JPanel(new GridBagLayout());
|
||||||
|
formPanel.setOpaque(false);
|
||||||
|
GridBagConstraints gbc = new GridBagConstraints();
|
||||||
|
gbc.insets = new Insets(4, 4, 4, 4);
|
||||||
|
gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
|
||||||
|
Font labelFont = new Font(Font.SANS_SERIF, Font.PLAIN, 13);
|
||||||
|
|
||||||
|
// Search text
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = 0;
|
||||||
|
JLabel findLabel = new JLabel("Find what:");
|
||||||
|
findLabel.setFont(labelFont);
|
||||||
|
formPanel.add(findLabel, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 1;
|
||||||
|
gbc.weightx = 1.0;
|
||||||
|
searchField = new JTextField(20);
|
||||||
|
searchField.setText(lastSearchText);
|
||||||
|
searchField.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 13));
|
||||||
|
ThemeManager.styleTextField(searchField);
|
||||||
|
formPanel.add(searchField, gbc);
|
||||||
|
|
||||||
|
// Options
|
||||||
|
gbc.gridx = 1;
|
||||||
|
gbc.gridy = 1;
|
||||||
|
matchCaseCheck = new JCheckBox("Match case", lastMatchCase);
|
||||||
|
ThemeManager.styleCheckBox(matchCaseCheck);
|
||||||
|
matchCaseCheck.setFont(labelFont);
|
||||||
|
formPanel.add(matchCaseCheck, gbc);
|
||||||
|
|
||||||
|
// Direction
|
||||||
|
gbc.gridx = 1;
|
||||||
|
gbc.gridy = 2;
|
||||||
|
JPanel dirPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
|
||||||
|
dirPanel.setOpaque(false);
|
||||||
|
JLabel dirLabel = new JLabel("Direction: ");
|
||||||
|
dirLabel.setFont(labelFont);
|
||||||
|
forwardRadio = new JRadioButton("Down", lastForward);
|
||||||
|
backwardRadio = new JRadioButton("Up", !lastForward);
|
||||||
|
ThemeManager.styleRadioButton(forwardRadio);
|
||||||
|
ThemeManager.styleRadioButton(backwardRadio);
|
||||||
|
|
||||||
|
ButtonGroup bg = new ButtonGroup();
|
||||||
|
bg.add(forwardRadio);
|
||||||
|
bg.add(backwardRadio);
|
||||||
|
dirPanel.add(dirLabel);
|
||||||
|
dirPanel.add(forwardRadio);
|
||||||
|
dirPanel.add(Box.createHorizontalStrut(10));
|
||||||
|
dirPanel.add(backwardRadio);
|
||||||
|
formPanel.add(dirPanel, gbc);
|
||||||
|
|
||||||
|
// Status
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = 3;
|
||||||
|
gbc.gridwidth = 2;
|
||||||
|
statusLabel = new JLabel(" ");
|
||||||
|
statusLabel.setForeground(ThemeManager.getOiaFgAlert());
|
||||||
|
statusLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12));
|
||||||
|
formPanel.add(statusLabel, gbc);
|
||||||
|
|
||||||
|
mainPanel.add(formPanel, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 6, 0));
|
||||||
|
buttonPanel.setOpaque(false);
|
||||||
|
|
||||||
|
JButton findNextBtn = new JButton("Find Next");
|
||||||
|
ThemeManager.styleButton(findNextBtn, ThemeManager.ButtonVariant.PRIMARY);
|
||||||
|
findNextBtn.setFont(labelFont);
|
||||||
|
findNextBtn.addActionListener(e -> findNext());
|
||||||
|
|
||||||
|
JButton closeBtn = new JButton("Close");
|
||||||
|
ThemeManager.styleButton(closeBtn, ThemeManager.ButtonVariant.CANCEL);
|
||||||
|
closeBtn.setFont(labelFont);
|
||||||
|
closeBtn.addActionListener(e -> dispose());
|
||||||
|
|
||||||
|
buttonPanel.add(findNextBtn);
|
||||||
|
buttonPanel.add(closeBtn);
|
||||||
|
|
||||||
|
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
setContentPane(mainPanel);
|
||||||
|
ThemeManager.applyThemeToWindow(this);
|
||||||
|
getRootPane().setDefaultButton(findNextBtn);
|
||||||
|
|
||||||
|
searchField.addKeyListener(new KeyAdapter() {
|
||||||
|
@Override
|
||||||
|
public void keyPressed(KeyEvent e) {
|
||||||
|
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||||
|
findNext();
|
||||||
|
} else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
|
||||||
|
dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void findNext() {
|
||||||
|
find(forwardRadio.isSelected());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void findPrevious() {
|
||||||
|
find(!forwardRadio.isSelected());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void find(boolean forward) {
|
||||||
|
String target = searchField.getText();
|
||||||
|
if (target == null || target.isEmpty()) {
|
||||||
|
statusLabel.setText("Please enter search text.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client == null || !client.getConnectionState().isFullSession()) {
|
||||||
|
statusLabel.setText("Terminal is not connected.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastSearchText = target;
|
||||||
|
lastMatchCase = matchCaseCheck.isSelected();
|
||||||
|
lastForward = forwardRadio.isSelected();
|
||||||
|
|
||||||
|
ScreenBuffer sb = client.getScreenBuffer();
|
||||||
|
int curAddr = sb.getCursorAddress();
|
||||||
|
int cols = sb.getDisplayCols();
|
||||||
|
int curRow = curAddr / cols;
|
||||||
|
int curCol = curAddr % cols;
|
||||||
|
|
||||||
|
// Offset search start position past current character so repeat search moves to next occurrence
|
||||||
|
int startRow = curRow;
|
||||||
|
int startCol = curCol;
|
||||||
|
if (forward) {
|
||||||
|
startCol = (curCol + 1) % cols;
|
||||||
|
if (startCol == 0) startRow = (curRow + 1) % sb.getDisplayRows();
|
||||||
|
} else {
|
||||||
|
startCol = (curCol - 1 + cols) % cols;
|
||||||
|
if (startCol == cols - 1) startRow = (curRow - 1 + sb.getDisplayRows()) % sb.getDisplayRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
int dir = forward ? ECLConstants.SEARCH_FORWARD : ECLConstants.SEARCH_BACKWARD;
|
||||||
|
int foundPos = client.getPS().searchString(target, startRow, startCol, dir, !matchCaseCheck.isSelected());
|
||||||
|
|
||||||
|
if (foundPos >= 0) {
|
||||||
|
statusLabel.setText("Found at position " + (foundPos + 1) + " (" + (foundPos / cols + 1) + "/" + (foundPos % cols + 1) + ")");
|
||||||
|
statusLabel.setForeground(new Color(100, 255, 100));
|
||||||
|
sb.setCursorAddress(foundPos);
|
||||||
|
if (terminalPanel != null) {
|
||||||
|
terminalPanel.setSearchHighlight(foundPos, target.length());
|
||||||
|
terminalPanel.repaint();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
statusLabel.setText("Text not found: \"" + target + "\"");
|
||||||
|
statusLabel.setForeground(new Color(255, 120, 120));
|
||||||
|
if (terminalPanel != null) {
|
||||||
|
terminalPanel.clearSearchHighlight();
|
||||||
|
terminalPanel.repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getLastSearchText() {
|
||||||
|
return lastSearchText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean getLastMatchCase() {
|
||||||
|
return lastMatchCase;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean getLastForward() {
|
||||||
|
return lastForward;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,269 @@
|
|||||||
|
package haus.nightmare.j3270.ui;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||||
|
import haus.nightmare.lib3270j.ft.FTConfig;
|
||||||
|
import haus.nightmare.lib3270j.ft.dir.CMSDirectoryEntry;
|
||||||
|
import haus.nightmare.lib3270j.ft.dir.HostDirectoryEntry;
|
||||||
|
import haus.nightmare.lib3270j.ft.dir.TSODirectoryEntry;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import javax.swing.border.LineBorder;
|
||||||
|
import javax.swing.table.DefaultTableModel;
|
||||||
|
import javax.swing.table.JTableHeader;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Host Directory and Dataset Catalog browser dialog for TSO and VM/CMS environments.
|
||||||
|
*/
|
||||||
|
public class HostDirectoryDialog extends JDialog {
|
||||||
|
|
||||||
|
private final Telnet3270Client client;
|
||||||
|
private final FTConfig.HostType initialHostType;
|
||||||
|
|
||||||
|
private JComboBox<FTConfig.HostType> hostTypeCombo;
|
||||||
|
private JTextField queryField;
|
||||||
|
private JTable table;
|
||||||
|
private DefaultTableModel tableModel;
|
||||||
|
private JLabel statusLabel;
|
||||||
|
|
||||||
|
private String selectedHostFile = null;
|
||||||
|
private boolean confirmed = false;
|
||||||
|
|
||||||
|
private final List<HostDirectoryEntry> currentEntries = new ArrayList<>();
|
||||||
|
|
||||||
|
public HostDirectoryDialog(Dialog parent, Telnet3270Client client, FTConfig.HostType hostType, String initialQuery) {
|
||||||
|
super(parent, "Host Catalog & Directory Browser", true);
|
||||||
|
this.client = client;
|
||||||
|
this.initialHostType = hostType;
|
||||||
|
|
||||||
|
buildUI(initialQuery);
|
||||||
|
setSize(700, 420);
|
||||||
|
setLocationRelativeTo(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildUI(String initialQuery) {
|
||||||
|
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
|
||||||
|
mainPanel.setBorder(new EmptyBorder(12, 12, 12, 12));
|
||||||
|
|
||||||
|
Font font = new Font(Font.SANS_SERIF, Font.PLAIN, 13);
|
||||||
|
|
||||||
|
// Top Query bar
|
||||||
|
JPanel topPanel = new JPanel(new GridBagLayout());
|
||||||
|
topPanel.setOpaque(false);
|
||||||
|
GridBagConstraints gbc = new GridBagConstraints();
|
||||||
|
gbc.insets = new Insets(3, 4, 3, 4);
|
||||||
|
gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
|
||||||
|
JLabel envLabel = new JLabel("System:");
|
||||||
|
envLabel.setFont(font);
|
||||||
|
topPanel.add(envLabel, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 1;
|
||||||
|
hostTypeCombo = new JComboBox<>(new FTConfig.HostType[]{FTConfig.HostType.TSO, FTConfig.HostType.CMS});
|
||||||
|
if (initialHostType == FTConfig.HostType.CMS) hostTypeCombo.setSelectedItem(FTConfig.HostType.CMS);
|
||||||
|
else hostTypeCombo.setSelectedItem(FTConfig.HostType.TSO);
|
||||||
|
ThemeManager.styleComboBox(hostTypeCombo);
|
||||||
|
topPanel.add(hostTypeCombo, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 2;
|
||||||
|
JLabel queryLabel = new JLabel("Query Pattern / Text:");
|
||||||
|
queryLabel.setFont(font);
|
||||||
|
topPanel.add(queryLabel, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 3;
|
||||||
|
gbc.weightx = 1.0;
|
||||||
|
queryField = new JTextField(initialQuery != null ? initialQuery : "", 16);
|
||||||
|
queryField.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 13));
|
||||||
|
ThemeManager.styleTextField(queryField);
|
||||||
|
topPanel.add(queryField, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 4;
|
||||||
|
gbc.weightx = 0;
|
||||||
|
JButton parseBtn = new JButton("Query / Parse");
|
||||||
|
ThemeManager.styleButton(parseBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
parseBtn.setFont(font);
|
||||||
|
parseBtn.addActionListener(e -> runQuery());
|
||||||
|
topPanel.add(parseBtn, gbc);
|
||||||
|
|
||||||
|
mainPanel.add(topPanel, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
// Table
|
||||||
|
tableModel = new DefaultTableModel();
|
||||||
|
table = new JTable(tableModel);
|
||||||
|
ThemeManager.styleTable(table);
|
||||||
|
table.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
|
||||||
|
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||||
|
table.setRowHeight(20);
|
||||||
|
|
||||||
|
JTableHeader header = table.getTableHeader();
|
||||||
|
header.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 12));
|
||||||
|
|
||||||
|
table.addMouseListener(new MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(MouseEvent e) {
|
||||||
|
if (e.getClickCount() == 2 && table.getSelectedRow() >= 0) {
|
||||||
|
onConfirmSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
JScrollPane scrollPane = new JScrollPane(table);
|
||||||
|
ThemeManager.styleScrollPane(scrollPane);
|
||||||
|
mainPanel.add(scrollPane, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
// Bottom
|
||||||
|
JPanel bottomPanel = new JPanel(new BorderLayout(5, 5));
|
||||||
|
bottomPanel.setOpaque(false);
|
||||||
|
|
||||||
|
statusLabel = new JLabel("Enter a dataset pattern or parse active screen.");
|
||||||
|
statusLabel.setForeground(ThemeManager.getFgMuted());
|
||||||
|
statusLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12));
|
||||||
|
bottomPanel.add(statusLabel, BorderLayout.WEST);
|
||||||
|
|
||||||
|
JPanel btnPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 6, 0));
|
||||||
|
btnPanel.setOpaque(false);
|
||||||
|
|
||||||
|
JButton pasteScreenBtn = new JButton("Parse Current Screen");
|
||||||
|
ThemeManager.styleButton(pasteScreenBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
pasteScreenBtn.setFont(font);
|
||||||
|
pasteScreenBtn.addActionListener(e -> parseCurrentScreen());
|
||||||
|
|
||||||
|
JButton selectBtn = new JButton("Select Dataset");
|
||||||
|
ThemeManager.styleButton(selectBtn, ThemeManager.ButtonVariant.PRIMARY);
|
||||||
|
selectBtn.setFont(font);
|
||||||
|
selectBtn.addActionListener(e -> onConfirmSelection());
|
||||||
|
|
||||||
|
JButton cancelBtn = new JButton("Cancel");
|
||||||
|
ThemeManager.styleButton(cancelBtn, ThemeManager.ButtonVariant.CANCEL);
|
||||||
|
cancelBtn.setFont(font);
|
||||||
|
cancelBtn.addActionListener(e -> {
|
||||||
|
confirmed = false;
|
||||||
|
dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
btnPanel.add(pasteScreenBtn);
|
||||||
|
btnPanel.add(Box.createHorizontalStrut(10));
|
||||||
|
btnPanel.add(selectBtn);
|
||||||
|
btnPanel.add(cancelBtn);
|
||||||
|
|
||||||
|
bottomPanel.add(btnPanel, BorderLayout.EAST);
|
||||||
|
mainPanel.add(bottomPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
setContentPane(mainPanel);
|
||||||
|
ThemeManager.applyThemeToWindow(this);
|
||||||
|
|
||||||
|
// Initial setup
|
||||||
|
setupTableColumns();
|
||||||
|
if (initialQuery != null && !initialQuery.isEmpty()) {
|
||||||
|
runQuery();
|
||||||
|
} else {
|
||||||
|
parseCurrentScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupTableColumns() {
|
||||||
|
FTConfig.HostType hostType = (FTConfig.HostType) hostTypeCombo.getSelectedItem();
|
||||||
|
tableModel.setRowCount(0);
|
||||||
|
if (hostType == FTConfig.HostType.CMS) {
|
||||||
|
tableModel.setColumnIdentifiers(new Object[]{
|
||||||
|
"Filename", "Filetype", "Filemode", "Format", "Lrecl", "Records", "Date / Time"
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
tableModel.setColumnIdentifiers(new Object[]{
|
||||||
|
"Dataset Name", "Volume", "Recfm", "Lrecl", "Blksize", "Tracks", "Extents"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runQuery() {
|
||||||
|
setupTableColumns();
|
||||||
|
currentEntries.clear();
|
||||||
|
String query = queryField.getText().trim();
|
||||||
|
FTConfig.HostType hostType = (FTConfig.HostType) hostTypeCombo.getSelectedItem();
|
||||||
|
|
||||||
|
if (client != null && client.getXfer() != null) {
|
||||||
|
if (hostType == FTConfig.HostType.CMS) {
|
||||||
|
List<CMSDirectoryEntry> cmsEntries = client.getXfer().getCmsDirectory(query);
|
||||||
|
currentEntries.addAll(cmsEntries);
|
||||||
|
for (CMSDirectoryEntry entry : cmsEntries) {
|
||||||
|
tableModel.addRow(new Object[]{
|
||||||
|
entry.getFilename(), entry.getFiletype(), entry.getFilemode(),
|
||||||
|
entry.getRecfm(), entry.getLrecl(), entry.getNumRecords(), entry.getDate() + " " + entry.getTime()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
List<TSODirectoryEntry> tsoEntries = client.getXfer().getTsoDirectory(query);
|
||||||
|
currentEntries.addAll(tsoEntries);
|
||||||
|
for (TSODirectoryEntry entry : tsoEntries) {
|
||||||
|
tableModel.addRow(new Object[]{
|
||||||
|
entry.getDatasetName(), entry.getVolume(), entry.getRecfm(),
|
||||||
|
entry.getLrecl(), entry.getBlksize(), entry.getTracksAllocated(), entry.getExtents()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
statusLabel.setText(currentEntries.size() + " entries found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseCurrentScreen() {
|
||||||
|
if (client == null || !client.getConnectionState().isFullSession()) {
|
||||||
|
statusLabel.setText("No active session to parse.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setupTableColumns();
|
||||||
|
currentEntries.clear();
|
||||||
|
FTConfig.HostType hostType = (FTConfig.HostType) hostTypeCombo.getSelectedItem();
|
||||||
|
String screenText = client.getPS().getString(0, client.getPS().getSize());
|
||||||
|
|
||||||
|
if (hostType == FTConfig.HostType.CMS) {
|
||||||
|
List<CMSDirectoryEntry> cmsEntries = client.getXfer().getCmsDirectory(screenText);
|
||||||
|
currentEntries.addAll(cmsEntries);
|
||||||
|
for (CMSDirectoryEntry entry : cmsEntries) {
|
||||||
|
tableModel.addRow(new Object[]{
|
||||||
|
entry.getFilename(), entry.getFiletype(), entry.getFilemode(),
|
||||||
|
entry.getRecfm(), entry.getLrecl(), entry.getNumRecords(), entry.getDate() + " " + entry.getTime()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
List<TSODirectoryEntry> tsoEntries = client.getXfer().getTsoDirectory(screenText);
|
||||||
|
currentEntries.addAll(tsoEntries);
|
||||||
|
for (TSODirectoryEntry entry : tsoEntries) {
|
||||||
|
tableModel.addRow(new Object[]{
|
||||||
|
entry.getDatasetName(), entry.getVolume(), entry.getRecfm(),
|
||||||
|
entry.getLrecl(), entry.getBlksize(), entry.getTracksAllocated(), entry.getExtents()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
statusLabel.setText("Parsed " + currentEntries.size() + " entries from presentation space.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onConfirmSelection() {
|
||||||
|
int row = table.getSelectedRow();
|
||||||
|
if (row < 0 || row >= currentEntries.size()) {
|
||||||
|
JOptionPane.showMessageDialog(this, "Please select an entry from the table.",
|
||||||
|
"No Selection", JOptionPane.WARNING_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HostDirectoryEntry entry = currentEntries.get(row);
|
||||||
|
selectedHostFile = entry.getName();
|
||||||
|
confirmed = true;
|
||||||
|
dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConfirmed() {
|
||||||
|
return confirmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSelectedHostFile() {
|
||||||
|
return selectedHostFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,452 @@
|
|||||||
|
package haus.nightmare.j3270.ui;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.printer.*;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import javax.swing.border.LineBorder;
|
||||||
|
import javax.swing.border.TitledBorder;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.print.PrinterJob;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM 3287 / 3286 TN3270E Printer Session Manager and Spool Viewer.
|
||||||
|
* Integrates directly with lib3270j.printer.Telnet3270EP and PD3270.
|
||||||
|
*/
|
||||||
|
public class PrinterSessionDialog extends JDialog implements PrintSessionListener {
|
||||||
|
|
||||||
|
private final Frame parent;
|
||||||
|
private PrinterConfig config;
|
||||||
|
private Telnet3270EP printerSession;
|
||||||
|
|
||||||
|
// Configuration Fields
|
||||||
|
private JTextField hostField;
|
||||||
|
private JTextField portField;
|
||||||
|
private JTextField printerLuField;
|
||||||
|
private JTextField displayLuField;
|
||||||
|
private JComboBox<String> codePageCombo;
|
||||||
|
private JCheckBox tlsCheck;
|
||||||
|
private JCheckBox verifyCertCheck;
|
||||||
|
private JComboBox<PrinterConfig.DestinationType> destinationCombo;
|
||||||
|
private JTextField targetField;
|
||||||
|
|
||||||
|
// Status Fields
|
||||||
|
private JLabel statusLabel;
|
||||||
|
private JLabel sessionTypeLabel;
|
||||||
|
private JLabel pagesLabel;
|
||||||
|
private JLabel bytesLabel;
|
||||||
|
private JButton connectBtn;
|
||||||
|
private JButton disconnectBtn;
|
||||||
|
|
||||||
|
// Spool Display
|
||||||
|
private JTextArea spoolArea;
|
||||||
|
|
||||||
|
public PrinterSessionDialog(Frame parent, String defaultHost, int defaultPort, boolean defaultTls) {
|
||||||
|
super(parent, "IBM 3287 Printer Session Manager", false);
|
||||||
|
this.parent = parent;
|
||||||
|
|
||||||
|
config = new PrinterConfig(defaultHost, defaultPort);
|
||||||
|
config.setUseTls(defaultTls);
|
||||||
|
config.setDestinationType(PrinterConfig.DestinationType.MEMORY);
|
||||||
|
|
||||||
|
buildUI();
|
||||||
|
setSize(780, 560);
|
||||||
|
setLocationRelativeTo(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildUI() {
|
||||||
|
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
|
||||||
|
mainPanel.setBorder(new EmptyBorder(12, 14, 12, 14));
|
||||||
|
|
||||||
|
// Top: Configuration Panel
|
||||||
|
JPanel topPanel = new JPanel(new GridBagLayout());
|
||||||
|
topPanel.setOpaque(false);
|
||||||
|
topPanel.setBorder(ThemeManager.createTitledBorder("Printer Session Configuration"));
|
||||||
|
|
||||||
|
GridBagConstraints gbc = new GridBagConstraints();
|
||||||
|
gbc.insets = new Insets(3, 4, 3, 4);
|
||||||
|
gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
|
||||||
|
Font labelFont = new Font(Font.SANS_SERIF, Font.PLAIN, 12);
|
||||||
|
|
||||||
|
// Host / Port
|
||||||
|
gbc.gridx = 0; gbc.gridy = 0;
|
||||||
|
JLabel hLbl = new JLabel("Host:");
|
||||||
|
hLbl.setFont(labelFont);
|
||||||
|
topPanel.add(hLbl, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 1; gbc.weightx = 1.0;
|
||||||
|
hostField = createField(config.getHost() != null ? config.getHost() : "localhost", 14);
|
||||||
|
topPanel.add(hostField, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 2; gbc.weightx = 0;
|
||||||
|
JLabel pLbl = new JLabel("Port:");
|
||||||
|
pLbl.setFont(labelFont);
|
||||||
|
topPanel.add(pLbl, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 3; gbc.weightx = 0.5;
|
||||||
|
portField = createField(String.valueOf(config.getPort()), 5);
|
||||||
|
topPanel.add(portField, gbc);
|
||||||
|
|
||||||
|
// Printer LU / Display LU
|
||||||
|
gbc.gridx = 0; gbc.gridy = 1; gbc.weightx = 0;
|
||||||
|
JLabel pluLbl = new JLabel("Printer LU:");
|
||||||
|
pluLbl.setFont(labelFont);
|
||||||
|
topPanel.add(pluLbl, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 1; gbc.weightx = 1.0;
|
||||||
|
printerLuField = createField("", 10);
|
||||||
|
topPanel.add(printerLuField, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 2; gbc.weightx = 0;
|
||||||
|
JLabel assocLbl = new JLabel("Assoc LU:");
|
||||||
|
assocLbl.setFont(labelFont);
|
||||||
|
topPanel.add(assocLbl, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 3; gbc.weightx = 0.5;
|
||||||
|
displayLuField = createField("", 10);
|
||||||
|
topPanel.add(displayLuField, gbc);
|
||||||
|
|
||||||
|
// CodePage & TLS
|
||||||
|
gbc.gridx = 0; gbc.gridy = 2; gbc.weightx = 0;
|
||||||
|
JLabel cpLbl = new JLabel("CodePage:");
|
||||||
|
cpLbl.setFont(labelFont);
|
||||||
|
topPanel.add(cpLbl, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 1; gbc.weightx = 1.0;
|
||||||
|
codePageCombo = new JComboBox<>(new String[]{"037", "1047", "500", "273", "277", "278", "280", "284", "285", "297", "870", "1140"});
|
||||||
|
ThemeManager.styleComboBox(codePageCombo);
|
||||||
|
topPanel.add(codePageCombo, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 2; gbc.gridwidth = 2;
|
||||||
|
JPanel tlsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 4, 0));
|
||||||
|
tlsPanel.setOpaque(false);
|
||||||
|
tlsCheck = new JCheckBox("TLS/SSL", config.isUseTls());
|
||||||
|
ThemeManager.styleCheckBox(tlsCheck);
|
||||||
|
verifyCertCheck = new JCheckBox("Verify Cert", config.isTlsVerifyCert());
|
||||||
|
ThemeManager.styleCheckBox(verifyCertCheck);
|
||||||
|
tlsPanel.add(tlsCheck);
|
||||||
|
tlsPanel.add(verifyCertCheck);
|
||||||
|
topPanel.add(tlsPanel, gbc);
|
||||||
|
|
||||||
|
// Destination Type & Target
|
||||||
|
gbc.gridx = 0; gbc.gridy = 3; gbc.gridwidth = 1;
|
||||||
|
JLabel destLbl = new JLabel("Destination:");
|
||||||
|
destLbl.setFont(labelFont);
|
||||||
|
topPanel.add(destLbl, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 1;
|
||||||
|
destinationCombo = new JComboBox<>(PrinterConfig.DestinationType.values());
|
||||||
|
destinationCombo.setSelectedItem(PrinterConfig.DestinationType.MEMORY);
|
||||||
|
ThemeManager.styleComboBox(destinationCombo);
|
||||||
|
topPanel.add(destinationCombo, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 2;
|
||||||
|
JLabel tgtLbl = new JLabel("Target Path:");
|
||||||
|
tgtLbl.setFont(labelFont);
|
||||||
|
topPanel.add(tgtLbl, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 3;
|
||||||
|
targetField = createField("printer_output.txt", 12);
|
||||||
|
topPanel.add(targetField, gbc);
|
||||||
|
|
||||||
|
// Connect / Disconnect Buttons
|
||||||
|
gbc.gridx = 0; gbc.gridy = 4; gbc.gridwidth = 4;
|
||||||
|
JPanel connBtnPan = new JPanel(new FlowLayout(FlowLayout.RIGHT, 6, 0));
|
||||||
|
connBtnPan.setOpaque(false);
|
||||||
|
|
||||||
|
connectBtn = new JButton("Start Printer Session");
|
||||||
|
ThemeManager.styleButton(connectBtn, ThemeManager.ButtonVariant.PRIMARY);
|
||||||
|
connectBtn.addActionListener(e -> startPrinterSession());
|
||||||
|
|
||||||
|
disconnectBtn = new JButton("Stop Session");
|
||||||
|
ThemeManager.styleButton(disconnectBtn, ThemeManager.ButtonVariant.DANGER);
|
||||||
|
disconnectBtn.setEnabled(false);
|
||||||
|
disconnectBtn.addActionListener(e -> stopPrinterSession());
|
||||||
|
|
||||||
|
connBtnPan.add(connectBtn);
|
||||||
|
connBtnPan.add(disconnectBtn);
|
||||||
|
topPanel.add(connBtnPan, gbc);
|
||||||
|
|
||||||
|
mainPanel.add(topPanel, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
// Center: Spool Area & Status
|
||||||
|
JPanel centerPanel = new JPanel(new BorderLayout(6, 6));
|
||||||
|
centerPanel.setOpaque(false);
|
||||||
|
centerPanel.setBorder(ThemeManager.createTitledBorder("Printer Spool & Status"));
|
||||||
|
|
||||||
|
// Status Header
|
||||||
|
JPanel statusHeader = new JPanel(new GridLayout(1, 4, 10, 0));
|
||||||
|
statusHeader.setOpaque(false);
|
||||||
|
statusHeader.setBorder(new EmptyBorder(0, 4, 4, 4));
|
||||||
|
|
||||||
|
statusLabel = new JLabel("Status: Disconnected");
|
||||||
|
statusLabel.setForeground(ThemeManager.getFgMuted());
|
||||||
|
statusLabel.setFont(labelFont);
|
||||||
|
|
||||||
|
sessionTypeLabel = new JLabel("Session: -");
|
||||||
|
sessionTypeLabel.setFont(labelFont);
|
||||||
|
|
||||||
|
pagesLabel = new JLabel("Pages: 0");
|
||||||
|
pagesLabel.setFont(labelFont);
|
||||||
|
|
||||||
|
bytesLabel = new JLabel("Bytes: 0");
|
||||||
|
bytesLabel.setFont(labelFont);
|
||||||
|
|
||||||
|
statusHeader.add(statusLabel);
|
||||||
|
statusHeader.add(sessionTypeLabel);
|
||||||
|
statusHeader.add(pagesLabel);
|
||||||
|
statusHeader.add(bytesLabel);
|
||||||
|
centerPanel.add(statusHeader, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
// Spool text area
|
||||||
|
spoolArea = new JTextArea();
|
||||||
|
spoolArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
|
||||||
|
spoolArea.setEditable(false);
|
||||||
|
ThemeManager.styleTextArea(spoolArea);
|
||||||
|
|
||||||
|
JScrollPane spoolScroll = new JScrollPane(spoolArea);
|
||||||
|
ThemeManager.styleScrollPane(spoolScroll);
|
||||||
|
centerPanel.add(spoolScroll, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
mainPanel.add(centerPanel, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
// Bottom: Spool controls
|
||||||
|
JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 6, 0));
|
||||||
|
bottomPanel.setOpaque(false);
|
||||||
|
|
||||||
|
JButton clearSpoolBtn = new JButton("Clear Spool");
|
||||||
|
ThemeManager.styleButton(clearSpoolBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
clearSpoolBtn.addActionListener(e -> {
|
||||||
|
spoolArea.setText("");
|
||||||
|
if (printerSession != null && printerSession.getPD() != null) {
|
||||||
|
printerSession.getPD().resetCapture();
|
||||||
|
}
|
||||||
|
pagesLabel.setText("Pages: 0");
|
||||||
|
bytesLabel.setText("Bytes: 0");
|
||||||
|
});
|
||||||
|
|
||||||
|
JButton saveSpoolBtn = new JButton("Save Spool As...");
|
||||||
|
ThemeManager.styleButton(saveSpoolBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
saveSpoolBtn.addActionListener(e -> saveSpool());
|
||||||
|
|
||||||
|
JButton printSpoolBtn = new JButton("Print Spool...");
|
||||||
|
ThemeManager.styleButton(printSpoolBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
printSpoolBtn.addActionListener(e -> printSpool());
|
||||||
|
|
||||||
|
JButton closeBtn = new JButton("Close");
|
||||||
|
ThemeManager.styleButton(closeBtn, ThemeManager.ButtonVariant.CANCEL);
|
||||||
|
closeBtn.addActionListener(e -> dispose());
|
||||||
|
|
||||||
|
bottomPanel.add(clearSpoolBtn);
|
||||||
|
bottomPanel.add(saveSpoolBtn);
|
||||||
|
bottomPanel.add(printSpoolBtn);
|
||||||
|
bottomPanel.add(Box.createHorizontalStrut(10));
|
||||||
|
bottomPanel.add(closeBtn);
|
||||||
|
|
||||||
|
mainPanel.add(bottomPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
setContentPane(mainPanel);
|
||||||
|
ThemeManager.applyThemeToWindow(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JTextField createField(String text, int cols) {
|
||||||
|
JTextField tf = new JTextField(text, cols);
|
||||||
|
tf.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
|
||||||
|
ThemeManager.styleTextField(tf);
|
||||||
|
return tf;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startPrinterSession() {
|
||||||
|
String host = hostField.getText().trim();
|
||||||
|
if (host.isEmpty()) {
|
||||||
|
hostField.requestFocus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int port = 23;
|
||||||
|
try {
|
||||||
|
port = Integer.parseInt(portField.getText().trim());
|
||||||
|
} catch (NumberFormatException ignored) {}
|
||||||
|
|
||||||
|
config.setHost(host);
|
||||||
|
config.setPort(port);
|
||||||
|
config.setUseTls(tlsCheck.isSelected());
|
||||||
|
config.setTlsVerifyCert(verifyCertCheck.isSelected());
|
||||||
|
config.setCodePage((String) codePageCombo.getSelectedItem());
|
||||||
|
|
||||||
|
String plu = printerLuField.getText().trim();
|
||||||
|
config.setPrinterLuName(plu.isEmpty() ? null : plu);
|
||||||
|
|
||||||
|
String dlu = displayLuField.getText().trim();
|
||||||
|
config.setAssociatedDisplayLuName(dlu.isEmpty() ? null : dlu);
|
||||||
|
|
||||||
|
config.setDestinationType((PrinterConfig.DestinationType) destinationCombo.getSelectedItem());
|
||||||
|
config.setDestinationTarget(targetField.getText().trim());
|
||||||
|
|
||||||
|
if (printerSession != null) {
|
||||||
|
printerSession.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
printerSession = new Telnet3270EP(config);
|
||||||
|
printerSession.addPrintListener(this);
|
||||||
|
|
||||||
|
connectBtn.setEnabled(false);
|
||||||
|
disconnectBtn.setEnabled(true);
|
||||||
|
statusLabel.setText("Status: Connecting...");
|
||||||
|
statusLabel.setForeground(new Color(255, 200, 80));
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
boolean success = printerSession.open();
|
||||||
|
if (!success) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
connectBtn.setEnabled(true);
|
||||||
|
disconnectBtn.setEnabled(false);
|
||||||
|
statusLabel.setText("Status: Connection Failed");
|
||||||
|
statusLabel.setForeground(new Color(255, 80, 80));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, "PrinterSession-Thread").start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopPrinterSession() {
|
||||||
|
if (printerSession != null) {
|
||||||
|
printerSession.close();
|
||||||
|
printerSession = null;
|
||||||
|
}
|
||||||
|
connectBtn.setEnabled(true);
|
||||||
|
disconnectBtn.setEnabled(false);
|
||||||
|
statusLabel.setText("Status: Disconnected");
|
||||||
|
statusLabel.setForeground(new Color(180, 180, 180));
|
||||||
|
sessionTypeLabel.setText("Session: -");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveSpool() {
|
||||||
|
JFileChooser fc = new JFileChooser();
|
||||||
|
fc.setDialogTitle("Save Printer Spool Output");
|
||||||
|
fc.setSelectedFile(new File("spool_output.txt"));
|
||||||
|
if (fc.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
|
||||||
|
try (FileOutputStream fos = new FileOutputStream(fc.getSelectedFile())) {
|
||||||
|
fos.write(spoolArea.getText().getBytes(StandardCharsets.UTF_8));
|
||||||
|
JOptionPane.showMessageDialog(this, "Spool saved successfully to " + fc.getSelectedFile().getName(),
|
||||||
|
"Saved", JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
} catch (IOException e) {
|
||||||
|
JOptionPane.showMessageDialog(this, "Save error: " + e.getMessage(),
|
||||||
|
"Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void printSpool() {
|
||||||
|
String text = spoolArea.getText();
|
||||||
|
if (text.isEmpty()) {
|
||||||
|
JOptionPane.showMessageDialog(this, "Spool buffer is empty.", "Nothing to Print", JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PrinterJob job = PrinterJob.getPrinterJob();
|
||||||
|
job.setJobName("j3270 3287 Print Spool");
|
||||||
|
if (job.printDialog()) {
|
||||||
|
try {
|
||||||
|
spoolArea.print();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
JOptionPane.showMessageDialog(this, "Print error: " + ex.getMessage(), "Print Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== PrintSessionListener ==========
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPrintJobStarted(PrintSessionEvent event) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
statusLabel.setText("Status: Printing...");
|
||||||
|
statusLabel.setForeground(new Color(50, 205, 50));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPrintJobData(PrintSessionEvent event) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
if (printerSession != null && printerSession.getPD() != null) {
|
||||||
|
spoolArea.setText(printerSession.getPD().getCapturedText());
|
||||||
|
spoolArea.setCaretPosition(spoolArea.getDocument().getLength());
|
||||||
|
pagesLabel.setText("Pages: " + printerSession.getPD().getPageCount());
|
||||||
|
bytesLabel.setText("Bytes: " + printerSession.getPD().getByteCount());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPrintJobPageComplete(PrintSessionEvent event) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
if (printerSession != null && printerSession.getPD() != null) {
|
||||||
|
spoolArea.setText(printerSession.getPD().getCapturedText());
|
||||||
|
pagesLabel.setText("Pages: " + printerSession.getPD().getPageCount());
|
||||||
|
bytesLabel.setText("Bytes: " + printerSession.getPD().getByteCount());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPrintJobComplete(PrintSessionEvent event) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
if (printerSession != null && printerSession.getPD() != null) {
|
||||||
|
spoolArea.setText(printerSession.getPD().getCapturedText());
|
||||||
|
pagesLabel.setText("Pages: " + printerSession.getPD().getPageCount());
|
||||||
|
bytesLabel.setText("Bytes: " + printerSession.getPD().getByteCount());
|
||||||
|
}
|
||||||
|
statusLabel.setText("Status: In Session (Idle)");
|
||||||
|
statusLabel.setForeground(new Color(50, 205, 50));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPrinterStatusChanged(PrintSessionEvent event) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
int code = event.getStatusCode();
|
||||||
|
if (code == PrinterConstants.STATUS_CONNECTED || code == PrinterConstants.STATUS_PRINTER_READY || code == PrinterConstants.STATUS_PRINTING || code == PrinterConstants.STATUS_JOB_COMPLETE || code == PrinterConstants.STATUS_PAGE_COMPLETE) {
|
||||||
|
String lu = printerSession != null ? printerSession.getAssignedLuName() : null;
|
||||||
|
statusLabel.setText("Status: Connected" + (lu != null ? " (" + lu + ")" : ""));
|
||||||
|
statusLabel.setForeground(new Color(50, 205, 50));
|
||||||
|
connectBtn.setEnabled(false);
|
||||||
|
disconnectBtn.setEnabled(true);
|
||||||
|
|
||||||
|
if (printerSession != null) {
|
||||||
|
short luType = printerSession.getActiveLuType();
|
||||||
|
if (luType == PrinterConstants.LU_TYPE_1_SCS) {
|
||||||
|
sessionTypeLabel.setText("Session: LU1 (SCS)");
|
||||||
|
} else if (luType == PrinterConstants.LU_TYPE_3_DS) {
|
||||||
|
sessionTypeLabel.setText("Session: LU3 (3270 DS)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (code == PrinterConstants.STATUS_DISCONNECTED) {
|
||||||
|
statusLabel.setText("Status: Disconnected");
|
||||||
|
statusLabel.setForeground(new Color(180, 180, 180));
|
||||||
|
connectBtn.setEnabled(true);
|
||||||
|
disconnectBtn.setEnabled(false);
|
||||||
|
sessionTypeLabel.setText("Session: -");
|
||||||
|
} else {
|
||||||
|
statusLabel.setText("Status: " + event.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPrinterError(PrintSessionEvent event) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
statusLabel.setText("Status: Error (" + event.getMessage() + ")");
|
||||||
|
statusLabel.setForeground(new Color(255, 80, 80));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
stopPrinterSession();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
package haus.nightmare.j3270.ui;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||||
|
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
|
||||||
|
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||||
|
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility for exporting the 3270 Presentation Space to Plain Text, HTML, or PNG image.
|
||||||
|
*/
|
||||||
|
public class ScreenExporter {
|
||||||
|
|
||||||
|
public static void showExportDialog(Frame parent, Telnet3270Client client, TerminalPanel panel) {
|
||||||
|
if (client == null) {
|
||||||
|
JOptionPane.showMessageDialog(parent, "Connect to a host before exporting the screen.",
|
||||||
|
"Not Connected", JOptionPane.WARNING_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JFileChooser fc = new JFileChooser();
|
||||||
|
fc.setDialogTitle("Save Screen As...");
|
||||||
|
fc.addChoosableFileFilter(new FileNameExtensionFilter("PNG Image (*.png)", "png"));
|
||||||
|
fc.addChoosableFileFilter(new FileNameExtensionFilter("HTML Document (*.html)", "html", "htm"));
|
||||||
|
FileNameExtensionFilter txtFilter = new FileNameExtensionFilter("Plain Text (*.txt)", "txt");
|
||||||
|
fc.addChoosableFileFilter(txtFilter);
|
||||||
|
fc.setFileFilter(txtFilter);
|
||||||
|
fc.setSelectedFile(new File("screen_capture.txt"));
|
||||||
|
|
||||||
|
int result = fc.showSaveDialog(parent);
|
||||||
|
if (result == JFileChooser.APPROVE_OPTION) {
|
||||||
|
File file = fc.getSelectedFile();
|
||||||
|
String name = file.getName().toLowerCase();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (name.endsWith(".png") || (fc.getFileFilter() != null && fc.getFileFilter().getDescription().contains("PNG"))) {
|
||||||
|
if (!name.endsWith(".png")) file = new File(file.getAbsolutePath() + ".png");
|
||||||
|
exportToPng(panel, file);
|
||||||
|
} else if (name.endsWith(".html") || name.endsWith(".htm") || (fc.getFileFilter() != null && fc.getFileFilter().getDescription().contains("HTML"))) {
|
||||||
|
if (!name.endsWith(".html") && !name.endsWith(".htm")) file = new File(file.getAbsolutePath() + ".html");
|
||||||
|
exportToHtml(client, file);
|
||||||
|
} else {
|
||||||
|
if (!name.endsWith(".txt")) file = new File(file.getAbsolutePath() + ".txt");
|
||||||
|
exportToText(client, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
JOptionPane.showMessageDialog(parent, "Screen saved successfully to:\n" + file.getAbsolutePath(),
|
||||||
|
"Export Successful", JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
JOptionPane.showMessageDialog(parent, "Failed to export screen: " + ex.getMessage(),
|
||||||
|
"Export Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void exportToText(Telnet3270Client client, File file) throws IOException {
|
||||||
|
ScreenBuffer sb = client.getScreenBuffer();
|
||||||
|
int rows = sb.getDisplayRows();
|
||||||
|
int cols = sb.getDisplayCols();
|
||||||
|
|
||||||
|
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) {
|
||||||
|
for (int r = 0; r < rows; r++) {
|
||||||
|
StringBuilder line = new StringBuilder();
|
||||||
|
for (int c = 0; c < cols; c++) {
|
||||||
|
ExtendedAttribute ea = sb.getCell(r * cols + c);
|
||||||
|
char ch = ea.ucs4;
|
||||||
|
if (ea.isFieldAttribute() || ch <= 0x20 || ch == 0xFF) {
|
||||||
|
line.append(' ');
|
||||||
|
} else {
|
||||||
|
line.append(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Strip trailing spaces for clean text output
|
||||||
|
int lastNonSpace = line.length() - 1;
|
||||||
|
while (lastNonSpace >= 0 && line.charAt(lastNonSpace) == ' ') {
|
||||||
|
lastNonSpace--;
|
||||||
|
}
|
||||||
|
writer.write(line.substring(0, lastNonSpace + 1));
|
||||||
|
writer.newLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void exportToHtml(Telnet3270Client client, File file) throws IOException {
|
||||||
|
ScreenBuffer sb = client.getScreenBuffer();
|
||||||
|
int rows = sb.getDisplayRows();
|
||||||
|
int cols = sb.getDisplayCols();
|
||||||
|
|
||||||
|
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) {
|
||||||
|
writer.write("<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"utf-8\">\n<title>3270 Screen Capture</title>\n");
|
||||||
|
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-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(".bold { font-weight: bold; }\n");
|
||||||
|
writer.write(".underline { text-decoration: underline; }\n");
|
||||||
|
writer.write("</style>\n</head>\n<body>\n<div class=\"screen\"><pre>");
|
||||||
|
|
||||||
|
byte currentFA = 0;
|
||||||
|
ExtendedAttribute currentFieldEa = null;
|
||||||
|
|
||||||
|
for (int r = 0; r < rows; r++) {
|
||||||
|
String currentStyle = null;
|
||||||
|
StringBuilder currentRun = new StringBuilder();
|
||||||
|
|
||||||
|
for (int c = 0; c < cols; c++) {
|
||||||
|
int baddr = r * cols + c;
|
||||||
|
ExtendedAttribute ea = sb.getCell(baddr);
|
||||||
|
|
||||||
|
if (ea.isFieldAttribute()) {
|
||||||
|
currentFA = ea.fa;
|
||||||
|
currentFieldEa = ea;
|
||||||
|
if (currentRun.length() > 0) {
|
||||||
|
writeHtmlSpan(writer, currentStyle, currentRun.toString());
|
||||||
|
currentRun.setLength(0);
|
||||||
|
currentStyle = null;
|
||||||
|
}
|
||||||
|
writer.write(' ');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (faIsZero(currentFA & 0xFF)) {
|
||||||
|
if (currentRun.length() > 0) {
|
||||||
|
writeHtmlSpan(writer, currentStyle, currentRun.toString());
|
||||||
|
currentRun.setLength(0);
|
||||||
|
currentStyle = null;
|
||||||
|
}
|
||||||
|
writer.write(' ');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char ch = ea.ucs4;
|
||||||
|
if (ch == 0 || ch == 0xFF) {
|
||||||
|
if (currentRun.length() > 0) {
|
||||||
|
writeHtmlSpan(writer, currentStyle, currentRun.toString());
|
||||||
|
currentRun.setLength(0);
|
||||||
|
currentStyle = null;
|
||||||
|
}
|
||||||
|
writer.write(' ');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String colorClass = getHtmlColorClass(ea, currentFieldEa, currentFA);
|
||||||
|
boolean bold = faIsHigh(currentFA & 0xFF) || (ea.gr & GR_INTENSIFY) != 0;
|
||||||
|
boolean underline = (ea.gr & GR_UNDERLINE) != 0;
|
||||||
|
|
||||||
|
StringBuilder css = new StringBuilder();
|
||||||
|
if (!colorClass.isEmpty()) css.append(colorClass).append(" ");
|
||||||
|
if (bold) css.append("bold ");
|
||||||
|
if (underline) css.append("underline ");
|
||||||
|
String style = css.toString().trim();
|
||||||
|
|
||||||
|
if (currentStyle == null || !currentStyle.equals(style)) {
|
||||||
|
if (currentRun.length() > 0) {
|
||||||
|
writeHtmlSpan(writer, currentStyle, currentRun.toString());
|
||||||
|
currentRun.setLength(0);
|
||||||
|
}
|
||||||
|
currentStyle = style;
|
||||||
|
}
|
||||||
|
currentRun.append(escapeHtml(ch));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentRun.length() > 0) {
|
||||||
|
writeHtmlSpan(writer, currentStyle, currentRun.toString());
|
||||||
|
}
|
||||||
|
writer.newLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.write("</pre></div>\n</body>\n</html>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeHtmlSpan(BufferedWriter writer, String style, String content) throws IOException {
|
||||||
|
if (style != null && !style.isEmpty()) {
|
||||||
|
writer.write("<span class=\"" + style + "\">" + content + "</span>");
|
||||||
|
} else {
|
||||||
|
writer.write(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getHtmlColorClass(ExtendedAttribute ea, ExtendedAttribute currentFieldEa, byte currentFA) {
|
||||||
|
int fg = ea.fg != 0 ? (ea.fg & 0xFF) : (currentFieldEa != null && currentFieldEa.fg != 0 ? (currentFieldEa.fg & 0xFF) : 0);
|
||||||
|
if (fg >= 0xf0 && fg <= 0xff) {
|
||||||
|
switch (fg - 0xf0) {
|
||||||
|
case 1: return "c-blue";
|
||||||
|
case 2: return "c-red";
|
||||||
|
case 3: return "c-pink";
|
||||||
|
case 4: return "c-green";
|
||||||
|
case 5: return "c-turq";
|
||||||
|
case 6: return "c-yellow";
|
||||||
|
case 7: return "c-white";
|
||||||
|
case 8: return "c-black";
|
||||||
|
case 9: return "c-blue";
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (faIsProtected(currentFA & 0xFF)) {
|
||||||
|
return faIsHigh(currentFA & 0xFF) ? "c-white" : "c-blue";
|
||||||
|
}
|
||||||
|
return faIsHigh(currentFA & 0xFF) ? "c-red" : "c-green";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String escapeHtml(char ch) {
|
||||||
|
switch (ch) {
|
||||||
|
case '<': return "<";
|
||||||
|
case '>': return ">";
|
||||||
|
case '&': return "&";
|
||||||
|
case '"': return """;
|
||||||
|
default: return String.valueOf(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void exportToPng(TerminalPanel panel, File file) throws IOException {
|
||||||
|
int w = panel.getWidth();
|
||||||
|
int h = panel.getHeight();
|
||||||
|
if (w <= 0 || h <= 0) {
|
||||||
|
Dimension pref = panel.getPreferredSize();
|
||||||
|
w = Math.max(pref.width, 800);
|
||||||
|
h = Math.max(pref.height, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
|
||||||
|
Graphics2D g2 = image.createGraphics();
|
||||||
|
panel.paint(g2);
|
||||||
|
g2.dispose();
|
||||||
|
|
||||||
|
ImageIO.write(image, "PNG", file);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,278 @@
|
|||||||
|
package haus.nightmare.j3270.ui;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import javax.swing.border.LineBorder;
|
||||||
|
import javax.swing.border.TitledBorder;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dialog for composing and executing Host On-Demand ECL mnemonic keystroke scripts.
|
||||||
|
*/
|
||||||
|
public class ScriptDialog extends JDialog {
|
||||||
|
|
||||||
|
private final Telnet3270Client client;
|
||||||
|
private final TerminalPanel terminalPanel;
|
||||||
|
|
||||||
|
private JTextArea scriptArea;
|
||||||
|
private JLabel statusLabel;
|
||||||
|
|
||||||
|
public ScriptDialog(Frame parent, Telnet3270Client client, TerminalPanel terminalPanel) {
|
||||||
|
super(parent, "Run Keystrokes / ECL Script", false);
|
||||||
|
this.client = client;
|
||||||
|
this.terminalPanel = terminalPanel;
|
||||||
|
|
||||||
|
buildUI();
|
||||||
|
setSize(600, 480);
|
||||||
|
setLocationRelativeTo(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildUI() {
|
||||||
|
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
|
||||||
|
mainPanel.setBorder(new EmptyBorder(12, 14, 12, 14));
|
||||||
|
|
||||||
|
Font labelFont = new Font(Font.SANS_SERIF, Font.PLAIN, 13);
|
||||||
|
|
||||||
|
// Header
|
||||||
|
JPanel topPanel = new JPanel(new BorderLayout(5, 5));
|
||||||
|
topPanel.setOpaque(false);
|
||||||
|
JLabel descLabel = new JLabel("<html>Compose keystrokes and ECL bracketed mnemonics to stream to the mainframe.<br>"
|
||||||
|
+ "Example: <code>TSO[enter]USER[tab]PASSWORD[enter]</code> or <code>[pf3][clear]</code></html>");
|
||||||
|
descLabel.setFont(labelFont);
|
||||||
|
topPanel.add(descLabel, BorderLayout.CENTER);
|
||||||
|
mainPanel.add(topPanel, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
// Script area
|
||||||
|
scriptArea = new JTextArea();
|
||||||
|
scriptArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 13));
|
||||||
|
scriptArea.setLineWrap(true);
|
||||||
|
scriptArea.setWrapStyleWord(false);
|
||||||
|
ThemeManager.styleTextArea(scriptArea);
|
||||||
|
|
||||||
|
JScrollPane scrollPane = new JScrollPane(scriptArea);
|
||||||
|
ThemeManager.styleScrollPane(scrollPane);
|
||||||
|
|
||||||
|
// Mnemonic helper buttons
|
||||||
|
JPanel tokenPanel = new JPanel(new WrapLayout(FlowLayout.LEFT, 4, 4));
|
||||||
|
tokenPanel.setOpaque(false);
|
||||||
|
tokenPanel.setBorder(ThemeManager.createTitledBorder("Insert Keystroke Token"));
|
||||||
|
|
||||||
|
String[] tokens = {
|
||||||
|
"[enter]", "[tab]", "[backtab]", "[clear]", "[reset]",
|
||||||
|
"[eraseeof]", "[eraseinput]", "[newline]", "[dup]", "[fm]",
|
||||||
|
"[attn]", "[sysreq]", "[cursel]",
|
||||||
|
"[pf1]", "[pf2]", "[pf3]", "[pf4]", "[pf5]", "[pf6]",
|
||||||
|
"[pf7]", "[pf8]", "[pf9]", "[pf10]", "[pf11]", "[pf12]",
|
||||||
|
"[pf13]", "[pf14]", "[pf15]", "[pf16]", "[pf17]", "[pf18]",
|
||||||
|
"[pf19]", "[pf20]", "[pf21]", "[pf22]", "[pf23]", "[pf24]",
|
||||||
|
"[pa1]", "[pa2]", "[pa3]", "[up]", "[down]", "[left]", "[right]", "[home]"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (String token : tokens) {
|
||||||
|
JButton btn = new JButton(token);
|
||||||
|
btn.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 11));
|
||||||
|
ThemeManager.styleButton(btn, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
btn.setFocusable(false);
|
||||||
|
btn.setMargin(new Insets(2, 4, 2, 4));
|
||||||
|
btn.addActionListener(e -> insertToken(token));
|
||||||
|
tokenPanel.add(btn);
|
||||||
|
}
|
||||||
|
|
||||||
|
JPanel centerPanel = new JPanel(new BorderLayout(6, 6));
|
||||||
|
centerPanel.setOpaque(false);
|
||||||
|
centerPanel.add(scrollPane, BorderLayout.CENTER);
|
||||||
|
centerPanel.add(tokenPanel, BorderLayout.SOUTH);
|
||||||
|
mainPanel.add(centerPanel, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
// Bottom
|
||||||
|
JPanel bottomPanel = new JPanel(new BorderLayout(5, 5));
|
||||||
|
bottomPanel.setOpaque(false);
|
||||||
|
|
||||||
|
statusLabel = new JLabel("Ready");
|
||||||
|
statusLabel.setForeground(ThemeManager.getFgMuted());
|
||||||
|
statusLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12));
|
||||||
|
bottomPanel.add(statusLabel, BorderLayout.WEST);
|
||||||
|
|
||||||
|
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 6, 0));
|
||||||
|
buttonPanel.setOpaque(false);
|
||||||
|
|
||||||
|
JButton loadBtn = new JButton("Load Script...");
|
||||||
|
ThemeManager.styleButton(loadBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
loadBtn.setFont(labelFont);
|
||||||
|
loadBtn.addActionListener(e -> loadScript());
|
||||||
|
|
||||||
|
JButton saveBtn = new JButton("Save Script...");
|
||||||
|
ThemeManager.styleButton(saveBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
saveBtn.setFont(labelFont);
|
||||||
|
saveBtn.addActionListener(e -> saveScript());
|
||||||
|
|
||||||
|
JButton runBtn = new JButton("Execute");
|
||||||
|
ThemeManager.styleButton(runBtn, ThemeManager.ButtonVariant.PRIMARY);
|
||||||
|
runBtn.setFont(labelFont);
|
||||||
|
runBtn.addActionListener(e -> executeScript());
|
||||||
|
|
||||||
|
JButton closeBtn = new JButton("Close");
|
||||||
|
ThemeManager.styleButton(closeBtn, ThemeManager.ButtonVariant.CANCEL);
|
||||||
|
closeBtn.setFont(labelFont);
|
||||||
|
closeBtn.addActionListener(e -> dispose());
|
||||||
|
|
||||||
|
buttonPanel.add(loadBtn);
|
||||||
|
buttonPanel.add(saveBtn);
|
||||||
|
buttonPanel.add(Box.createHorizontalStrut(10));
|
||||||
|
buttonPanel.add(runBtn);
|
||||||
|
buttonPanel.add(closeBtn);
|
||||||
|
|
||||||
|
bottomPanel.add(buttonPanel, BorderLayout.EAST);
|
||||||
|
mainPanel.add(bottomPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
setContentPane(mainPanel);
|
||||||
|
ThemeManager.applyThemeToWindow(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertToken(String token) {
|
||||||
|
scriptArea.replaceSelection(token);
|
||||||
|
scriptArea.requestFocusInWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeScript() {
|
||||||
|
String script = scriptArea.getText();
|
||||||
|
if (script == null || script.trim().isEmpty()) {
|
||||||
|
statusLabel.setText("Script is empty.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client == null || !client.getConnectionState().isFullSession()) {
|
||||||
|
statusLabel.setText("Not connected to host.");
|
||||||
|
JOptionPane.showMessageDialog(this, "Connect to a 3270 session before executing scripts.",
|
||||||
|
"Not Connected", JOptionPane.WARNING_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
statusLabel.setText("Executing keystrokes...");
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
// Split multi-line scripts or process sequentially
|
||||||
|
String cleaned = script.replace("\r\n", "\n").replace("\r", "\n");
|
||||||
|
String[] lines = cleaned.split("\n");
|
||||||
|
for (int i = 0; i < lines.length; i++) {
|
||||||
|
String line = lines[i].trim();
|
||||||
|
if (!line.isEmpty() && !line.startsWith("#") && !line.startsWith("//")) {
|
||||||
|
client.sendKeys(line);
|
||||||
|
Thread.sleep(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
statusLabel.setText("Script execution completed successfully.");
|
||||||
|
if (terminalPanel != null) {
|
||||||
|
terminalPanel.repaint();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception ex) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
statusLabel.setText("Error executing script: " + ex.getMessage());
|
||||||
|
JOptionPane.showMessageDialog(this, "Execution error: " + ex.getMessage(),
|
||||||
|
"Script Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, "ScriptRunner").start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadScript() {
|
||||||
|
JFileChooser fc = new JFileChooser();
|
||||||
|
fc.setDialogTitle("Open Script File");
|
||||||
|
if (fc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
|
||||||
|
try {
|
||||||
|
byte[] bytes = java.nio.file.Files.readAllBytes(fc.getSelectedFile().toPath());
|
||||||
|
scriptArea.setText(new String(bytes, StandardCharsets.UTF_8));
|
||||||
|
statusLabel.setText("Loaded script: " + fc.getSelectedFile().getName());
|
||||||
|
} catch (IOException e) {
|
||||||
|
JOptionPane.showMessageDialog(this, "Could not open file: " + e.getMessage(),
|
||||||
|
"Open Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveScript() {
|
||||||
|
JFileChooser fc = new JFileChooser();
|
||||||
|
fc.setDialogTitle("Save Script File");
|
||||||
|
if (fc.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
|
||||||
|
try {
|
||||||
|
java.nio.file.Files.write(fc.getSelectedFile().toPath(),
|
||||||
|
scriptArea.getText().getBytes(StandardCharsets.UTF_8));
|
||||||
|
statusLabel.setText("Saved script: " + fc.getSelectedFile().getName());
|
||||||
|
} catch (IOException e) {
|
||||||
|
JOptionPane.showMessageDialog(this, "Could not save file: " + e.getMessage(),
|
||||||
|
"Save Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FlowLayout subclass that fully supports word wrapping inside JScrollPanes / dialogs.
|
||||||
|
*/
|
||||||
|
private static class WrapLayout extends FlowLayout {
|
||||||
|
public WrapLayout(int align, int hgap, int vgap) {
|
||||||
|
super(align, hgap, vgap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Dimension preferredLayoutSize(Container target) {
|
||||||
|
return layoutSize(target, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Dimension minimumLayoutSize(Container target) {
|
||||||
|
Dimension minimum = layoutSize(target, false);
|
||||||
|
minimum.width -= (getHgap() + 1);
|
||||||
|
return minimum;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dimension layoutSize(Container target, boolean preferred) {
|
||||||
|
synchronized (target.getTreeLock()) {
|
||||||
|
int targetWidth = target.getWidth();
|
||||||
|
if (targetWidth == 0) targetWidth = Integer.MAX_VALUE;
|
||||||
|
|
||||||
|
int hgap = getHgap();
|
||||||
|
int vgap = getVgap();
|
||||||
|
Insets insets = target.getInsets();
|
||||||
|
int horizontalInsetsAndGap = insets.left + insets.right + (hgap * 2);
|
||||||
|
int maxWidth = targetWidth - horizontalInsetsAndGap;
|
||||||
|
|
||||||
|
Dimension dim = new Dimension(0, 0);
|
||||||
|
int rowWidth = 0;
|
||||||
|
int rowHeight = 0;
|
||||||
|
|
||||||
|
int nmembers = target.getComponentCount();
|
||||||
|
for (int i = 0; i < nmembers; i++) {
|
||||||
|
Component m = target.getComponent(i);
|
||||||
|
if (m.isVisible()) {
|
||||||
|
Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize();
|
||||||
|
if (rowWidth + d.width > maxWidth) {
|
||||||
|
addRow(dim, rowWidth, rowHeight);
|
||||||
|
rowWidth = 0;
|
||||||
|
rowHeight = 0;
|
||||||
|
}
|
||||||
|
if (rowWidth != 0) rowWidth += hgap;
|
||||||
|
rowWidth += d.width;
|
||||||
|
rowHeight = Math.max(rowHeight, d.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addRow(dim, rowWidth, rowHeight);
|
||||||
|
|
||||||
|
dim.width += horizontalInsetsAndGap;
|
||||||
|
dim.height += insets.top + insets.bottom + vgap * 2;
|
||||||
|
return dim;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addRow(Dimension dim, int rowWidth, int rowHeight) {
|
||||||
|
dim.width = Math.max(dim.width, rowWidth);
|
||||||
|
if (dim.height > 0) dim.height += getVgap();
|
||||||
|
dim.height += rowHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,16 +19,8 @@ public class SettingsDialog extends JDialog {
|
|||||||
|
|
||||||
private final J3270App parentApp;
|
private final J3270App parentApp;
|
||||||
|
|
||||||
// Dark theme colors
|
|
||||||
private static final Color DARK_BG = new Color(43, 43, 43);
|
|
||||||
private static final Color DARK_BG_LIGHTER = new Color(55, 55, 55);
|
|
||||||
private static final Color DARK_FG = new Color(224, 224, 224);
|
|
||||||
private static final Color DARK_BORDER = new Color(70, 70, 70);
|
|
||||||
private static final Color DARK_SELECTION = new Color(75, 110, 175);
|
|
||||||
private static final Color DARK_BUTTON_BG = new Color(60, 63, 65);
|
|
||||||
private static final Color DARK_FIELD_BG = new Color(50, 50, 50);
|
|
||||||
|
|
||||||
// Appearance tab
|
// Appearance tab
|
||||||
|
private JComboBox<UITheme> uiThemeBox;
|
||||||
private JComboBox<String> fontBox;
|
private JComboBox<String> fontBox;
|
||||||
private JSpinner fontSizeSpinner;
|
private JSpinner fontSizeSpinner;
|
||||||
private JComboBox<haus.nightmare.lib3270j.graphics.GraphicsMode> graphicsModeBox;
|
private JComboBox<haus.nightmare.lib3270j.graphics.GraphicsMode> graphicsModeBox;
|
||||||
@@ -51,22 +43,30 @@ public class SettingsDialog extends JDialog {
|
|||||||
this.parentApp = parent;
|
this.parentApp = parent;
|
||||||
|
|
||||||
initComponents();
|
initComponents();
|
||||||
setSize(550, 450);
|
setSize(560, 480);
|
||||||
setLocationRelativeTo(parent);
|
setLocationRelativeTo(parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initComponents() {
|
private void initComponents() {
|
||||||
JTabbedPane tabbedPane = new JTabbedPane();
|
JTabbedPane tabbedPane = new JTabbedPane();
|
||||||
|
ThemeManager.styleTabbedPane(tabbedPane);
|
||||||
|
|
||||||
tabbedPane.addTab("Appearance", createAppearancePanel());
|
tabbedPane.addTab("Appearance", createAppearancePanel());
|
||||||
tabbedPane.addTab("Behavior", createBehaviorPanel());
|
tabbedPane.addTab("Behavior", createBehaviorPanel());
|
||||||
tabbedPane.addTab("Advanced", createAdvancedPanel());
|
tabbedPane.addTab("Advanced", createAdvancedPanel());
|
||||||
|
|
||||||
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 8, 8));
|
||||||
JButton btnExport = new JButton("Export Config...");
|
JButton btnExport = new JButton("Export Config...");
|
||||||
JButton btnOk = new JButton("OK");
|
ThemeManager.styleButton(btnExport, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
|
||||||
JButton btnApply = new JButton("Apply");
|
JButton btnApply = new JButton("Apply");
|
||||||
|
ThemeManager.styleButton(btnApply, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
|
||||||
JButton btnCancel = new JButton("Cancel");
|
JButton btnCancel = new JButton("Cancel");
|
||||||
|
ThemeManager.styleButton(btnCancel, ThemeManager.ButtonVariant.CANCEL);
|
||||||
|
|
||||||
|
JButton btnOk = new JButton("OK");
|
||||||
|
ThemeManager.styleButton(btnOk, ThemeManager.ButtonVariant.PRIMARY);
|
||||||
|
|
||||||
btnExport.addActionListener((ActionEvent e) -> {
|
btnExport.addActionListener((ActionEvent e) -> {
|
||||||
exportConfig();
|
exportConfig();
|
||||||
@@ -96,132 +96,11 @@ public class SettingsDialog extends JDialog {
|
|||||||
getContentPane().add(tabbedPane, BorderLayout.CENTER);
|
getContentPane().add(tabbedPane, BorderLayout.CENTER);
|
||||||
getContentPane().add(buttonPanel, BorderLayout.SOUTH);
|
getContentPane().add(buttonPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
// Apply dark theme to all components for cross-platform readability
|
ThemeManager.applyThemeToWindow(this);
|
||||||
applyDarkTheme(getContentPane());
|
|
||||||
applyDarkTheme(tabbedPane);
|
|
||||||
applyDarkTheme(buttonPanel);
|
|
||||||
getContentPane().setBackground(DARK_BG);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Dark Theme Utility ==========
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively apply dark theme to a component and all its children.
|
|
||||||
* Ensures the Settings dialog is readable on Windows, Linux, and macOS.
|
|
||||||
*/
|
|
||||||
private void applyDarkTheme(Component comp) {
|
private void applyDarkTheme(Component comp) {
|
||||||
if (comp instanceof JTabbedPane) {
|
ThemeManager.applyTheme(comp);
|
||||||
JTabbedPane tp = (JTabbedPane) comp;
|
|
||||||
tp.setBackground(DARK_BG);
|
|
||||||
tp.setForeground(DARK_FG);
|
|
||||||
for (int i = 0; i < tp.getTabCount(); i++) {
|
|
||||||
applyDarkTheme(tp.getComponentAt(i));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (comp instanceof JTable) {
|
|
||||||
JTable table = (JTable) comp;
|
|
||||||
table.setBackground(DARK_FIELD_BG);
|
|
||||||
table.setForeground(DARK_FG);
|
|
||||||
table.setSelectionBackground(DARK_SELECTION);
|
|
||||||
table.setSelectionForeground(Color.WHITE);
|
|
||||||
table.setGridColor(DARK_BORDER);
|
|
||||||
JTableHeader header = table.getTableHeader();
|
|
||||||
if (header != null) {
|
|
||||||
header.setBackground(DARK_BG_LIGHTER);
|
|
||||||
header.setForeground(DARK_FG);
|
|
||||||
DefaultTableCellRenderer headerRenderer = new DefaultTableCellRenderer();
|
|
||||||
headerRenderer.setBackground(DARK_BG_LIGHTER);
|
|
||||||
headerRenderer.setForeground(DARK_FG);
|
|
||||||
header.setDefaultRenderer(headerRenderer);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (comp instanceof JScrollPane) {
|
|
||||||
JScrollPane sp = (JScrollPane) comp;
|
|
||||||
sp.setBackground(DARK_BG);
|
|
||||||
sp.getViewport().setBackground(DARK_FIELD_BG);
|
|
||||||
applyDarkTheme(sp.getViewport().getView());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (comp instanceof JButton) {
|
|
||||||
JButton btn = (JButton) comp;
|
|
||||||
btn.setBackground(DARK_BUTTON_BG);
|
|
||||||
btn.setForeground(DARK_FG);
|
|
||||||
btn.setFocusPainted(false);
|
|
||||||
btn.setBorder(BorderFactory.createCompoundBorder(
|
|
||||||
new LineBorder(DARK_BORDER, 1),
|
|
||||||
BorderFactory.createEmptyBorder(3, 10, 3, 10)));
|
|
||||||
btn.setOpaque(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (comp instanceof JComboBox) {
|
|
||||||
JComboBox<?> cb = (JComboBox<?>) comp;
|
|
||||||
cb.setBackground(DARK_FIELD_BG);
|
|
||||||
cb.setForeground(DARK_FG);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (comp instanceof JSpinner) {
|
|
||||||
JSpinner sp = (JSpinner) comp;
|
|
||||||
sp.setBackground(DARK_FIELD_BG);
|
|
||||||
sp.setForeground(DARK_FG);
|
|
||||||
JComponent editor = sp.getEditor();
|
|
||||||
if (editor instanceof JSpinner.DefaultEditor) {
|
|
||||||
JTextField tf = ((JSpinner.DefaultEditor) editor).getTextField();
|
|
||||||
tf.setBackground(DARK_FIELD_BG);
|
|
||||||
tf.setForeground(DARK_FG);
|
|
||||||
tf.setCaretColor(DARK_FG);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (comp instanceof JTextField) {
|
|
||||||
JTextField tf = (JTextField) comp;
|
|
||||||
tf.setBackground(DARK_FIELD_BG);
|
|
||||||
tf.setForeground(DARK_FG);
|
|
||||||
tf.setCaretColor(DARK_FG);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (comp instanceof JCheckBox) {
|
|
||||||
JCheckBox cb = (JCheckBox) comp;
|
|
||||||
cb.setBackground(DARK_BG);
|
|
||||||
cb.setForeground(DARK_FG);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (comp instanceof JLabel) {
|
|
||||||
comp.setForeground(DARK_FG);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic panel / container
|
|
||||||
// Skip color swatch panels — their background IS the color
|
|
||||||
if (comp instanceof JPanel && "colorSwatch".equals(comp.getName())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
comp.setBackground(DARK_BG);
|
|
||||||
comp.setForeground(DARK_FG);
|
|
||||||
|
|
||||||
if (comp instanceof JPanel) {
|
|
||||||
JPanel panel = (JPanel) comp;
|
|
||||||
// Style titled borders
|
|
||||||
if (panel.getBorder() instanceof TitledBorder) {
|
|
||||||
TitledBorder tb = (TitledBorder) panel.getBorder();
|
|
||||||
tb.setTitleColor(DARK_FG);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (comp instanceof Container) {
|
|
||||||
for (Component child : ((Container) comp).getComponents()) {
|
|
||||||
applyDarkTheme(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Tab Panels ==========
|
// ========== Tab Panels ==========
|
||||||
@@ -232,9 +111,22 @@ public class SettingsDialog extends JDialog {
|
|||||||
gbc.insets = new Insets(10, 10, 10, 10);
|
gbc.insets = new Insets(10, 10, 10, 10);
|
||||||
gbc.anchor = GridBagConstraints.WEST;
|
gbc.anchor = GridBagConstraints.WEST;
|
||||||
|
|
||||||
// Font family
|
// UI Theme
|
||||||
gbc.gridx = 0;
|
gbc.gridx = 0;
|
||||||
gbc.gridy = 0;
|
gbc.gridy = 0;
|
||||||
|
JLabel themeLabel = new JLabel("Java UI Theme:");
|
||||||
|
panel.add(themeLabel, gbc);
|
||||||
|
|
||||||
|
uiThemeBox = new JComboBox<>(UITheme.values());
|
||||||
|
uiThemeBox.setSelectedItem(Settings.getJavaUiTheme());
|
||||||
|
gbc.gridx = 1;
|
||||||
|
gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
panel.add(uiThemeBox, gbc);
|
||||||
|
|
||||||
|
// Font family
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = 1;
|
||||||
|
gbc.fill = GridBagConstraints.NONE;
|
||||||
JLabel fontLabel = new JLabel("Terminal Font:");
|
JLabel fontLabel = new JLabel("Terminal Font:");
|
||||||
panel.add(fontLabel, gbc);
|
panel.add(fontLabel, gbc);
|
||||||
|
|
||||||
@@ -254,7 +146,7 @@ public class SettingsDialog extends JDialog {
|
|||||||
|
|
||||||
// Font size
|
// Font size
|
||||||
gbc.gridx = 0;
|
gbc.gridx = 0;
|
||||||
gbc.gridy = 1;
|
gbc.gridy = 2;
|
||||||
gbc.fill = GridBagConstraints.NONE;
|
gbc.fill = GridBagConstraints.NONE;
|
||||||
JLabel sizeLabel = new JLabel("Font Size:");
|
JLabel sizeLabel = new JLabel("Font Size:");
|
||||||
panel.add(sizeLabel, gbc);
|
panel.add(sizeLabel, gbc);
|
||||||
@@ -267,7 +159,7 @@ public class SettingsDialog extends JDialog {
|
|||||||
|
|
||||||
// Graphics Mode
|
// Graphics Mode
|
||||||
gbc.gridx = 0;
|
gbc.gridx = 0;
|
||||||
gbc.gridy = 2;
|
gbc.gridy = 3;
|
||||||
gbc.fill = GridBagConstraints.NONE;
|
gbc.fill = GridBagConstraints.NONE;
|
||||||
JLabel graphicsLabel = new JLabel("Graphics Mode:");
|
JLabel graphicsLabel = new JLabel("Graphics Mode:");
|
||||||
panel.add(graphicsLabel, gbc);
|
panel.add(graphicsLabel, gbc);
|
||||||
@@ -279,7 +171,7 @@ public class SettingsDialog extends JDialog {
|
|||||||
panel.add(graphicsModeBox, gbc);
|
panel.add(graphicsModeBox, gbc);
|
||||||
|
|
||||||
// Fill remaining space
|
// Fill remaining space
|
||||||
gbc.gridy = 3;
|
gbc.gridy = 4;
|
||||||
gbc.weighty = 1.0;
|
gbc.weighty = 1.0;
|
||||||
panel.add(Box.createGlue(), gbc);
|
panel.add(Box.createGlue(), gbc);
|
||||||
|
|
||||||
@@ -429,6 +321,7 @@ public class SettingsDialog extends JDialog {
|
|||||||
|
|
||||||
JPanel resetPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
JPanel resetPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
||||||
JButton btnResetColors = new JButton("Reset to Defaults");
|
JButton btnResetColors = new JButton("Reset to Defaults");
|
||||||
|
ThemeManager.styleButton(btnResetColors, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
btnResetColors.addActionListener(e -> {
|
btnResetColors.addActionListener(e -> {
|
||||||
for (int i=0; i<16; i++) {
|
for (int i=0; i<16; i++) {
|
||||||
tempHostColors[i] = TerminalPanel.DEFAULT_HOST_COLORS[i];
|
tempHostColors[i] = TerminalPanel.DEFAULT_HOST_COLORS[i];
|
||||||
@@ -436,8 +329,6 @@ public class SettingsDialog extends JDialog {
|
|||||||
for (int i=0; i<monoKeys.length; i++) {
|
for (int i=0; i<monoKeys.length; i++) {
|
||||||
tempMonoColors.put(monoKeys[i], monoDefs[i]);
|
tempMonoColors.put(monoKeys[i], monoDefs[i]);
|
||||||
}
|
}
|
||||||
// Repaint container implicitly handled if we trigger a UI update,
|
|
||||||
// but for simplicity user can just close/reopen or have it refresh on save
|
|
||||||
JOptionPane.showMessageDialog(main, "Colors reset. Click Apply to save.");
|
JOptionPane.showMessageDialog(main, "Colors reset. Click Apply to save.");
|
||||||
});
|
});
|
||||||
resetPanel.add(btnResetColors);
|
resetPanel.add(btnResetColors);
|
||||||
@@ -453,7 +344,8 @@ public class SettingsDialog extends JDialog {
|
|||||||
JPanel main = new JPanel(new BorderLayout());
|
JPanel main = new JPanel(new BorderLayout());
|
||||||
|
|
||||||
String[] actions = {"ENTER", "TAB", "shift TAB", "UP", "DOWN", "LEFT", "RIGHT",
|
String[] actions = {"ENTER", "TAB", "shift TAB", "UP", "DOWN", "LEFT", "RIGHT",
|
||||||
"HOME", "END", "PAGE_UP", "PAGE_DOWN", "ESCAPE", "INSERT", "DELETE", "BACK_SPACE", "CLEAR"};
|
"HOME", "END", "PAGE_UP", "PAGE_DOWN", "ESCAPE", "INSERT", "DELETE", "BACK_SPACE", "CLEAR",
|
||||||
|
"ERASE_INPUT", "NEWLINE", "DUP", "FIELD_MARK", "ATTN", "SYSREQ", "CURSEL"};
|
||||||
|
|
||||||
keymapModel = new DefaultTableModel(new Object[]{"Action", "Key Binding"}, 0) {
|
keymapModel = new DefaultTableModel(new Object[]{"Action", "Key Binding"}, 0) {
|
||||||
@Override
|
@Override
|
||||||
@@ -465,7 +357,14 @@ public class SettingsDialog extends JDialog {
|
|||||||
// Populate table from Settings or Defaults
|
// Populate table from Settings or Defaults
|
||||||
for(String act : actions) {
|
for(String act : actions) {
|
||||||
String def = act;
|
String def = act;
|
||||||
if(def.equals("PAGE_UP")) def = "PAGE_UP"; // fallback example
|
if (def.equals("ERASE_INPUT")) def = "alt E";
|
||||||
|
else if (def.equals("NEWLINE")) def = "shift ENTER";
|
||||||
|
else if (def.equals("DUP")) def = "alt D";
|
||||||
|
else if (def.equals("FIELD_MARK")) def = "alt M";
|
||||||
|
else if (def.equals("ATTN")) def = "alt A";
|
||||||
|
else if (def.equals("SYSREQ")) def = "alt S";
|
||||||
|
else if (def.equals("CURSEL")) def = "alt Q";
|
||||||
|
else if (def.equals("CLEAR")) def = "alt C";
|
||||||
String current = haus.nightmare.j3270.config.Settings.getKeyBinding(act, def);
|
String current = haus.nightmare.j3270.config.Settings.getKeyBinding(act, def);
|
||||||
tempKeyBindings.put(act, current);
|
tempKeyBindings.put(act, current);
|
||||||
keymapModel.addRow(new Object[]{act, current});
|
keymapModel.addRow(new Object[]{act, current});
|
||||||
@@ -490,6 +389,7 @@ public class SettingsDialog extends JDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
JTable table = new JTable(keymapModel);
|
JTable table = new JTable(keymapModel);
|
||||||
|
ThemeManager.styleTable(table);
|
||||||
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||||
table.setFillsViewportHeight(true);
|
table.setFillsViewportHeight(true);
|
||||||
|
|
||||||
@@ -502,9 +402,8 @@ public class SettingsDialog extends JDialog {
|
|||||||
captureDialog.setSize(320, 100);
|
captureDialog.setSize(320, 100);
|
||||||
captureDialog.setLocationRelativeTo(this);
|
captureDialog.setLocationRelativeTo(this);
|
||||||
JLabel lbl = new JLabel("Press any key combination now...", SwingConstants.CENTER);
|
JLabel lbl = new JLabel("Press any key combination now...", SwingConstants.CENTER);
|
||||||
lbl.setForeground(DARK_FG);
|
|
||||||
captureDialog.getContentPane().setBackground(DARK_BG);
|
|
||||||
captureDialog.add(lbl);
|
captureDialog.add(lbl);
|
||||||
|
ThemeManager.applyThemeToWindow(captureDialog);
|
||||||
captureDialog.addKeyListener(new KeyAdapter() {
|
captureDialog.addKeyListener(new KeyAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void keyPressed(KeyEvent e) {
|
public void keyPressed(KeyEvent e) {
|
||||||
@@ -524,6 +423,7 @@ public class SettingsDialog extends JDialog {
|
|||||||
|
|
||||||
// Replace Binding — sets the action to exactly one new key
|
// Replace Binding — sets the action to exactly one new key
|
||||||
JButton btnReplace = new JButton("Replace Binding");
|
JButton btnReplace = new JButton("Replace Binding");
|
||||||
|
ThemeManager.styleButton(btnReplace, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
btnReplace.addActionListener(e -> {
|
btnReplace.addActionListener(e -> {
|
||||||
int row = table.getSelectedRow();
|
int row = table.getSelectedRow();
|
||||||
if (row < 0) return;
|
if (row < 0) return;
|
||||||
@@ -537,6 +437,7 @@ public class SettingsDialog extends JDialog {
|
|||||||
|
|
||||||
// Add Binding — appends an additional key to the existing binding(s)
|
// Add Binding — appends an additional key to the existing binding(s)
|
||||||
JButton btnAdd = new JButton("Add Binding");
|
JButton btnAdd = new JButton("Add Binding");
|
||||||
|
ThemeManager.styleButton(btnAdd, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
btnAdd.addActionListener(e -> {
|
btnAdd.addActionListener(e -> {
|
||||||
int row = table.getSelectedRow();
|
int row = table.getSelectedRow();
|
||||||
if (row < 0) return;
|
if (row < 0) return;
|
||||||
@@ -557,6 +458,7 @@ public class SettingsDialog extends JDialog {
|
|||||||
|
|
||||||
// Remove Last — removes the last comma-separated binding entry
|
// Remove Last — removes the last comma-separated binding entry
|
||||||
JButton btnRemoveLast = new JButton("Remove Last");
|
JButton btnRemoveLast = new JButton("Remove Last");
|
||||||
|
ThemeManager.styleButton(btnRemoveLast, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
btnRemoveLast.addActionListener(e -> {
|
btnRemoveLast.addActionListener(e -> {
|
||||||
int row = table.getSelectedRow();
|
int row = table.getSelectedRow();
|
||||||
if (row < 0) return;
|
if (row < 0) return;
|
||||||
@@ -577,6 +479,7 @@ public class SettingsDialog extends JDialog {
|
|||||||
|
|
||||||
// Unbind All — clears all bindings for the action
|
// Unbind All — clears all bindings for the action
|
||||||
JButton btnUnbind = new JButton("Unbind All");
|
JButton btnUnbind = new JButton("Unbind All");
|
||||||
|
ThemeManager.styleButton(btnUnbind, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
btnUnbind.addActionListener(e -> {
|
btnUnbind.addActionListener(e -> {
|
||||||
int row = table.getSelectedRow();
|
int row = table.getSelectedRow();
|
||||||
if (row < 0) return;
|
if (row < 0) return;
|
||||||
@@ -588,6 +491,7 @@ public class SettingsDialog extends JDialog {
|
|||||||
|
|
||||||
// Reset Keymaps — restore all defaults
|
// Reset Keymaps — restore all defaults
|
||||||
JButton btnReset = new JButton("Reset All");
|
JButton btnReset = new JButton("Reset All");
|
||||||
|
ThemeManager.styleButton(btnReset, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
btnReset.addActionListener(e -> {
|
btnReset.addActionListener(e -> {
|
||||||
for (int row = 0; row < keymapModel.getRowCount(); row++) {
|
for (int row = 0; row < keymapModel.getRowCount(); row++) {
|
||||||
String action = (String) keymapModel.getValueAt(row, 0);
|
String action = (String) keymapModel.getValueAt(row, 0);
|
||||||
@@ -620,6 +524,12 @@ public class SettingsDialog extends JDialog {
|
|||||||
private boolean applySettings() {
|
private boolean applySettings() {
|
||||||
try {
|
try {
|
||||||
// Apply Appearance
|
// Apply Appearance
|
||||||
|
UITheme selectedTheme = (UITheme) uiThemeBox.getSelectedItem();
|
||||||
|
if (selectedTheme != null) {
|
||||||
|
Settings.setJavaUiTheme(selectedTheme);
|
||||||
|
ThemeManager.setTheme(selectedTheme);
|
||||||
|
}
|
||||||
|
|
||||||
String fontFam = (String) fontBox.getSelectedItem();
|
String fontFam = (String) fontBox.getSelectedItem();
|
||||||
if (fontFam != null) {
|
if (fontFam != null) {
|
||||||
Settings.setFontFamily(fontFam);
|
Settings.setFontFamily(fontFam);
|
||||||
@@ -660,6 +570,7 @@ public class SettingsDialog extends JDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parentApp.getTerminalPanel().reloadSettings();
|
parentApp.getTerminalPanel().reloadSettings();
|
||||||
|
ThemeManager.applyThemeToWindow(this);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
@@ -2,74 +2,60 @@ package haus.nightmare.j3270.ui;
|
|||||||
|
|
||||||
import haus.nightmare.lib3270j.ConnectionState;
|
import haus.nightmare.lib3270j.ConnectionState;
|
||||||
import haus.nightmare.lib3270j.Telnet3270Client;
|
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||||
|
import haus.nightmare.lib3270j.ecl.ECLConstants;
|
||||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Status bar displaying connection state, cursor position, timing, and lock
|
* Status bar displaying connection state, cursor position, timing, and lock status.
|
||||||
* status.
|
|
||||||
* Equivalent to the OIA (Operator Information Area) on a real 3270 terminal.
|
* Equivalent to the OIA (Operator Information Area) on a real 3270 terminal.
|
||||||
*/
|
*/
|
||||||
public class StatusBar extends JPanel {
|
public class StatusBar extends JPanel {
|
||||||
|
|
||||||
private final JLabel connectionStatus;
|
private final JLabel connectionStatus;
|
||||||
private final JLabel tlsStatus;
|
private final JLabel tlsStatus;
|
||||||
private final JLabel cursorPosition;
|
|
||||||
private final JLabel luName;
|
private final JLabel luName;
|
||||||
private final JLabel lockStatus;
|
private final JLabel lockStatus;
|
||||||
|
private final JLabel fieldTypeStatus;
|
||||||
|
private final JLabel codePageInfo;
|
||||||
private final JLabel modelInfo;
|
private final JLabel modelInfo;
|
||||||
private final JButton lpButton;
|
private final JLabel cursorPosition;
|
||||||
|
|
||||||
private Telnet3270Client client;
|
private Telnet3270Client client;
|
||||||
private TerminalPanel terminalPanel;
|
private TerminalPanel terminalPanel;
|
||||||
|
|
||||||
// OIA colors
|
|
||||||
private static final Color OIA_BG = new Color(20, 20, 20);
|
|
||||||
private static final Color OIA_FG = new Color(50, 205, 50);
|
|
||||||
private static final Color OIA_DIM = new Color(80, 80, 80);
|
|
||||||
private static final Color OIA_ALERT = new Color(255, 80, 80);
|
|
||||||
|
|
||||||
public StatusBar() {
|
public StatusBar() {
|
||||||
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
|
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
|
||||||
setBackground(OIA_BG);
|
setBackground(ThemeManager.getStatusBarBg());
|
||||||
setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, new Color(40, 40, 40)));
|
setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, ThemeManager.getStatusBarBorder()));
|
||||||
setPreferredSize(new Dimension(800, 22));
|
setPreferredSize(new Dimension(800, 22));
|
||||||
|
|
||||||
Font oiaFont = new Font(Font.MONOSPACED, Font.PLAIN, 12);
|
Font oiaFont = new Font(Font.MONOSPACED, Font.PLAIN, 12);
|
||||||
|
|
||||||
connectionStatus = createLabel("Not Connected", oiaFont, OIA_DIM);
|
connectionStatus = createLabel("Not Connected", oiaFont, ThemeManager.getOiaFgDim());
|
||||||
tlsStatus = createLabel("", oiaFont, OIA_FG);
|
tlsStatus = createLabel("", oiaFont, ThemeManager.getOiaFgNormal());
|
||||||
luName = createLabel("", oiaFont, OIA_FG);
|
luName = createLabel("", oiaFont, ThemeManager.getOiaFgNormal());
|
||||||
lockStatus = createLabel("", oiaFont, OIA_ALERT);
|
lockStatus = createLabel("", oiaFont, ThemeManager.getOiaFgAlert());
|
||||||
modelInfo = createLabel("", oiaFont, OIA_DIM);
|
fieldTypeStatus = createLabel("", oiaFont, ThemeManager.getOiaFgDim());
|
||||||
cursorPosition = createLabel("001/001", oiaFont, OIA_FG);
|
codePageInfo = createLabel("", oiaFont, ThemeManager.getOiaFgDim());
|
||||||
|
modelInfo = createLabel("", oiaFont, ThemeManager.getOiaFgDim());
|
||||||
lpButton = new JButton("LightPen: OFF");
|
cursorPosition = createLabel("001/001 [0000]", oiaFont, ThemeManager.getOiaFgNormal());
|
||||||
lpButton.setFont(oiaFont);
|
|
||||||
lpButton.setForeground(OIA_FG);
|
|
||||||
lpButton.setBackground(OIA_BG);
|
|
||||||
lpButton.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
|
|
||||||
lpButton.setFocusable(false);
|
|
||||||
lpButton.addActionListener(e -> {
|
|
||||||
if (terminalPanel != null) {
|
|
||||||
terminalPanel.toggleLightPen();
|
|
||||||
lpButton.setText("LightPen: " + (terminalPanel.isLightPenMode() ? "ON" : "OFF"));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
add(Box.createHorizontalStrut(6));
|
add(Box.createHorizontalStrut(6));
|
||||||
add(connectionStatus);
|
add(connectionStatus);
|
||||||
add(Box.createHorizontalStrut(10));
|
add(Box.createHorizontalStrut(10));
|
||||||
add(tlsStatus);
|
add(tlsStatus);
|
||||||
add(Box.createHorizontalStrut(12));
|
add(Box.createHorizontalStrut(10));
|
||||||
add(luName);
|
add(luName);
|
||||||
add(Box.createHorizontalStrut(12));
|
add(Box.createHorizontalStrut(12));
|
||||||
add(lockStatus);
|
add(lockStatus);
|
||||||
add(Box.createHorizontalStrut(12));
|
add(Box.createHorizontalStrut(10));
|
||||||
add(lpButton);
|
add(fieldTypeStatus);
|
||||||
add(Box.createHorizontalGlue());
|
add(Box.createHorizontalGlue());
|
||||||
|
add(codePageInfo);
|
||||||
|
add(Box.createHorizontalStrut(12));
|
||||||
add(modelInfo);
|
add(modelInfo);
|
||||||
add(Box.createHorizontalStrut(12));
|
add(Box.createHorizontalStrut(12));
|
||||||
add(cursorPosition);
|
add(cursorPosition);
|
||||||
@@ -86,53 +72,67 @@ public class StatusBar extends JPanel {
|
|||||||
public void setClient(Telnet3270Client client, TerminalPanel terminalPanel) {
|
public void setClient(Telnet3270Client client, TerminalPanel terminalPanel) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.terminalPanel = terminalPanel;
|
this.terminalPanel = terminalPanel;
|
||||||
if (terminalPanel != null) {
|
updateStatus();
|
||||||
terminalPanel.setOnLightPenToggle(this::updateStatus);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void applyTheme(UITheme theme) {
|
||||||
|
setBackground(ThemeManager.getStatusBarBg(theme));
|
||||||
|
setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, ThemeManager.getStatusBarBorder(theme)));
|
||||||
updateStatus();
|
updateStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateStatus() {
|
public void updateStatus() {
|
||||||
if (client == null)
|
UITheme theme = ThemeManager.getTheme();
|
||||||
|
if (client == null) {
|
||||||
|
connectionStatus.setText("Not Connected");
|
||||||
|
connectionStatus.setForeground(ThemeManager.getOiaFgDim(theme));
|
||||||
|
tlsStatus.setText("");
|
||||||
|
luName.setText("");
|
||||||
|
lockStatus.setText("");
|
||||||
|
fieldTypeStatus.setText("");
|
||||||
|
codePageInfo.setText("");
|
||||||
|
modelInfo.setText("");
|
||||||
|
cursorPosition.setText("001/001");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Connection state
|
// Connection state
|
||||||
ConnectionState state = client.getConnectionState();
|
ConnectionState state = client.getConnectionState();
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case NOT_CONNECTED:
|
case NOT_CONNECTED:
|
||||||
connectionStatus.setText("Not Connected");
|
connectionStatus.setText("Not Connected");
|
||||||
connectionStatus.setForeground(OIA_DIM);
|
connectionStatus.setForeground(ThemeManager.getOiaFgDim(theme));
|
||||||
break;
|
break;
|
||||||
case TCP_PENDING:
|
case TCP_PENDING:
|
||||||
case TELNET_PENDING:
|
case TELNET_PENDING:
|
||||||
connectionStatus.setText("Connecting...");
|
connectionStatus.setText("Connecting...");
|
||||||
connectionStatus.setForeground(OIA_ALERT);
|
connectionStatus.setForeground(ThemeManager.getOiaFgAlert(theme));
|
||||||
break;
|
break;
|
||||||
case CONNECTED_3270:
|
case CONNECTED_3270:
|
||||||
connectionStatus.setText("TN3270");
|
connectionStatus.setText("TN3270");
|
||||||
connectionStatus.setForeground(OIA_FG);
|
connectionStatus.setForeground(ThemeManager.getOiaFgNormal(theme));
|
||||||
break;
|
break;
|
||||||
case CONNECTED_TN3270E:
|
case CONNECTED_TN3270E:
|
||||||
connectionStatus.setText("TN3270E");
|
connectionStatus.setText("TN3270E");
|
||||||
connectionStatus.setForeground(OIA_FG);
|
connectionStatus.setForeground(ThemeManager.getOiaFgNormal(theme));
|
||||||
break;
|
break;
|
||||||
case CONNECTED_SSCP:
|
case CONNECTED_SSCP:
|
||||||
connectionStatus.setText("SSCP-LU");
|
connectionStatus.setText("SSCP-LU");
|
||||||
connectionStatus.setForeground(OIA_FG);
|
connectionStatus.setForeground(ThemeManager.getOiaFgNormal(theme));
|
||||||
break;
|
break;
|
||||||
case CONNECTED_NVT:
|
case CONNECTED_NVT:
|
||||||
case CONNECTED_NVT_CHAR:
|
case CONNECTED_NVT_CHAR:
|
||||||
case CONNECTED_E_NVT:
|
case CONNECTED_E_NVT:
|
||||||
connectionStatus.setText("NVT");
|
connectionStatus.setText("NVT");
|
||||||
connectionStatus.setForeground(OIA_FG);
|
connectionStatus.setForeground(ThemeManager.getOiaFgNormal(theme));
|
||||||
break;
|
break;
|
||||||
case CONNECTED_UNBOUND:
|
case CONNECTED_UNBOUND:
|
||||||
connectionStatus.setText("Unbound");
|
connectionStatus.setText("Unbound");
|
||||||
connectionStatus.setForeground(new Color(255, 255, 80));
|
connectionStatus.setForeground(ThemeManager.getOiaFgWarn(theme));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
connectionStatus.setText(state.name());
|
connectionStatus.setText(state.name());
|
||||||
connectionStatus.setForeground(OIA_DIM);
|
connectionStatus.setForeground(ThemeManager.getOiaFgDim(theme));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,11 +144,11 @@ public class StatusBar extends JPanel {
|
|||||||
String protocol = session != null ? session.getProtocol() : "TLS";
|
String protocol = session != null ? session.getProtocol() : "TLS";
|
||||||
if (verified) {
|
if (verified) {
|
||||||
tlsStatus.setText("🔒 TLS");
|
tlsStatus.setText("🔒 TLS");
|
||||||
tlsStatus.setForeground(OIA_FG);
|
tlsStatus.setForeground(ThemeManager.getOiaFgNormal(theme));
|
||||||
tlsStatus.setToolTipText(protocol + " / " + cipher + " (Verified)");
|
tlsStatus.setToolTipText(protocol + " / " + cipher + " (Verified)");
|
||||||
} else {
|
} else {
|
||||||
tlsStatus.setText("🔓 TLS (Unverified)");
|
tlsStatus.setText("🔓 TLS (Unverified)");
|
||||||
tlsStatus.setForeground(new Color(255, 180, 80));
|
tlsStatus.setForeground(ThemeManager.getOiaFgWarn(theme));
|
||||||
tlsStatus.setToolTipText(protocol + " / " + cipher + " (Verification Bypassed)");
|
tlsStatus.setToolTipText(protocol + " / " + cipher + " (Verification Bypassed)");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -156,37 +156,81 @@ public class StatusBar extends JPanel {
|
|||||||
tlsStatus.setToolTipText(null);
|
tlsStatus.setToolTipText(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// LU name
|
// LU Name
|
||||||
String lu = "";
|
String lu = "";
|
||||||
if (client.getConnectionState().isTn3270e()) {
|
if (client.getTelnetFSM() != null && client.getTelnetFSM().getConnectedLu() != null && !client.getTelnetFSM().getConnectedLu().isEmpty()) {
|
||||||
// lu would come from FSM
|
lu = "LU:" + client.getTelnetFSM().getConnectedLu();
|
||||||
|
} else if (client.getConfig().getLuName() != null && !client.getConfig().getLuName().isEmpty()) {
|
||||||
|
lu = "LU:" + client.getConfig().getLuName();
|
||||||
}
|
}
|
||||||
luName.setText(lu);
|
luName.setText(lu);
|
||||||
|
luName.setForeground(ThemeManager.getOiaFgNormal(theme));
|
||||||
|
|
||||||
// Lock status
|
// Lock / Inhibit status
|
||||||
if (client.getInputProcessor().isKeyboardLocked()) {
|
int inhibit = client.getOIA().getInputInhibited();
|
||||||
|
if (inhibit != ECLConstants.INHIBIT_NOT_INHIBITED) {
|
||||||
|
switch (inhibit) {
|
||||||
|
case ECLConstants.INHIBIT_SYSTEM_LOCK:
|
||||||
lockStatus.setText("X SYSTEM");
|
lockStatus.setText("X SYSTEM");
|
||||||
lockStatus.setForeground(OIA_ALERT);
|
break;
|
||||||
|
case ECLConstants.INHIBIT_COMM_CHECK:
|
||||||
|
lockStatus.setText("X COMM");
|
||||||
|
break;
|
||||||
|
case ECLConstants.INHIBIT_NUMERIC_ONLY:
|
||||||
|
lockStatus.setText("X NUM");
|
||||||
|
break;
|
||||||
|
case ECLConstants.INHIBIT_PROTECTED_FIELD:
|
||||||
|
lockStatus.setText("X PROT");
|
||||||
|
break;
|
||||||
|
case ECLConstants.INHIBIT_OVERFLOW:
|
||||||
|
lockStatus.setText("X >");
|
||||||
|
break;
|
||||||
|
case ECLConstants.INHIBIT_OPERATOR_DUE:
|
||||||
|
lockStatus.setText("X OP");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
lockStatus.setText("X LOCKED");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
lockStatus.setForeground(ThemeManager.getOiaFgAlert(theme));
|
||||||
} else if (client.getInputProcessor().isInsertMode()) {
|
} else if (client.getInputProcessor().isInsertMode()) {
|
||||||
lockStatus.setText("INSERT");
|
lockStatus.setText("INSERT");
|
||||||
lockStatus.setForeground(OIA_FG);
|
lockStatus.setForeground(ThemeManager.getOiaFgNormal(theme));
|
||||||
} else {
|
} else {
|
||||||
lockStatus.setText("");
|
lockStatus.setText("");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Model info
|
// Field status: Numeric vs Alphanumeric
|
||||||
modelInfo.setText(client.getConfig().getModel().getTerminalType());
|
if (state.isFullSession() && client.getScreenBuffer().isFormatted()) {
|
||||||
|
if (client.getOIA().isNumeric()) {
|
||||||
|
fieldTypeStatus.setText("NUM");
|
||||||
|
fieldTypeStatus.setForeground(ThemeManager.getOiaFgWarn(theme));
|
||||||
|
} else {
|
||||||
|
fieldTypeStatus.setText("ALPHA");
|
||||||
|
fieldTypeStatus.setForeground(ThemeManager.getOiaFgDim(theme));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fieldTypeStatus.setText("");
|
||||||
|
}
|
||||||
|
|
||||||
// Cursor position
|
// Active Code Page
|
||||||
|
String cp = client.getCodePage();
|
||||||
|
codePageInfo.setText(cp != null ? "CP" + cp : "");
|
||||||
|
codePageInfo.setForeground(ThemeManager.getOiaFgDim(theme));
|
||||||
|
codePageInfo.setToolTipText("Active EBCDIC Code Page: CP" + cp);
|
||||||
|
|
||||||
|
// Model & Dimensions info
|
||||||
ScreenBuffer sb = client.getScreenBuffer();
|
ScreenBuffer sb = client.getScreenBuffer();
|
||||||
|
int rows = sb.getDisplayRows();
|
||||||
|
int cols = sb.getDisplayCols();
|
||||||
|
modelInfo.setText(client.getConfig().getModel().name().replace('_', '-') + " [" + rows + "x" + cols + "]");
|
||||||
|
modelInfo.setForeground(ThemeManager.getOiaFgDim(theme));
|
||||||
|
|
||||||
|
// Cursor position and buffer address
|
||||||
|
int curAddr = sb.getCursorAddress();
|
||||||
int row = sb.getCursorRow() + 1;
|
int row = sb.getCursorRow() + 1;
|
||||||
int col = sb.getCursorCol() + 1;
|
int col = sb.getCursorCol() + 1;
|
||||||
cursorPosition.setText(String.format("%03d/%03d", row, col));
|
cursorPosition.setText(String.format("%03d/%03d [%04d]", row, col, curAddr));
|
||||||
|
cursorPosition.setForeground(ThemeManager.getOiaFgNormal(theme));
|
||||||
// Light Pen indicator — sync with actual state
|
|
||||||
if (terminalPanel != null) {
|
|
||||||
lpButton.setText("LightPen: " + (terminalPanel.isLightPenMode() ? "ON" : "OFF"));
|
|
||||||
lpButton.setForeground(terminalPanel.isLightPenMode() ? new Color(255, 255, 80) : OIA_FG);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package haus.nightmare.j3270.ui;
|
package haus.nightmare.j3270.ui;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.ConnectionState;
|
||||||
import haus.nightmare.lib3270j.Telnet3270Client;
|
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||||
import haus.nightmare.lib3270j.graphics.GocaConstants;
|
import haus.nightmare.lib3270j.graphics.GocaConstants;
|
||||||
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
|
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
|
||||||
@@ -14,10 +15,10 @@ import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom JPanel that renders the 3270 screen buffer.
|
* Custom JPanel that renders the 3270 screen buffer.
|
||||||
* Supports colors, bold, underline, reverse, blink, and all 3278/3279
|
* Supports colors, bold, underline, reverse, blink, GOCA vector graphics,
|
||||||
* attributes.
|
* programmed symbols, search highlighting, and all 3278/3279 attributes.
|
||||||
*/
|
*/
|
||||||
public class TerminalPanel extends JPanel {
|
public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||||
|
|
||||||
private Telnet3270Client client;
|
private Telnet3270Client client;
|
||||||
|
|
||||||
@@ -62,7 +63,12 @@ public class TerminalPanel extends JPanel {
|
|||||||
private boolean isDragging = false;
|
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, 100);
|
||||||
|
|
||||||
// ========== Resize guard ==========
|
// ========== Search Highlight state ==========
|
||||||
|
private int searchHighlightAddr = -1;
|
||||||
|
private int searchHighlightLen = 0;
|
||||||
|
private static final Color SEARCH_HIGHLIGHT_COLOR = new Color(255, 215, 0, 120);
|
||||||
|
|
||||||
|
// ========== Graphics / Resize guard ==========
|
||||||
private boolean lightPenMode = false;
|
private boolean lightPenMode = false;
|
||||||
private long lastGraphicsUpdateCount = -1;
|
private long lastGraphicsUpdateCount = -1;
|
||||||
private java.awt.image.BufferedImage cachedGraphicsImage = null;
|
private java.awt.image.BufferedImage cachedGraphicsImage = null;
|
||||||
@@ -70,7 +76,6 @@ public class TerminalPanel extends JPanel {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute the horizontal render offset to center the grid within the panel.
|
* Compute the horizontal render offset to center the grid within the panel.
|
||||||
* Any leftover pixels (from integer font sizing) are split evenly.
|
|
||||||
*/
|
*/
|
||||||
private int getRenderOffsetX() {
|
private int getRenderOffsetX() {
|
||||||
int termCols = 80;
|
int termCols = 80;
|
||||||
@@ -82,7 +87,6 @@ public class TerminalPanel extends JPanel {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute the vertical render offset to center the grid within the panel.
|
* Compute the vertical render offset to center the grid within the panel.
|
||||||
* Must use getDisplayRows() to match paintComponent's iteration.
|
|
||||||
*/
|
*/
|
||||||
private int getRenderOffsetY() {
|
private int getRenderOffsetY() {
|
||||||
int termRows = 24;
|
int termRows = 24;
|
||||||
@@ -119,7 +123,7 @@ public class TerminalPanel extends JPanel {
|
|||||||
public static final Color DEFAULT_MONO_PROTECTED_HIGH = new Color(255, 255, 255);
|
public static final Color DEFAULT_MONO_PROTECTED_HIGH = new Color(255, 255, 255);
|
||||||
|
|
||||||
// Default Background
|
// Default Background
|
||||||
public static final Color DEFAULT_BG_COLOR = new Color(10, 10, 10);
|
public static final Color DEFAULT_BG_COLOR = Color.BLACK;
|
||||||
|
|
||||||
public TerminalPanel() {
|
public TerminalPanel() {
|
||||||
setupColors();
|
setupColors();
|
||||||
@@ -128,8 +132,6 @@ public class TerminalPanel extends JPanel {
|
|||||||
setDoubleBuffered(true);
|
setDoubleBuffered(true);
|
||||||
setFocusTraversalKeysEnabled(false);
|
setFocusTraversalKeysEnabled(false);
|
||||||
|
|
||||||
// Use key bindings instead of KeyListener for reliable key handling
|
|
||||||
// This avoids focus/event issues with JScrollPane
|
|
||||||
setOpaque(true);
|
setOpaque(true);
|
||||||
setupKeyBindings();
|
setupKeyBindings();
|
||||||
setupFont();
|
setupFont();
|
||||||
@@ -160,13 +162,17 @@ public class TerminalPanel extends JPanel {
|
|||||||
col = Math.max(0, Math.min(col, displayCols - 1));
|
col = Math.max(0, Math.min(col, displayCols - 1));
|
||||||
row = Math.max(0, Math.min(row, displayRows - 1));
|
row = Math.max(0, Math.min(row, displayRows - 1));
|
||||||
|
|
||||||
// IMPORTANT ARCHITECTURE NOTE:
|
|
||||||
// 1. In GDDM Graphic Cursor Mode (3179G / GOCA), mouse events represent graphic light-pen touches.
|
|
||||||
// Text drag-selection is explicitly suppressed so blue selection boxes do not artifact over graphics.
|
|
||||||
// 2. In Text Light Pen mode (Alt+L on 3270 formatted screens), clicks toggle selectable fields.
|
|
||||||
// 3. In standard alphanumeric mode, click-drag selects text for copy-paste.
|
|
||||||
boolean isGraphic = client.getGocaDecoder() != null && client.getGocaDecoder().isGraphicsCursorActive();
|
boolean isGraphic = client.getGocaDecoder() != null && client.getGocaDecoder().isGraphicsCursorActive();
|
||||||
if (isGraphic || lightPenMode) {
|
boolean isSelectableField = false;
|
||||||
|
if (sb.isFormatted()) {
|
||||||
|
int faPos = sb.findFieldAttribute(row * displayCols + col);
|
||||||
|
if (faPos >= 0) {
|
||||||
|
int fa = sb.getCell(faPos).fa & 0xFF;
|
||||||
|
isSelectableField = haus.nightmare.lib3270j.protocol.DS3270Constants.faIsSelectable(fa) &&
|
||||||
|
!haus.nightmare.lib3270j.protocol.DS3270Constants.faIsZero(fa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isGraphic || lightPenMode || isSelectableField) {
|
||||||
clearSelection();
|
clearSelection();
|
||||||
} else {
|
} else {
|
||||||
selectionStartRow = row;
|
selectionStartRow = row;
|
||||||
@@ -215,8 +221,8 @@ public class TerminalPanel extends JPanel {
|
|||||||
int ox = getRenderOffsetX();
|
int ox = getRenderOffsetX();
|
||||||
int oy = getRenderOffsetY();
|
int oy = getRenderOffsetY();
|
||||||
ScreenBuffer sb = client.getScreenBuffer();
|
ScreenBuffer sb = client.getScreenBuffer();
|
||||||
int gridW = sb.getDisplayCols() * cellWidth;
|
int gridW = (sb != null ? sb.getDisplayCols() : 80) * cellWidth;
|
||||||
int gridH = sb.getDisplayRows() * cellHeight;
|
int gridH = (sb != null ? sb.getDisplayRows() : 24) * cellHeight;
|
||||||
int gWidth = client.getGraphicsPlane().getCanvasWidth();
|
int gWidth = client.getGraphicsPlane().getCanvasWidth();
|
||||||
int gHeight = client.getGraphicsPlane().getCanvasHeight();
|
int gHeight = client.getGraphicsPlane().getCanvasHeight();
|
||||||
int px = (gridW > 0 && gWidth > 0) ? (int) Math.round((double) (e.getX() - ox) * gWidth / gridW) : (e.getX() - ox);
|
int px = (gridW > 0 && gWidth > 0) ? (int) Math.round((double) (e.getX() - ox) * gWidth / gridW) : (e.getX() - ox);
|
||||||
@@ -247,6 +253,7 @@ public class TerminalPanel extends JPanel {
|
|||||||
|
|
||||||
if (isGraphic) {
|
if (isGraphic) {
|
||||||
clearSelection();
|
clearSelection();
|
||||||
|
sb.setCursorAddress(clickAddr);
|
||||||
int gridW = displayCols * cellWidth;
|
int gridW = displayCols * cellWidth;
|
||||||
int gridH = displayRows * cellHeight;
|
int gridH = displayRows * cellHeight;
|
||||||
int gWidth = client.getGraphicsPlane().getCanvasWidth();
|
int gWidth = client.getGraphicsPlane().getCanvasWidth();
|
||||||
@@ -257,14 +264,7 @@ public class TerminalPanel extends JPanel {
|
|||||||
py = Math.max(0, Math.min(py, gHeight - 1));
|
py = Math.max(0, Math.min(py, gHeight - 1));
|
||||||
client.getGocaDecoder().setGraphicCursorFromPixel(px, py);
|
client.getGocaDecoder().setGraphicCursorFromPixel(px, py);
|
||||||
|
|
||||||
int gx = client.getGocaDecoder().getGraphicCursorX();
|
|
||||||
int gy = client.getGocaDecoder().getGraphicCursorY();
|
|
||||||
int button = javax.swing.SwingUtilities.isRightMouseButton(e) ? 2 : 1;
|
int button = javax.swing.SwingUtilities.isRightMouseButton(e) ? 2 : 1;
|
||||||
System.err.println(String.format(
|
|
||||||
"TerminalPanel.mouseReleased: mouse=(%d, %d) offset=(%d, %d) grid=(%dx%d) px=(%d, %d) goca=(%d, %d) btn=%d",
|
|
||||||
e.getX(), e.getY(), ox, oy, gridW, gridH, px, py, gx, gy, button
|
|
||||||
));
|
|
||||||
|
|
||||||
client.getInputProcessor().sendGraphicMouseAid(
|
client.getInputProcessor().sendGraphicMouseAid(
|
||||||
haus.nightmare.lib3270j.protocol.DS3270Constants.AID_ENTER,
|
haus.nightmare.lib3270j.protocol.DS3270Constants.AID_ENTER,
|
||||||
button,
|
button,
|
||||||
@@ -275,13 +275,31 @@ public class TerminalPanel extends JPanel {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Automatic Light-Pen / Selectable Field detection on mouse click:
|
||||||
|
if (sb.isFormatted()) {
|
||||||
|
int faPos = sb.findFieldAttribute(clickAddr);
|
||||||
|
if (faPos >= 0) {
|
||||||
|
int fa = sb.getCell(faPos).fa & 0xFF;
|
||||||
|
if (haus.nightmare.lib3270j.protocol.DS3270Constants.faIsSelectable(fa) &&
|
||||||
|
!haus.nightmare.lib3270j.protocol.DS3270Constants.faIsZero(fa)) {
|
||||||
|
clearSelection();
|
||||||
|
if (client.lightPenSelect(clickAddr)) {
|
||||||
|
refreshScreen();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (lightPenMode) {
|
if (lightPenMode) {
|
||||||
clearSelection();
|
clearSelection();
|
||||||
sb.setCursorAddress(clickAddr);
|
sb.setCursorAddress(clickAddr);
|
||||||
boolean result = client.getInputProcessor().lightPenSelect(clickAddr);
|
boolean result = client.lightPenSelect(clickAddr);
|
||||||
if (result) {
|
if (result) {
|
||||||
refreshScreen();
|
refreshScreen();
|
||||||
return;
|
return;
|
||||||
|
} else {
|
||||||
|
Toolkit.getDefaultToolkit().beep();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,23 +329,42 @@ public class TerminalPanel extends JPanel {
|
|||||||
|
|
||||||
// ========== Selection / Copy-Paste ==========
|
// ========== Selection / Copy-Paste ==========
|
||||||
|
|
||||||
private void clearSelection() {
|
public void clearSelection() {
|
||||||
selectionStartRow = -1;
|
selectionStartRow = -1;
|
||||||
selectionStartCol = -1;
|
selectionStartCol = -1;
|
||||||
selectionEndRow = -1;
|
selectionEndRow = -1;
|
||||||
selectionEndCol = -1;
|
selectionEndCol = -1;
|
||||||
|
repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasSelection() {
|
public void selectAll() {
|
||||||
|
if (client == null) return;
|
||||||
|
ScreenBuffer sb = client.getScreenBuffer();
|
||||||
|
selectionStartRow = 0;
|
||||||
|
selectionStartCol = 0;
|
||||||
|
selectionEndRow = sb.getDisplayRows() - 1;
|
||||||
|
selectionEndCol = sb.getDisplayCols() - 1;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectionRange(int startAddr, int endAddr) {
|
||||||
|
if (client == null) return;
|
||||||
|
ScreenBuffer sb = client.getScreenBuffer();
|
||||||
|
int cols = sb.getDisplayCols();
|
||||||
|
if (cols <= 0) return;
|
||||||
|
|
||||||
|
selectionStartRow = startAddr / cols;
|
||||||
|
selectionStartCol = startAddr % cols;
|
||||||
|
selectionEndRow = endAddr / cols;
|
||||||
|
selectionEndCol = endAddr % cols;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasSelection() {
|
||||||
return selectionStartRow >= 0 && selectionEndRow >= 0 &&
|
return selectionStartRow >= 0 && selectionEndRow >= 0 &&
|
||||||
!(selectionStartRow == selectionEndRow && selectionStartCol == selectionEndCol);
|
!(selectionStartRow == selectionEndRow && selectionStartCol == selectionEndCol);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the selected text from the screen buffer.
|
|
||||||
* In block mode: rectangular selection with newlines between rows.
|
|
||||||
* In line mode: stream selection, flowing left-to-right, top-to-bottom.
|
|
||||||
*/
|
|
||||||
public String getSelectedText() {
|
public String getSelectedText() {
|
||||||
if (!hasSelection() || client == null) return "";
|
if (!hasSelection() || client == null) return "";
|
||||||
ScreenBuffer sb = client.getScreenBuffer();
|
ScreenBuffer sb = client.getScreenBuffer();
|
||||||
@@ -338,7 +375,6 @@ public class TerminalPanel extends JPanel {
|
|||||||
int c1, c2;
|
int c1, c2;
|
||||||
|
|
||||||
if (blockSelectMode) {
|
if (blockSelectMode) {
|
||||||
// Block mode: rectangular selection
|
|
||||||
c1 = Math.min(selectionStartCol, selectionEndCol);
|
c1 = Math.min(selectionStartCol, selectionEndCol);
|
||||||
c2 = Math.max(selectionStartCol, selectionEndCol);
|
c2 = Math.max(selectionStartCol, selectionEndCol);
|
||||||
|
|
||||||
@@ -358,7 +394,6 @@ public class TerminalPanel extends JPanel {
|
|||||||
}
|
}
|
||||||
return result.toString();
|
return result.toString();
|
||||||
} else {
|
} else {
|
||||||
// Line/stream mode: flow from start to end
|
|
||||||
int startAddr, endAddr;
|
int startAddr, endAddr;
|
||||||
if (selectionStartRow < selectionEndRow ||
|
if (selectionStartRow < selectionEndRow ||
|
||||||
(selectionStartRow == selectionEndRow && selectionStartCol <= selectionEndCol)) {
|
(selectionStartRow == selectionEndRow && selectionStartCol <= selectionEndCol)) {
|
||||||
@@ -389,7 +424,7 @@ public class TerminalPanel extends JPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void copySelection() {
|
public void copySelection() {
|
||||||
String text = getSelectedText();
|
String text = getSelectedText();
|
||||||
if (!text.isEmpty()) {
|
if (!text.isEmpty()) {
|
||||||
StringSelection ss = new StringSelection(text);
|
StringSelection ss = new StringSelection(text);
|
||||||
@@ -397,16 +432,24 @@ public class TerminalPanel extends JPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pasteClipboard() {
|
public void pasteClipboard() {
|
||||||
if (client == null || !client.getConnectionState().isFullSession()) return;
|
if (client == null || !client.getConnectionState().isFullSession()) return;
|
||||||
|
if (client.getConnectionState().isNvt()) {
|
||||||
|
try {
|
||||||
|
String text = (String) Toolkit.getDefaultToolkit().getSystemClipboard()
|
||||||
|
.getData(DataFlavor.stringFlavor);
|
||||||
|
if (text != null && !text.isEmpty()) {
|
||||||
|
client.sendNVTString(text);
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
String text = (String) Toolkit.getDefaultToolkit().getSystemClipboard()
|
String text = (String) Toolkit.getDefaultToolkit().getSystemClipboard()
|
||||||
.getData(DataFlavor.stringFlavor);
|
.getData(DataFlavor.stringFlavor);
|
||||||
if (text != null) {
|
if (text != null) {
|
||||||
for (char ch : text.toCharArray()) {
|
for (char ch : text.toCharArray()) {
|
||||||
if (ch == '\n' || ch == '\r') {
|
if (ch == '\n' || ch == '\r') {
|
||||||
// Skip newlines in paste — user may paste multi-line text
|
|
||||||
// but 3270 fields don't wrap the same way
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (ch >= 0x20 && ch != 0x7F) {
|
if (ch >= 0x20 && ch != 0x7F) {
|
||||||
@@ -415,27 +458,62 @@ public class TerminalPanel extends JPanel {
|
|||||||
}
|
}
|
||||||
refreshScreen();
|
refreshScreen();
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ignored) {}
|
||||||
// Clipboard not available or wrong type — silently ignore
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void pasteLineWrap(String text, int endCol, boolean wordWrap) {
|
||||||
|
if (client == null || text == null || text.isEmpty()) return;
|
||||||
|
int curPos = client.getScreenBuffer().getCursorAddress();
|
||||||
|
client.getPS().pasteLineWrap(text, curPos, endCol, wordWrap);
|
||||||
|
refreshScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Search Highlighting ==========
|
||||||
|
|
||||||
|
public void setSearchHighlight(int addr, int len) {
|
||||||
|
this.searchHighlightAddr = addr;
|
||||||
|
this.searchHighlightLen = len;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearSearchHighlight() {
|
||||||
|
this.searchHighlightAddr = -1;
|
||||||
|
this.searchHighlightLen = 0;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isCellSearchHighlighted(int row, int col) {
|
||||||
|
if (searchHighlightAddr < 0 || searchHighlightLen <= 0 || client == null) return false;
|
||||||
|
int cols = client.getScreenBuffer().getDisplayCols();
|
||||||
|
int addr = row * cols + col;
|
||||||
|
return addr >= searchHighlightAddr && addr < searchHighlightAddr + searchHighlightLen;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showContextMenu(MouseEvent e) {
|
private void showContextMenu(MouseEvent e) {
|
||||||
JPopupMenu popup = new JPopupMenu();
|
JPopupMenu popup = new JPopupMenu();
|
||||||
|
ThemeManager.stylePopupMenu(popup);
|
||||||
|
|
||||||
JMenuItem copyItem = new JMenuItem("Copy");
|
JMenuItem copyItem = new JMenuItem("Copy");
|
||||||
|
ThemeManager.styleMenuItem(copyItem);
|
||||||
copyItem.setEnabled(hasSelection());
|
copyItem.setEnabled(hasSelection());
|
||||||
copyItem.addActionListener(ev -> copySelection());
|
copyItem.addActionListener(ev -> copySelection());
|
||||||
popup.add(copyItem);
|
popup.add(copyItem);
|
||||||
|
|
||||||
JMenuItem pasteItem = new JMenuItem("Paste");
|
JMenuItem pasteItem = new JMenuItem("Paste");
|
||||||
|
ThemeManager.styleMenuItem(pasteItem);
|
||||||
pasteItem.setEnabled(client != null && client.getConnectionState().isFullSession());
|
pasteItem.setEnabled(client != null && client.getConnectionState().isFullSession());
|
||||||
pasteItem.addActionListener(ev -> pasteClipboard());
|
pasteItem.addActionListener(ev -> pasteClipboard());
|
||||||
popup.add(pasteItem);
|
popup.add(pasteItem);
|
||||||
|
|
||||||
|
JMenuItem selectAllItem = new JMenuItem("Select All");
|
||||||
|
ThemeManager.styleMenuItem(selectAllItem);
|
||||||
|
selectAllItem.addActionListener(ev -> selectAll());
|
||||||
|
popup.add(selectAllItem);
|
||||||
|
|
||||||
popup.addSeparator();
|
popup.addSeparator();
|
||||||
|
|
||||||
JCheckBoxMenuItem blockModeItem = new JCheckBoxMenuItem("Block Select Mode", blockSelectMode);
|
JCheckBoxMenuItem blockModeItem = new JCheckBoxMenuItem("Block Select Mode", blockSelectMode);
|
||||||
|
ThemeManager.styleMenuItem(blockModeItem);
|
||||||
blockModeItem.addActionListener(ev -> {
|
blockModeItem.addActionListener(ev -> {
|
||||||
blockSelectMode = blockModeItem.isSelected();
|
blockSelectMode = blockModeItem.isSelected();
|
||||||
haus.nightmare.j3270.config.Settings.setBlockSelectMode(blockSelectMode);
|
haus.nightmare.j3270.config.Settings.setBlockSelectMode(blockSelectMode);
|
||||||
@@ -456,7 +534,6 @@ public class TerminalPanel extends JPanel {
|
|||||||
int c2 = Math.max(selectionStartCol, selectionEndCol);
|
int c2 = Math.max(selectionStartCol, selectionEndCol);
|
||||||
return row >= r1 && row <= r2 && col >= c1 && col <= c2;
|
return row >= r1 && row <= r2 && col >= c1 && col <= c2;
|
||||||
} else {
|
} else {
|
||||||
// Stream mode
|
|
||||||
int cols = 80;
|
int cols = 80;
|
||||||
if (client != null) cols = client.getScreenBuffer().getCols();
|
if (client != null) cols = client.getScreenBuffer().getCols();
|
||||||
int addr = row * cols + col;
|
int addr = row * cols + col;
|
||||||
@@ -475,12 +552,6 @@ public class TerminalPanel extends JPanel {
|
|||||||
|
|
||||||
// ========== Font auto-resize ==========
|
// ========== Font auto-resize ==========
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-fit the font size to fill the current panel dimensions
|
|
||||||
* while respecting the terminal model's character grid.
|
|
||||||
* Any leftover pixels are handled by centering the grid
|
|
||||||
* (see getRenderOffsetX/Y).
|
|
||||||
*/
|
|
||||||
private void autoFitFont() {
|
private void autoFitFont() {
|
||||||
int panelW = getWidth();
|
int panelW = getWidth();
|
||||||
int panelH = getHeight();
|
int panelH = getHeight();
|
||||||
@@ -494,12 +565,10 @@ public class TerminalPanel extends JPanel {
|
|||||||
termRows = sb.getDisplayRows();
|
termRows = sb.getDisplayRows();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use minimal padding for the fit calculation
|
|
||||||
int availW = panelW - 2 * padding;
|
int availW = panelW - 2 * padding;
|
||||||
int availH = panelH - 2 * padding;
|
int availH = panelH - 2 * padding;
|
||||||
if (availW <= 0 || availH <= 0) return;
|
if (availW <= 0 || availH <= 0) return;
|
||||||
|
|
||||||
// Find the largest font size where the grid fits
|
|
||||||
int bestSize = 8;
|
int bestSize = 8;
|
||||||
for (int testSize = 8; testSize <= 72; testSize++) {
|
for (int testSize = 8; testSize <= 72; testSize++) {
|
||||||
Font testFont = new Font(terminalFont.getFamily(), Font.PLAIN, testSize);
|
Font testFont = new Font(terminalFont.getFamily(), Font.PLAIN, testSize);
|
||||||
@@ -520,15 +589,9 @@ public class TerminalPanel extends JPanel {
|
|||||||
terminalFont = new Font(terminalFont.getFamily(), Font.PLAIN, bestSize);
|
terminalFont = new Font(terminalFont.getFamily(), Font.PLAIN, bestSize);
|
||||||
updateCellSize();
|
updateCellSize();
|
||||||
}
|
}
|
||||||
// No window snap — any leftover pixels are centered via getRenderOffsetX/Y
|
|
||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by J3270App when it needs to pack the frame (screen size changed,
|
|
||||||
* connection established, etc.). Sets the resize guard to prevent
|
|
||||||
* autoFitFont from firing during the pack.
|
|
||||||
*/
|
|
||||||
public void guardedPack() {
|
public void guardedPack() {
|
||||||
resizeGuard = true;
|
resizeGuard = true;
|
||||||
revalidate();
|
revalidate();
|
||||||
@@ -539,16 +602,10 @@ public class TerminalPanel extends JPanel {
|
|||||||
SwingUtilities.invokeLater(() -> resizeGuard = false);
|
SwingUtilities.invokeLater(() -> resizeGuard = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Use InputMap/ActionMap (key bindings) instead of KeyListener.
|
|
||||||
* This works reliably even inside a JScrollPane — the WHEN_FOCUSED
|
|
||||||
* condition ensures our panel receives all key events when focused.
|
|
||||||
*/
|
|
||||||
private void bindKeyToMap(InputMap im, String action, String bindingStr) {
|
private void bindKeyToMap(InputMap im, String action, String bindingStr) {
|
||||||
if ("UNBOUND".equals(bindingStr) || bindingStr == null || bindingStr.isEmpty()) {
|
if ("UNBOUND".equals(bindingStr) || bindingStr == null || bindingStr.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Support multiple bindings separated by commas
|
|
||||||
String[] bindings = bindingStr.split(",");
|
String[] bindings = bindingStr.split(",");
|
||||||
for (String binding : bindings) {
|
for (String binding : bindings) {
|
||||||
binding = binding.trim();
|
binding = binding.trim();
|
||||||
@@ -568,7 +625,6 @@ public class TerminalPanel extends JPanel {
|
|||||||
im.clear();
|
im.clear();
|
||||||
am.clear();
|
am.clear();
|
||||||
|
|
||||||
// Block the scroll pane from handling Tab, arrows, Page keys
|
|
||||||
String[] navKeys = { "TAB", "shift TAB", "UP", "DOWN", "LEFT", "RIGHT",
|
String[] navKeys = { "TAB", "shift TAB", "UP", "DOWN", "LEFT", "RIGHT",
|
||||||
"PAGE_UP", "PAGE_DOWN", "HOME", "END", "ENTER",
|
"PAGE_UP", "PAGE_DOWN", "HOME", "END", "ENTER",
|
||||||
"ESCAPE", "INSERT", "DELETE", "BACK_SPACE" };
|
"ESCAPE", "INSERT", "DELETE", "BACK_SPACE" };
|
||||||
@@ -578,25 +634,16 @@ public class TerminalPanel extends JPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PF keys 1-24
|
// PF keys 1-24
|
||||||
// PF1-12 default to F1-F12, PF13-24 default to shift F1-shift F12
|
|
||||||
for (int i = 1; i <= 24; i++) {
|
for (int i = 1; i <= 24; i++) {
|
||||||
String defaultBinding;
|
String defaultBinding = i <= 12 ? ("F" + i) : ("shift F" + (i - 12));
|
||||||
if (i <= 12) {
|
|
||||||
defaultBinding = "F" + i;
|
|
||||||
} else {
|
|
||||||
defaultBinding = "shift F" + (i - 12);
|
|
||||||
}
|
|
||||||
String binding = haus.nightmare.j3270.config.Settings.getKeyBinding("PF" + i, defaultBinding);
|
String binding = haus.nightmare.j3270.config.Settings.getKeyBinding("PF" + i, defaultBinding);
|
||||||
bindKeyToMap(im, "PF" + i, binding);
|
bindKeyToMap(im, "PF" + i, binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
// PA keys: Alt+1, Alt+2, Alt+3
|
// PA keys
|
||||||
String pa1Def = "alt 1";
|
bindKeyToMap(im, "PA1", haus.nightmare.j3270.config.Settings.getKeyBinding("PA1", "alt 1"));
|
||||||
String pa2Def = "alt 2";
|
bindKeyToMap(im, "PA2", haus.nightmare.j3270.config.Settings.getKeyBinding("PA2", "alt 2"));
|
||||||
String pa3Def = "alt 3";
|
bindKeyToMap(im, "PA3", haus.nightmare.j3270.config.Settings.getKeyBinding("PA3", "alt 3"));
|
||||||
bindKeyToMap(im, "PA1", haus.nightmare.j3270.config.Settings.getKeyBinding("PA1", pa1Def));
|
|
||||||
bindKeyToMap(im, "PA2", haus.nightmare.j3270.config.Settings.getKeyBinding("PA2", pa2Def));
|
|
||||||
bindKeyToMap(im, "PA3", haus.nightmare.j3270.config.Settings.getKeyBinding("PA3", pa3Def));
|
|
||||||
|
|
||||||
// Clear
|
// Clear
|
||||||
bindKeyToMap(im, "CLEAR", haus.nightmare.j3270.config.Settings.getKeyBinding("CLEAR", "alt C"));
|
bindKeyToMap(im, "CLEAR", haus.nightmare.j3270.config.Settings.getKeyBinding("CLEAR", "alt C"));
|
||||||
@@ -608,14 +655,16 @@ public class TerminalPanel extends JPanel {
|
|||||||
bindKeyToMap(im, "FIELD_MARK", haus.nightmare.j3270.config.Settings.getKeyBinding("FIELD_MARK", "alt M"));
|
bindKeyToMap(im, "FIELD_MARK", haus.nightmare.j3270.config.Settings.getKeyBinding("FIELD_MARK", "alt M"));
|
||||||
bindKeyToMap(im, "ATTN", haus.nightmare.j3270.config.Settings.getKeyBinding("ATTN", "alt A"));
|
bindKeyToMap(im, "ATTN", haus.nightmare.j3270.config.Settings.getKeyBinding("ATTN", "alt A"));
|
||||||
bindKeyToMap(im, "SYSREQ", haus.nightmare.j3270.config.Settings.getKeyBinding("SYSREQ", "alt S"));
|
bindKeyToMap(im, "SYSREQ", haus.nightmare.j3270.config.Settings.getKeyBinding("SYSREQ", "alt S"));
|
||||||
|
bindKeyToMap(im, "CURSEL", haus.nightmare.j3270.config.Settings.getKeyBinding("CURSEL", "alt Q"));
|
||||||
|
|
||||||
// Copy/Paste bindings — Cmd+C / Cmd+V (macOS) or Ctrl+C / Ctrl+V (others)
|
// Copy/Paste/Lightpen bindings
|
||||||
int shortcutMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
|
int shortcutMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
|
||||||
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, shortcutMask), "j3270-COPY");
|
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, shortcutMask), "j3270-COPY");
|
||||||
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_V, shortcutMask), "j3270-PASTE");
|
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_V, shortcutMask), "j3270-PASTE");
|
||||||
|
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, shortcutMask), "j3270-SELECTALL");
|
||||||
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_L, java.awt.event.InputEvent.ALT_DOWN_MASK), "j3270-LIGHTPEN");
|
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_L, java.awt.event.InputEvent.ALT_DOWN_MASK), "j3270-LIGHTPEN");
|
||||||
|
|
||||||
// Create actions for all bound keys
|
// Action map implementations
|
||||||
am.put("j3270-ENTER", createAction(this::handleEnter));
|
am.put("j3270-ENTER", createAction(this::handleEnter));
|
||||||
am.put("j3270-ESCAPE", createAction(this::handleReset));
|
am.put("j3270-ESCAPE", createAction(this::handleReset));
|
||||||
am.put("j3270-TAB", createAction(() -> handleTab(false)));
|
am.put("j3270-TAB", createAction(() -> handleTab(false)));
|
||||||
@@ -638,10 +687,11 @@ public class TerminalPanel extends JPanel {
|
|||||||
am.put("j3270-FIELD_MARK", createAction(this::handleFieldMark));
|
am.put("j3270-FIELD_MARK", createAction(this::handleFieldMark));
|
||||||
am.put("j3270-ATTN", createAction(this::handleAttn));
|
am.put("j3270-ATTN", createAction(this::handleAttn));
|
||||||
am.put("j3270-SYSREQ", createAction(this::handleSysReq));
|
am.put("j3270-SYSREQ", createAction(this::handleSysReq));
|
||||||
|
am.put("j3270-CURSEL", createAction(this::handleCursorSelect));
|
||||||
|
|
||||||
// Copy/Paste actions
|
|
||||||
am.put("j3270-COPY", createAction(this::copySelection));
|
am.put("j3270-COPY", createAction(this::copySelection));
|
||||||
am.put("j3270-PASTE", createAction(this::pasteClipboard));
|
am.put("j3270-PASTE", createAction(this::pasteClipboard));
|
||||||
|
am.put("j3270-SELECTALL", createAction(this::selectAll));
|
||||||
am.put("j3270-LIGHTPEN", createAction(this::toggleLightPen));
|
am.put("j3270-LIGHTPEN", createAction(this::toggleLightPen));
|
||||||
|
|
||||||
for (int i = 1; i <= 24; i++) {
|
for (int i = 1; i <= 24; i++) {
|
||||||
@@ -653,18 +703,78 @@ public class TerminalPanel extends JPanel {
|
|||||||
am.put("j3270-PA2", createAction(() -> handlePA(2)));
|
am.put("j3270-PA2", createAction(() -> handlePA(2)));
|
||||||
am.put("j3270-PA3", createAction(() -> handlePA(3)));
|
am.put("j3270-PA3", createAction(() -> handlePA(3)));
|
||||||
|
|
||||||
// For printable character input, we override processKeyEvent
|
|
||||||
enableEvents(AWTEvent.KEY_EVENT_MASK);
|
enableEvents(AWTEvent.KEY_EVENT_MASK);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void processKeyEvent(KeyEvent e) {
|
protected void processKeyEvent(KeyEvent e) {
|
||||||
// Handle character typing via processKeyEvent to capture ALL typed chars
|
if (client != null) {
|
||||||
|
ConnectionState state = client.getConnectionState();
|
||||||
|
if (state.isNvt()) {
|
||||||
if (e.getID() == KeyEvent.KEY_TYPED) {
|
if (e.getID() == KeyEvent.KEY_TYPED) {
|
||||||
char ch = e.getKeyChar();
|
char ch = e.getKeyChar();
|
||||||
if (ch >= 0x20 && ch != 0x7F && ch != KeyEvent.CHAR_UNDEFINED
|
if (ch >= 0x20 && ch != 0x7F && ch != KeyEvent.CHAR_UNDEFINED
|
||||||
&& !e.isControlDown() && !e.isAltDown() && !e.isMetaDown()) {
|
&& !e.isControlDown() && !e.isAltDown() && !e.isMetaDown()) {
|
||||||
if (client != null && client.getConnectionState().isFullSession()) {
|
try {
|
||||||
|
client.sendNVTChar(ch);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
e.consume();
|
||||||
|
return;
|
||||||
|
} else if (e.isControlDown() && !e.isAltDown() && !e.isMetaDown()) {
|
||||||
|
if (ch > 0 && ch < 0x20) {
|
||||||
|
try {
|
||||||
|
client.sendNVTChar(ch);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
e.consume();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (e.getID() == KeyEvent.KEY_PRESSED) {
|
||||||
|
if (client.getNvtProcessor() != null && client.getNvtProcessor().isApplicationKeypad()) {
|
||||||
|
String kp = client.getNvtProcessor().mapKeypadKey(e.getKeyCode());
|
||||||
|
if (kp != null) {
|
||||||
|
try {
|
||||||
|
client.sendNVTString(kp);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
e.consume();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (e.isControlDown() && !e.isAltDown() && !e.isMetaDown()) {
|
||||||
|
int code = e.getKeyCode();
|
||||||
|
if (code >= KeyEvent.VK_A && code <= KeyEvent.VK_Z) {
|
||||||
|
char ctrlChar = (char) (code - KeyEvent.VK_A + 1);
|
||||||
|
try {
|
||||||
|
client.sendNVTChar(ctrlChar);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
e.consume();
|
||||||
|
return;
|
||||||
|
} else if (code == KeyEvent.VK_OPEN_BRACKET) { // Ctrl+[ = ESC
|
||||||
|
try {
|
||||||
|
client.sendNVTChar('\u001B');
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
e.consume();
|
||||||
|
return;
|
||||||
|
} else if (code == KeyEvent.VK_BACK_SLASH) { // Ctrl+\ = FS
|
||||||
|
try {
|
||||||
|
client.sendNVTChar('\u001C');
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
e.consume();
|
||||||
|
return;
|
||||||
|
} else if (code == KeyEvent.VK_CLOSE_BRACKET) { // Ctrl+] = GS
|
||||||
|
try {
|
||||||
|
client.sendNVTChar('\u001D');
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
e.consume();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (e.getID() == KeyEvent.KEY_TYPED) {
|
||||||
|
char ch = e.getKeyChar();
|
||||||
|
if (ch >= 0x20 && ch != 0x7F && ch != KeyEvent.CHAR_UNDEFINED
|
||||||
|
&& !e.isControlDown() && !e.isAltDown() && !e.isMetaDown()) {
|
||||||
|
if (state.isFullSession()) {
|
||||||
client.typeCharacter(ch);
|
client.typeCharacter(ch);
|
||||||
refreshScreen();
|
refreshScreen();
|
||||||
e.consume();
|
e.consume();
|
||||||
@@ -672,6 +782,7 @@ public class TerminalPanel extends JPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
super.processKeyEvent(e);
|
super.processKeyEvent(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -687,14 +798,29 @@ public class TerminalPanel extends JPanel {
|
|||||||
// ========== Key action handlers ==========
|
// ========== Key action handlers ==========
|
||||||
|
|
||||||
private void handleEnter() {
|
private void handleEnter() {
|
||||||
if (client != null && client.getConnectionState().isFullSession()) {
|
if (client != null) {
|
||||||
|
ConnectionState state = client.getConnectionState();
|
||||||
|
if (state.isNvt()) {
|
||||||
|
try {
|
||||||
|
client.sendNVTString("\r\n");
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
} else if (state.isFullSession()) {
|
||||||
client.sendEnter();
|
client.sendEnter();
|
||||||
refreshScreen();
|
refreshScreen();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void handleReset() {
|
private void handleReset() {
|
||||||
if (client != null) {
|
if (client != null) {
|
||||||
|
if (client.getConnectionState().isNvt()) {
|
||||||
|
try {
|
||||||
|
client.sendNVTChar('\u001B');
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clearSearchHighlight();
|
||||||
|
clearSelection();
|
||||||
client.reset();
|
client.reset();
|
||||||
refreshScreen();
|
refreshScreen();
|
||||||
}
|
}
|
||||||
@@ -702,32 +828,50 @@ public class TerminalPanel extends JPanel {
|
|||||||
|
|
||||||
private void handleTab(boolean shift) {
|
private void handleTab(boolean shift) {
|
||||||
if (client != null && client.getConnectionState().isFullSession()) {
|
if (client != null && client.getConnectionState().isFullSession()) {
|
||||||
if (shift)
|
if (client.getConnectionState().isNvt()) {
|
||||||
client.backTab();
|
try {
|
||||||
else
|
if (shift) {
|
||||||
client.tab();
|
client.sendNVTString("\u001B[Z");
|
||||||
|
} else {
|
||||||
|
client.sendNVTChar('\t');
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (shift) client.backTab();
|
||||||
|
else client.tab();
|
||||||
refreshScreen();
|
refreshScreen();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleCursor(String dir) {
|
private void handleCursor(String dir) {
|
||||||
if (client != null && client.getConnectionState().isFullSession()) {
|
if (client != null && client.getConnectionState().isFullSession()) {
|
||||||
|
if (client.getConnectionState().isNvt()) {
|
||||||
|
try {
|
||||||
|
int vk = KeyEvent.VK_UP;
|
||||||
|
if ("down".equals(dir)) vk = KeyEvent.VK_DOWN;
|
||||||
|
else if ("left".equals(dir)) vk = KeyEvent.VK_LEFT;
|
||||||
|
else if ("right".equals(dir)) vk = KeyEvent.VK_RIGHT;
|
||||||
|
else if ("home".equals(dir)) vk = KeyEvent.VK_HOME;
|
||||||
|
boolean handled = client.sendNVTKey(vk, '\0', false, false, false);
|
||||||
|
if (!handled) {
|
||||||
switch (dir) {
|
switch (dir) {
|
||||||
case "up":
|
case "up": client.sendNVTString("\u001B[A"); break;
|
||||||
client.cursorUp();
|
case "down": client.sendNVTString("\u001B[B"); break;
|
||||||
break;
|
case "left": client.sendNVTString("\u001B[D"); break;
|
||||||
case "down":
|
case "right": client.sendNVTString("\u001B[C"); break;
|
||||||
client.cursorDown();
|
case "home": client.sendNVTString("\u001B[H"); break;
|
||||||
break;
|
}
|
||||||
case "left":
|
}
|
||||||
client.cursorLeft();
|
} catch (Exception ignored) {}
|
||||||
break;
|
return;
|
||||||
case "right":
|
}
|
||||||
client.cursorRight();
|
switch (dir) {
|
||||||
break;
|
case "up": client.cursorUp(); break;
|
||||||
case "home":
|
case "down": client.cursorDown(); break;
|
||||||
client.cursorHome();
|
case "left": client.cursorLeft(); break;
|
||||||
break;
|
case "right": client.cursorRight(); break;
|
||||||
|
case "home": client.cursorHome(); break;
|
||||||
}
|
}
|
||||||
refreshScreen();
|
refreshScreen();
|
||||||
}
|
}
|
||||||
@@ -735,11 +879,39 @@ public class TerminalPanel extends JPanel {
|
|||||||
|
|
||||||
private void handlePF(int n) {
|
private void handlePF(int n) {
|
||||||
if (client != null && client.getConnectionState().isFullSession()) {
|
if (client != null && client.getConnectionState().isFullSession()) {
|
||||||
|
if (client.getConnectionState().isNvt()) {
|
||||||
|
try {
|
||||||
|
if (client.getNvtProcessor() != null) {
|
||||||
|
client.sendNVTString(client.getNvtProcessor().getFunctionKeySequence(n));
|
||||||
|
} else {
|
||||||
|
client.sendNVTString(getNvtFunctionKeySequence(n));
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
return;
|
||||||
|
}
|
||||||
client.sendPF(n);
|
client.sendPF(n);
|
||||||
refreshScreen();
|
refreshScreen();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getNvtFunctionKeySequence(int n) {
|
||||||
|
switch (n) {
|
||||||
|
case 1: return "\u001BOP";
|
||||||
|
case 2: return "\u001BOQ";
|
||||||
|
case 3: return "\u001BOR";
|
||||||
|
case 4: return "\u001BOS";
|
||||||
|
case 5: return "\u001B[15~";
|
||||||
|
case 6: return "\u001B[17~";
|
||||||
|
case 7: return "\u001B[18~";
|
||||||
|
case 8: return "\u001B[19~";
|
||||||
|
case 9: return "\u001B[20~";
|
||||||
|
case 10: return "\u001B[21~";
|
||||||
|
case 11: return "\u001B[23~";
|
||||||
|
case 12: return "\u001B[24~";
|
||||||
|
default: return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void handlePA(int n) {
|
private void handlePA(int n) {
|
||||||
if (client != null && client.getConnectionState().isFullSession()) {
|
if (client != null && client.getConnectionState().isFullSession()) {
|
||||||
client.sendPA(n);
|
client.sendPA(n);
|
||||||
@@ -749,6 +921,12 @@ public class TerminalPanel extends JPanel {
|
|||||||
|
|
||||||
private void handleEraseEOF() {
|
private void handleEraseEOF() {
|
||||||
if (client != null && client.getConnectionState().isFullSession()) {
|
if (client != null && client.getConnectionState().isFullSession()) {
|
||||||
|
if (client.getConnectionState().isNvt()) {
|
||||||
|
try {
|
||||||
|
client.sendNVTString("\u001B[F");
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
return;
|
||||||
|
}
|
||||||
client.getInputProcessor().eraseEof();
|
client.getInputProcessor().eraseEof();
|
||||||
refreshScreen();
|
refreshScreen();
|
||||||
}
|
}
|
||||||
@@ -756,17 +934,30 @@ public class TerminalPanel extends JPanel {
|
|||||||
|
|
||||||
private void handleDelete() {
|
private void handleDelete() {
|
||||||
if (client != null && client.getConnectionState().isFullSession()) {
|
if (client != null && client.getConnectionState().isFullSession()) {
|
||||||
|
if (client.getConnectionState().isNvt()) {
|
||||||
|
try {
|
||||||
|
client.sendNVTString("\u001B[3~");
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
return;
|
||||||
|
}
|
||||||
client.getInputProcessor().deleteChar();
|
client.getInputProcessor().deleteChar();
|
||||||
refreshScreen();
|
refreshScreen();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleBackspace() {
|
private void handleBackspace() {
|
||||||
if (client != null && client.getConnectionState().isFullSession()) {
|
if (client != null) {
|
||||||
|
ConnectionState state = client.getConnectionState();
|
||||||
|
if (state.isNvt()) {
|
||||||
|
try {
|
||||||
|
client.sendNVTChar('\b');
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
} else if (state.isFullSession()) {
|
||||||
client.getInputProcessor().backspace();
|
client.getInputProcessor().backspace();
|
||||||
refreshScreen();
|
refreshScreen();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void handleInsert() {
|
private void handleInsert() {
|
||||||
if (client != null) {
|
if (client != null) {
|
||||||
@@ -778,6 +969,12 @@ public class TerminalPanel extends JPanel {
|
|||||||
|
|
||||||
private void handleClear() {
|
private void handleClear() {
|
||||||
if (client != null && client.getConnectionState().isFullSession()) {
|
if (client != null && client.getConnectionState().isFullSession()) {
|
||||||
|
if (client.getConnectionState().isNvt()) {
|
||||||
|
try {
|
||||||
|
client.sendNVTChar('\u000C');
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
return;
|
||||||
|
}
|
||||||
client.sendClear();
|
client.sendClear();
|
||||||
refreshScreen();
|
refreshScreen();
|
||||||
}
|
}
|
||||||
@@ -824,12 +1021,22 @@ public class TerminalPanel extends JPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshScreen() {
|
private void handleCursorSelect() {
|
||||||
|
if (client != null && client.getConnectionState().isFullSession()) {
|
||||||
|
boolean selected = client.cursorSelect();
|
||||||
|
if (selected) {
|
||||||
|
refreshScreen();
|
||||||
|
} else {
|
||||||
|
Toolkit.getDefaultToolkit().beep();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshScreen() {
|
||||||
if (client != null) {
|
if (client != null) {
|
||||||
client.getScreenBuffer().updateDisplaySnapshot();
|
client.getScreenBuffer().updateDisplaySnapshot();
|
||||||
}
|
}
|
||||||
repaint();
|
repaint();
|
||||||
// Notify parent to update status bar too
|
|
||||||
Container parent = getParent();
|
Container parent = getParent();
|
||||||
while (parent != null) {
|
while (parent != null) {
|
||||||
if (parent instanceof JFrame) {
|
if (parent instanceof JFrame) {
|
||||||
@@ -846,7 +1053,6 @@ public class TerminalPanel extends JPanel {
|
|||||||
|
|
||||||
terminalFont = new Font(fontFamily, Font.PLAIN, currentFontSize);
|
terminalFont = new Font(fontFamily, Font.PLAIN, currentFontSize);
|
||||||
if (terminalFont.getFamily().equals("Dialog") && !fontFamily.equals("Dialog")) {
|
if (terminalFont.getFamily().equals("Dialog") && !fontFamily.equals("Dialog")) {
|
||||||
// Fallback
|
|
||||||
terminalFont = new Font(Font.MONOSPACED, Font.PLAIN, currentFontSize);
|
terminalFont = new Font(Font.MONOSPACED, Font.PLAIN, currentFontSize);
|
||||||
}
|
}
|
||||||
updateCellSize();
|
updateCellSize();
|
||||||
@@ -909,6 +1115,37 @@ public class TerminalPanel extends JPanel {
|
|||||||
if (client != null) {
|
if (client != null) {
|
||||||
setupGraphicsPlaneRenderer();
|
setupGraphicsPlaneRenderer();
|
||||||
updateCellSize();
|
updateCellSize();
|
||||||
|
|
||||||
|
client.setNvtClipboardHandler(new haus.nightmare.lib3270j.nvt.NvtProcessor.ClipboardHandler() {
|
||||||
|
@Override
|
||||||
|
public String getClipboardText() {
|
||||||
|
try {
|
||||||
|
return (String) Toolkit.getDefaultToolkit().getSystemClipboard()
|
||||||
|
.getData(DataFlavor.stringFlavor);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setClipboardText(String text) {
|
||||||
|
if (text != null) {
|
||||||
|
try {
|
||||||
|
StringSelection ss = new StringSelection(text);
|
||||||
|
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss, null);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.addNvtTitleListener(title -> {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
Container top = getTopLevelAncestor();
|
||||||
|
if (top instanceof JFrame) {
|
||||||
|
((JFrame) top).setTitle(title != null && !title.isEmpty() ? ("j3270 - " + title) : "j3270");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -930,8 +1167,8 @@ public class TerminalPanel extends JPanel {
|
|||||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
|
g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
|
||||||
|
|
||||||
int fontSize = (int) Math.round(ch * 0.95);
|
int fontSize = Math.min((int) Math.round(ch * 0.82), (int) Math.round(cw * 1.45));
|
||||||
if (fontSize < 10) fontSize = 10;
|
if (fontSize < 9) fontSize = 9;
|
||||||
Font f = (boldTerminalFont != null ? boldTerminalFont : terminalFont).deriveFont((float) fontSize);
|
Font f = (boldTerminalFont != null ? boldTerminalFont : terminalFont).deriveFont((float) fontSize);
|
||||||
g2.setFont(f);
|
g2.setFont(f);
|
||||||
FontMetrics fm = g2.getFontMetrics();
|
FontMetrics fm = g2.getFontMetrics();
|
||||||
@@ -941,11 +1178,17 @@ public class TerminalPanel extends JPanel {
|
|||||||
|
|
||||||
double curX = x;
|
double curX = x;
|
||||||
double curY = y;
|
double curY = y;
|
||||||
|
if (dir == GocaConstants.CD_TB) {
|
||||||
|
curY += ch;
|
||||||
|
} else if (dir == GocaConstants.CD_RL) {
|
||||||
|
curX -= cw;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < text.length(); i++) {
|
for (int i = 0; i < text.length(); i++) {
|
||||||
String s = text.substring(i, i + 1);
|
String s = text.substring(i, i + 1);
|
||||||
int charW = fm.stringWidth(s);
|
int charW = fm.stringWidth(s);
|
||||||
int drawX = (int) Math.round(curX + Math.max(0, (cw - charW) / 2.0));
|
int drawX = (int) Math.round(curX + Math.max(0, (cw - charW) / 2.0));
|
||||||
int drawY = (int) Math.round(curY + ascent + Math.max(0, (ch - fm.getHeight()) / 2.0));
|
int drawY = (int) Math.round(curY);
|
||||||
g2.drawString(s, drawX, drawY);
|
g2.drawString(s, drawX, drawY);
|
||||||
|
|
||||||
switch (dir) {
|
switch (dir) {
|
||||||
@@ -969,9 +1212,6 @@ public class TerminalPanel extends JPanel {
|
|||||||
public Dimension getPreferredSize() {
|
public Dimension getPreferredSize() {
|
||||||
if (client != null) {
|
if (client != null) {
|
||||||
ScreenBuffer sb = client.getScreenBuffer();
|
ScreenBuffer sb = client.getScreenBuffer();
|
||||||
// Use the CURRENT screen dimensions (not max) to eliminate extra space.
|
|
||||||
// When the host switches to alternate screen, onScreenSizeChanged fires
|
|
||||||
// and the frame re-packs.
|
|
||||||
int displayCols = sb.getDisplayCols();
|
int displayCols = sb.getDisplayCols();
|
||||||
int displayRows = sb.getDisplayRows();
|
int displayRows = sb.getDisplayRows();
|
||||||
return new Dimension(displayCols * cellWidth + padding * 2,
|
return new Dimension(displayCols * cellWidth + padding * 2,
|
||||||
@@ -996,11 +1236,9 @@ public class TerminalPanel extends JPanel {
|
|||||||
g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
|
g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
|
||||||
g2.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
|
g2.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
|
||||||
|
|
||||||
// Clear entire panel with background color
|
|
||||||
g2.setColor(bgColor);
|
g2.setColor(bgColor);
|
||||||
g2.fillRect(0, 0, getWidth(), getHeight());
|
g2.fillRect(0, 0, getWidth(), getHeight());
|
||||||
|
|
||||||
// Compute centered offsets for the grid
|
|
||||||
int ox = getRenderOffsetX();
|
int ox = getRenderOffsetX();
|
||||||
int oy = getRenderOffsetY();
|
int oy = getRenderOffsetY();
|
||||||
|
|
||||||
@@ -1031,7 +1269,6 @@ public class TerminalPanel extends JPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track current field attribute for monochrome color decisions
|
|
||||||
byte currentFA = 0;
|
byte currentFA = 0;
|
||||||
ExtendedAttribute currentFieldEa = null;
|
ExtendedAttribute currentFieldEa = null;
|
||||||
|
|
||||||
@@ -1043,7 +1280,6 @@ public class TerminalPanel extends JPanel {
|
|||||||
int x = ox + col * cellWidth;
|
int x = ox + col * cellWidth;
|
||||||
int y = oy + row * cellHeight;
|
int y = oy + row * cellHeight;
|
||||||
|
|
||||||
// Determine colors and attributes
|
|
||||||
Color fgColor;
|
Color fgColor;
|
||||||
Color bgColor;
|
Color bgColor;
|
||||||
boolean bold = false;
|
boolean bold = false;
|
||||||
@@ -1053,7 +1289,6 @@ public class TerminalPanel extends JPanel {
|
|||||||
if (ea.isFieldAttribute()) {
|
if (ea.isFieldAttribute()) {
|
||||||
currentFA = ea.fa;
|
currentFA = ea.fa;
|
||||||
currentFieldEa = ea;
|
currentFieldEa = ea;
|
||||||
// Selection highlight on field attribute cells
|
|
||||||
if (isCellSelected(row, col)) {
|
if (isCellSelected(row, col)) {
|
||||||
g2.setColor(SELECTION_COLOR);
|
g2.setColor(SELECTION_COLOR);
|
||||||
g2.fillRect(x, y, cellWidth, cellHeight);
|
g2.fillRect(x, y, cellWidth, cellHeight);
|
||||||
@@ -1073,20 +1308,14 @@ public class TerminalPanel extends JPanel {
|
|||||||
// Graphics rendition
|
// Graphics rendition
|
||||||
byte gr = ea.gr != 0 ? ea.gr : (currentFieldEa != null ? currentFieldEa.gr : 0);
|
byte gr = ea.gr != 0 ? ea.gr : (currentFieldEa != null ? currentFieldEa.gr : 0);
|
||||||
if (gr != 0) {
|
if (gr != 0) {
|
||||||
if ((gr & GR_INTENSIFY) != 0)
|
if ((gr & GR_INTENSIFY) != 0) bold = true;
|
||||||
bold = true;
|
if ((gr & GR_UNDERLINE) != 0) underline = true;
|
||||||
if ((gr & GR_UNDERLINE) != 0)
|
if ((gr & GR_REVERSE) != 0) reverse = true;
|
||||||
underline = true;
|
|
||||||
if ((gr & GR_REVERSE) != 0)
|
|
||||||
reverse = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Field attribute implicit intensify
|
if (faIsHigh(currentFA & 0xFF)) bold = true;
|
||||||
if (faIsHigh(currentFA & 0xFF))
|
|
||||||
bold = true;
|
|
||||||
|
|
||||||
// Handle invisible fields (zero intensity / password fields)
|
// Password fields
|
||||||
// Modern UX: render '*' for typed characters so user sees length/digit count
|
|
||||||
if (faIsZero(currentFA & 0xFF)) {
|
if (faIsZero(currentFA & 0xFF)) {
|
||||||
char ch = ea.ucs4;
|
char ch = ea.ucs4;
|
||||||
if (ch > 0x20 && ch != 0xFF) {
|
if (ch > 0x20 && ch != 0xFF) {
|
||||||
@@ -1103,14 +1332,12 @@ public class TerminalPanel extends JPanel {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply reverse video
|
|
||||||
if (reverse) {
|
if (reverse) {
|
||||||
Color tmp = fgColor;
|
Color tmp = fgColor;
|
||||||
fgColor = bgColor;
|
fgColor = bgColor;
|
||||||
bgColor = tmp;
|
bgColor = tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw background only if different from default panel bgColor or if inverted
|
|
||||||
if (!bgColor.equals(this.bgColor) || reverse) {
|
if (!bgColor.equals(this.bgColor) || reverse) {
|
||||||
g2.setColor(bgColor);
|
g2.setColor(bgColor);
|
||||||
g2.fillRect(x, y, cellWidth, cellHeight);
|
g2.fillRect(x, y, cellWidth, cellHeight);
|
||||||
@@ -1137,19 +1364,25 @@ public class TerminalPanel extends JPanel {
|
|||||||
Font f = bold ? boldTerminalFont : terminalFont;
|
Font f = bold ? boldTerminalFont : terminalFont;
|
||||||
g2.setFont(f);
|
g2.setFont(f);
|
||||||
g2.setColor(fgColor);
|
g2.setColor(fgColor);
|
||||||
|
int textY = y + fontAscent + Math.max(0, (cellHeight - (fontAscent + fontDescent)) / 2);
|
||||||
if (ch < 128) {
|
if (ch < 128) {
|
||||||
g2.drawString(CHAR_STRINGS[ch], x, y + fontAscent);
|
g2.drawString(CHAR_STRINGS[ch], x, textY);
|
||||||
} else {
|
} else {
|
||||||
g2.drawString(String.valueOf(ch), x, y + fontAscent);
|
g2.drawString(String.valueOf(ch), x, textY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw underline
|
|
||||||
if (underline) {
|
if (underline) {
|
||||||
g2.setColor(fgColor);
|
g2.setColor(fgColor);
|
||||||
g2.drawLine(x, y + cellHeight - fontDescent,
|
int ulY = Math.min(y + cellHeight - 1, y + fontAscent + Math.max(0, (cellHeight - (fontAscent + fontDescent)) / 2) + 2);
|
||||||
x + cellWidth - 1, y + cellHeight - fontDescent);
|
g2.drawLine(x, ulY, x + cellWidth - 1, ulY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw search highlight
|
||||||
|
if (isCellSearchHighlighted(row, col)) {
|
||||||
|
g2.setColor(SEARCH_HIGHLIGHT_COLOR);
|
||||||
|
g2.fillRect(x, y, cellWidth, cellHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw selection highlight
|
// Draw selection highlight
|
||||||
@@ -1160,7 +1393,7 @@ public class TerminalPanel extends JPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw Graphic Cursor (Light-Pen / interactive graphics pointer) if active
|
// Draw Graphic Cursor if active
|
||||||
if (client.getGocaDecoder() != null && client.getGocaDecoder().isGraphicsCursorActive() && client.getGraphicsPlane() != null) {
|
if (client.getGocaDecoder() != null && client.getGocaDecoder().isGraphicsCursorActive() && client.getGraphicsPlane() != null) {
|
||||||
int gocaX = client.getGocaDecoder().getGraphicCursorX();
|
int gocaX = client.getGocaDecoder().getGraphicCursorX();
|
||||||
int gocaY = client.getGocaDecoder().getGraphicCursorY();
|
int gocaY = client.getGocaDecoder().getGraphicCursorY();
|
||||||
@@ -1175,7 +1408,6 @@ public class TerminalPanel extends JPanel {
|
|||||||
|
|
||||||
g2.setColor(Color.WHITE);
|
g2.setColor(Color.WHITE);
|
||||||
g2.setXORMode(Color.BLACK);
|
g2.setXORMode(Color.BLACK);
|
||||||
// Draw a crosshair cursor for the light-pen / graphic cursor
|
|
||||||
g2.drawLine(px - 6, py, px + 6, py);
|
g2.drawLine(px - 6, py, px + 6, py);
|
||||||
g2.drawLine(px, py - 6, px, py + 6);
|
g2.drawLine(px, py - 6, px, py + 6);
|
||||||
g2.setPaintMode();
|
g2.setPaintMode();
|
||||||
@@ -1194,13 +1426,6 @@ public class TerminalPanel extends JPanel {
|
|||||||
g2.fillRect(cx, cy, cellWidth, cellHeight);
|
g2.fillRect(cx, cy, cellWidth, cellHeight);
|
||||||
g2.setPaintMode();
|
g2.setPaintMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw Light Pen mode indicator
|
|
||||||
if (lightPenMode) {
|
|
||||||
g2.setFont(new Font(Font.MONOSPACED, Font.BOLD, 12));
|
|
||||||
g2.setColor(new Color(50, 255, 50));
|
|
||||||
g2.drawString("LP", ox + 2, oy + rows * cellHeight + 14);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Color getColorForAttribute(ExtendedAttribute ea, ExtendedAttribute currentFieldEa, byte currentFA) {
|
private Color getColorForAttribute(ExtendedAttribute ea, ExtendedAttribute currentFieldEa, byte currentFA) {
|
||||||
@@ -1220,8 +1445,6 @@ public class TerminalPanel extends JPanel {
|
|||||||
: (currentFieldEa != null && currentFieldEa.bg != 0 ? (currentFieldEa.bg & 0xFF) : 0);
|
: (currentFieldEa != null && currentFieldEa.bg != 0 ? (currentFieldEa.bg & 0xFF) : 0);
|
||||||
if (bg >= 0xf0 && bg <= 0xff) {
|
if (bg >= 0xf0 && bg <= 0xff) {
|
||||||
int idx = bg - 0xf0;
|
int idx = bg - 0xf0;
|
||||||
// Neutral black (0xf0) and black (0xf8) should use window bgColor
|
|
||||||
// to avoid visible seams between field bg and window bg
|
|
||||||
if (idx == HOST_COLOR_NEUTRAL_BLACK || idx == HOST_COLOR_BLACK) {
|
if (idx == HOST_COLOR_NEUTRAL_BLACK || idx == HOST_COLOR_BLACK) {
|
||||||
return bgColor;
|
return bgColor;
|
||||||
}
|
}
|
||||||
@@ -1241,22 +1464,26 @@ public class TerminalPanel extends JPanel {
|
|||||||
if (blinkTimer != null)
|
if (blinkTimer != null)
|
||||||
blinkTimer.stop();
|
blinkTimer.stop();
|
||||||
}
|
}
|
||||||
private Runnable onLightPenToggle;
|
|
||||||
|
|
||||||
public void setOnLightPenToggle(Runnable callback) {
|
|
||||||
this.onLightPenToggle = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void toggleLightPen() {
|
public void toggleLightPen() {
|
||||||
this.lightPenMode = !this.lightPenMode;
|
this.lightPenMode = !this.lightPenMode;
|
||||||
System.out.println("Light Pen mode: " + (this.lightPenMode ? "ON" : "OFF"));
|
|
||||||
if (onLightPenToggle != null) {
|
|
||||||
onLightPenToggle.run();
|
|
||||||
}
|
|
||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLightPenMode() {
|
public boolean isLightPenMode() {
|
||||||
return this.lightPenMode;
|
return this.lightPenMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int print(Graphics g, java.awt.print.PageFormat pageFormat, int pageIndex) {
|
||||||
|
if (pageIndex > 0) return NO_SUCH_PAGE;
|
||||||
|
Graphics2D g2d = (Graphics2D) g;
|
||||||
|
g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
|
||||||
|
double scaleX = pageFormat.getImageableWidth() / (double) Math.max(1, getWidth());
|
||||||
|
double scaleY = pageFormat.getImageableHeight() / (double) Math.max(1, getHeight());
|
||||||
|
double scale = Math.min(scaleX, scaleY);
|
||||||
|
g2d.scale(scale, scale);
|
||||||
|
paint(g2d);
|
||||||
|
return PAGE_EXISTS;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,955 @@
|
|||||||
|
package haus.nightmare.j3270.ui;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.Border;
|
||||||
|
import javax.swing.border.CompoundBorder;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import javax.swing.border.LineBorder;
|
||||||
|
import javax.swing.border.TitledBorder;
|
||||||
|
import javax.swing.plaf.basic.BasicButtonUI;
|
||||||
|
import javax.swing.plaf.basic.BasicMenuBarUI;
|
||||||
|
import javax.swing.plaf.basic.BasicMenuItemUI;
|
||||||
|
import javax.swing.plaf.basic.BasicMenuUI;
|
||||||
|
import javax.swing.plaf.basic.BasicPopupMenuUI;
|
||||||
|
import javax.swing.plaf.basic.BasicTabbedPaneUI;
|
||||||
|
import javax.swing.table.DefaultTableCellRenderer;
|
||||||
|
import javax.swing.table.JTableHeader;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Centralized theme manager for j3270 Java desktop UI.
|
||||||
|
* Provides high-contrast, platform-independent Light and Dark theme palettes,
|
||||||
|
* custom Swing UI delegates, and recursive styling helpers for all dialogs and menus.
|
||||||
|
*/
|
||||||
|
public final class ThemeManager {
|
||||||
|
|
||||||
|
private static UITheme currentTheme = UITheme.DARK;
|
||||||
|
private static final List<Consumer<UITheme>> themeChangeListeners = new ArrayList<>();
|
||||||
|
|
||||||
|
private ThemeManager() {}
|
||||||
|
|
||||||
|
public static UITheme getTheme() {
|
||||||
|
return currentTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isDark() {
|
||||||
|
return currentTheme == UITheme.DARK;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isLight() {
|
||||||
|
return currentTheme == UITheme.LIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setTheme(UITheme theme) {
|
||||||
|
if (theme == null) theme = UITheme.DARK;
|
||||||
|
if (currentTheme != theme) {
|
||||||
|
currentTheme = theme;
|
||||||
|
applyUIManagerDefaults(theme);
|
||||||
|
for (Consumer<UITheme> listener : new ArrayList<>(themeChangeListeners)) {
|
||||||
|
try {
|
||||||
|
listener.accept(theme);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addThemeChangeListener(Consumer<UITheme> listener) {
|
||||||
|
if (listener != null && !themeChangeListeners.contains(listener)) {
|
||||||
|
themeChangeListeners.add(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void removeThemeChangeListener(Consumer<UITheme> listener) {
|
||||||
|
themeChangeListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Color Palette Tokens
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
public static Color getBgMain() { return getBgMain(currentTheme); }
|
||||||
|
public static Color getBgMain(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(28, 28, 28) : new Color(242, 242, 244);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getBgPanel() { return getBgPanel(currentTheme); }
|
||||||
|
public static Color getBgPanel(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(36, 36, 36) : new Color(250, 250, 252);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getBgComponent() { return getBgComponent(currentTheme); }
|
||||||
|
public static Color getBgComponent(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(46, 46, 46) : Color.WHITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getBgComponentLighter() { return getBgComponentLighter(currentTheme); }
|
||||||
|
public static Color getBgComponentLighter(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(56, 56, 56) : new Color(236, 236, 238);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getFgMain() { return getFgMain(currentTheme); }
|
||||||
|
public static Color getFgMain(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(238, 238, 238) : new Color(24, 24, 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getFgMuted() { return getFgMuted(currentTheme); }
|
||||||
|
public static Color getFgMuted(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(175, 175, 175) : new Color(90, 90, 90);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getFgDisabled() { return getFgDisabled(currentTheme); }
|
||||||
|
public static Color getFgDisabled(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(110, 110, 110) : new Color(160, 160, 160);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getBorder() { return getBorder(currentTheme); }
|
||||||
|
public static Color getBorder(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(65, 65, 65) : new Color(205, 205, 205);
|
||||||
|
}
|
||||||
|
public static Color getBorderColor() { return getBorder(currentTheme); }
|
||||||
|
public static Color getBorderColor(UITheme t) { return getBorder(t); }
|
||||||
|
|
||||||
|
public static Color getBorderFocused() { return getBorderFocused(currentTheme); }
|
||||||
|
public static Color getBorderFocused(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(80, 140, 230) : new Color(50, 115, 220);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getSelectionBg() { return getSelectionBg(currentTheme); }
|
||||||
|
public static Color getSelectionBg(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(55, 105, 180) : new Color(60, 125, 215);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getSelectionFg() { return getSelectionFg(currentTheme); }
|
||||||
|
public static Color getSelectionFg(UITheme t) {
|
||||||
|
return Color.WHITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getMenuBarBg() { return getMenuBarBg(currentTheme); }
|
||||||
|
public static Color getMenuBarBg(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(26, 26, 26) : new Color(240, 240, 242);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getMenuBarFg() { return getMenuBarFg(currentTheme); }
|
||||||
|
public static Color getMenuBarFg(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(235, 235, 235) : new Color(25, 25, 25);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getMenuPopupBg() { return getMenuPopupBg(currentTheme); }
|
||||||
|
public static Color getMenuPopupBg(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(38, 38, 38) : Color.WHITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getMenuItemFg() { return getMenuItemFg(currentTheme); }
|
||||||
|
public static Color getMenuItemFg(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(230, 230, 230) : new Color(30, 30, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getMenuItemHoverBg() { return getMenuItemHoverBg(currentTheme); }
|
||||||
|
public static Color getMenuItemHoverBg(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(55, 95, 160) : new Color(220, 232, 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getMenuItemHoverFg() { return getMenuItemHoverFg(currentTheme); }
|
||||||
|
public static Color getMenuItemHoverFg(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? Color.WHITE : new Color(15, 15, 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getTabBg() { return getTabBg(currentTheme); }
|
||||||
|
public static Color getTabBg(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(34, 34, 34) : new Color(230, 230, 232);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getTabFg() { return getTabFg(currentTheme); }
|
||||||
|
public static Color getTabFg(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(185, 185, 185) : new Color(75, 75, 75);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getTabSelectedBg() { return getTabSelectedBg(currentTheme); }
|
||||||
|
public static Color getTabSelectedBg(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(50, 50, 50) : Color.WHITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getTabSelectedFg() { return getTabSelectedFg(currentTheme); }
|
||||||
|
public static Color getTabSelectedFg(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? Color.WHITE : new Color(20, 20, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getTableHeaderBg() { return getTableHeaderBg(currentTheme); }
|
||||||
|
public static Color getTableHeaderBg(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(46, 46, 46) : new Color(232, 232, 236);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getTableHeaderFg() { return getTableHeaderFg(currentTheme); }
|
||||||
|
public static Color getTableHeaderFg(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(235, 235, 235) : new Color(30, 30, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getTableRowEven() { return getTableRowEven(currentTheme); }
|
||||||
|
public static Color getTableRowEven(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(38, 38, 38) : Color.WHITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getTableRowOdd() { return getTableRowOdd(currentTheme); }
|
||||||
|
public static Color getTableRowOdd(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(33, 33, 33) : new Color(246, 247, 249);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getTableGrid() { return getTableGrid(currentTheme); }
|
||||||
|
public static Color getTableGrid(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(55, 55, 55) : new Color(225, 225, 225);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getCodeAreaBg() { return getCodeAreaBg(currentTheme); }
|
||||||
|
public static Color getCodeAreaBg(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(22, 22, 22) : new Color(252, 252, 252);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getCodeAreaFg() { return getCodeAreaFg(currentTheme); }
|
||||||
|
public static Color getCodeAreaFg(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(80, 230, 80) : new Color(0, 120, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getStatusBarBg() { return getStatusBarBg(currentTheme); }
|
||||||
|
public static Color getStatusBarBg(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(20, 20, 20) : new Color(235, 235, 238);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getStatusBarBorder() { return getStatusBarBorder(currentTheme); }
|
||||||
|
public static Color getStatusBarBorder(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(42, 42, 42) : new Color(210, 210, 215);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getOiaFgNormal() { return getOiaFgNormal(currentTheme); }
|
||||||
|
public static Color getOiaFgNormal(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(50, 205, 50) : new Color(0, 130, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getOiaFgDim() { return getOiaFgDim(currentTheme); }
|
||||||
|
public static Color getOiaFgDim(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(110, 110, 110) : new Color(90, 90, 90);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getOiaFgAlert() { return getOiaFgAlert(currentTheme); }
|
||||||
|
public static Color getOiaFgAlert(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(255, 80, 80) : new Color(190, 20, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getOiaFgWarn() { return getOiaFgWarn(currentTheme); }
|
||||||
|
public static Color getOiaFgWarn(UITheme t) {
|
||||||
|
return t == UITheme.DARK ? new Color(255, 190, 70) : new Color(180, 100, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Button Variants & Color helpers
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
public enum ButtonVariant {
|
||||||
|
DEFAULT,
|
||||||
|
PRIMARY,
|
||||||
|
CANCEL,
|
||||||
|
DANGER,
|
||||||
|
ACCENT
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getButtonBg(ButtonVariant variant) {
|
||||||
|
return getButtonBg(variant, currentTheme, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getButtonBg(ButtonVariant variant, UITheme t) {
|
||||||
|
return getButtonBg(variant, t, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getButtonBg(ButtonVariant variant, UITheme t, boolean hover, boolean pressed) {
|
||||||
|
switch (variant) {
|
||||||
|
case PRIMARY:
|
||||||
|
if (pressed) return new Color(25, 105, 35);
|
||||||
|
if (hover) return new Color(38, 145, 48);
|
||||||
|
return new Color(32, 128, 42);
|
||||||
|
|
||||||
|
case DANGER:
|
||||||
|
if (pressed) return new Color(125, 30, 30);
|
||||||
|
if (hover) return new Color(165, 45, 45);
|
||||||
|
return new Color(145, 38, 38);
|
||||||
|
|
||||||
|
case ACCENT:
|
||||||
|
if (pressed) return new Color(35, 80, 145);
|
||||||
|
if (hover) return new Color(55, 115, 195);
|
||||||
|
return new Color(45, 100, 175);
|
||||||
|
|
||||||
|
case CANCEL:
|
||||||
|
case DEFAULT:
|
||||||
|
default:
|
||||||
|
if (t == UITheme.DARK) {
|
||||||
|
if (pressed) return new Color(42, 42, 42);
|
||||||
|
if (hover) return new Color(68, 68, 68);
|
||||||
|
return new Color(56, 56, 56);
|
||||||
|
} else {
|
||||||
|
if (pressed) return new Color(210, 210, 212);
|
||||||
|
if (hover) return new Color(228, 228, 232);
|
||||||
|
return new Color(238, 238, 240);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color getButtonFg(ButtonVariant variant, UITheme t) {
|
||||||
|
switch (variant) {
|
||||||
|
case PRIMARY:
|
||||||
|
case DANGER:
|
||||||
|
case ACCENT:
|
||||||
|
return Color.WHITE;
|
||||||
|
case CANCEL:
|
||||||
|
case DEFAULT:
|
||||||
|
default:
|
||||||
|
return t == UITheme.DARK ? new Color(235, 235, 235) : new Color(30, 30, 30);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// UIManager Defaults Configuration
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
public static void applyUIManagerDefaults(UITheme theme) {
|
||||||
|
Color bgMain = getBgMain(theme);
|
||||||
|
Color bgPanel = getBgPanel(theme);
|
||||||
|
Color bgComp = getBgComponent(theme);
|
||||||
|
Color fgMain = getFgMain(theme);
|
||||||
|
Color fgMuted = getFgMuted(theme);
|
||||||
|
Color border = getBorder(theme);
|
||||||
|
Color selBg = getSelectionBg(theme);
|
||||||
|
Color selFg = getSelectionFg(theme);
|
||||||
|
|
||||||
|
UIManager.put("Panel.background", bgPanel);
|
||||||
|
UIManager.put("Panel.foreground", fgMain);
|
||||||
|
UIManager.put("Label.foreground", fgMain);
|
||||||
|
|
||||||
|
UIManager.put("TextField.background", bgComp);
|
||||||
|
UIManager.put("TextField.foreground", fgMain);
|
||||||
|
UIManager.put("TextField.caretForeground", fgMain);
|
||||||
|
UIManager.put("TextField.selectionBackground", selBg);
|
||||||
|
UIManager.put("TextField.selectionForeground", selFg);
|
||||||
|
|
||||||
|
UIManager.put("TextArea.background", bgComp);
|
||||||
|
UIManager.put("TextArea.foreground", fgMain);
|
||||||
|
UIManager.put("TextArea.caretForeground", fgMain);
|
||||||
|
UIManager.put("TextArea.selectionBackground", selBg);
|
||||||
|
UIManager.put("TextArea.selectionForeground", selFg);
|
||||||
|
|
||||||
|
UIManager.put("Button.background", getBgComponentLighter(theme));
|
||||||
|
UIManager.put("Button.foreground", fgMain);
|
||||||
|
UIManager.put("Button.select", selBg);
|
||||||
|
|
||||||
|
UIManager.put("ComboBox.background", bgComp);
|
||||||
|
UIManager.put("ComboBox.foreground", fgMain);
|
||||||
|
UIManager.put("ComboBox.selectionBackground", selBg);
|
||||||
|
UIManager.put("ComboBox.selectionForeground", selFg);
|
||||||
|
|
||||||
|
UIManager.put("Table.background", bgComp);
|
||||||
|
UIManager.put("Table.foreground", fgMain);
|
||||||
|
UIManager.put("Table.selectionBackground", selBg);
|
||||||
|
UIManager.put("Table.selectionForeground", selFg);
|
||||||
|
UIManager.put("Table.gridColor", getTableGrid(theme));
|
||||||
|
|
||||||
|
UIManager.put("TableHeader.background", getTableHeaderBg(theme));
|
||||||
|
UIManager.put("TableHeader.foreground", getTableHeaderFg(theme));
|
||||||
|
|
||||||
|
UIManager.put("ScrollPane.background", bgPanel);
|
||||||
|
UIManager.put("Viewport.background", bgComp);
|
||||||
|
|
||||||
|
UIManager.put("MenuBar.background", getMenuBarBg(theme));
|
||||||
|
UIManager.put("MenuBar.foreground", getMenuBarFg(theme));
|
||||||
|
UIManager.put("Menu.background", getMenuBarBg(theme));
|
||||||
|
UIManager.put("Menu.foreground", getMenuBarFg(theme));
|
||||||
|
UIManager.put("Menu.selectionBackground", selBg);
|
||||||
|
UIManager.put("Menu.selectionForeground", selFg);
|
||||||
|
|
||||||
|
UIManager.put("PopupMenu.background", getMenuPopupBg(theme));
|
||||||
|
UIManager.put("PopupMenu.foreground", getMenuItemFg(theme));
|
||||||
|
|
||||||
|
UIManager.put("MenuItem.background", getMenuPopupBg(theme));
|
||||||
|
UIManager.put("MenuItem.foreground", getMenuItemFg(theme));
|
||||||
|
UIManager.put("MenuItem.selectionBackground", selBg);
|
||||||
|
UIManager.put("MenuItem.selectionForeground", selFg);
|
||||||
|
|
||||||
|
UIManager.put("CheckBoxMenuItem.background", getMenuPopupBg(theme));
|
||||||
|
UIManager.put("CheckBoxMenuItem.foreground", getMenuItemFg(theme));
|
||||||
|
UIManager.put("CheckBoxMenuItem.selectionBackground", selBg);
|
||||||
|
UIManager.put("CheckBoxMenuItem.selectionForeground", selFg);
|
||||||
|
|
||||||
|
UIManager.put("RadioButtonMenuItem.background", getMenuPopupBg(theme));
|
||||||
|
UIManager.put("RadioButtonMenuItem.foreground", getMenuItemFg(theme));
|
||||||
|
UIManager.put("RadioButtonMenuItem.selectionBackground", selBg);
|
||||||
|
UIManager.put("RadioButtonMenuItem.selectionForeground", selFg);
|
||||||
|
|
||||||
|
UIManager.put("Separator.background", border);
|
||||||
|
UIManager.put("Separator.foreground", border);
|
||||||
|
|
||||||
|
UIManager.put("TabbedPane.background", getTabBg(theme));
|
||||||
|
UIManager.put("TabbedPane.foreground", getTabFg(theme));
|
||||||
|
UIManager.put("TabbedPane.selected", getTabSelectedBg(theme));
|
||||||
|
UIManager.put("TabbedPane.selectHighlight", getTabSelectedBg(theme));
|
||||||
|
|
||||||
|
UIManager.put("CheckBox.background", bgPanel);
|
||||||
|
UIManager.put("CheckBox.foreground", fgMain);
|
||||||
|
UIManager.put("RadioButton.background", bgPanel);
|
||||||
|
UIManager.put("RadioButton.foreground", fgMain);
|
||||||
|
|
||||||
|
UIManager.put("TitledBorder.titleColor", fgMain);
|
||||||
|
UIManager.put("OptionPane.background", bgPanel);
|
||||||
|
UIManager.put("OptionPane.messageForeground", fgMain);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Component Styling & Custom UI Delegates
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Styles a button with custom rendering, ensuring consistent high contrast
|
||||||
|
* and proper background color across Windows, macOS, and Linux.
|
||||||
|
*/
|
||||||
|
public static JButton styleButton(JButton button, ButtonVariant variant) {
|
||||||
|
if (button == null) return null;
|
||||||
|
button.setUI(new StyledButtonUI(variant));
|
||||||
|
button.setFocusPainted(false);
|
||||||
|
button.setOpaque(false);
|
||||||
|
button.setContentAreaFilled(false);
|
||||||
|
button.setBorder(BorderFactory.createEmptyBorder(6, 14, 6, 14));
|
||||||
|
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JButton createStyledButton(String text, ButtonVariant variant, Runnable action) {
|
||||||
|
JButton btn = new JButton(text);
|
||||||
|
styleButton(btn, variant);
|
||||||
|
if (action != null) {
|
||||||
|
btn.addActionListener(e -> action.run());
|
||||||
|
}
|
||||||
|
return btn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JTextField styleTextField(JTextField field) {
|
||||||
|
if (field == null) return null;
|
||||||
|
field.setBackground(getBgComponent());
|
||||||
|
field.setForeground(getFgMain());
|
||||||
|
field.setCaretColor(getFgMain());
|
||||||
|
field.setSelectionColor(getSelectionBg());
|
||||||
|
field.setSelectedTextColor(getSelectionFg());
|
||||||
|
field.setBorder(BorderFactory.createCompoundBorder(
|
||||||
|
new LineBorder(getBorder(), 1, true),
|
||||||
|
new EmptyBorder(4, 8, 4, 8)));
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JTextArea styleTextArea(JTextArea area) {
|
||||||
|
if (area == null) return null;
|
||||||
|
area.setBackground(getCodeAreaBg());
|
||||||
|
area.setForeground(getCodeAreaFg());
|
||||||
|
area.setCaretColor(getFgMain());
|
||||||
|
area.setSelectionColor(getSelectionBg());
|
||||||
|
area.setSelectedTextColor(getSelectionFg());
|
||||||
|
return area;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JComboBox<?> styleComboBox(JComboBox<?> box) {
|
||||||
|
if (box == null) return null;
|
||||||
|
box.setBackground(getBgComponent());
|
||||||
|
box.setForeground(getFgMain());
|
||||||
|
box.setRenderer(new DefaultListCellRenderer() {
|
||||||
|
@Override
|
||||||
|
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||||
|
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||||
|
setBorder(new EmptyBorder(4, 8, 4, 8));
|
||||||
|
if (isSelected) {
|
||||||
|
setBackground(getSelectionBg());
|
||||||
|
setForeground(getSelectionFg());
|
||||||
|
} else {
|
||||||
|
setBackground(getBgComponent());
|
||||||
|
setForeground(getFgMain());
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JTable styleTable(JTable table) {
|
||||||
|
if (table == null) return null;
|
||||||
|
table.setBackground(getBgComponent());
|
||||||
|
table.setForeground(getFgMain());
|
||||||
|
table.setSelectionBackground(getSelectionBg());
|
||||||
|
table.setSelectionForeground(getSelectionFg());
|
||||||
|
table.setGridColor(getTableGrid());
|
||||||
|
table.setRowHeight(24);
|
||||||
|
|
||||||
|
table.setDefaultRenderer(Object.class, new DefaultTableCellRenderer() {
|
||||||
|
@Override
|
||||||
|
public Component getTableCellRendererComponent(JTable tbl, Object val, boolean isSel, boolean hasFocus, int row, int col) {
|
||||||
|
super.getTableCellRendererComponent(tbl, val, isSel, hasFocus, row, col);
|
||||||
|
setBorder(new EmptyBorder(2, 6, 2, 6));
|
||||||
|
if (isSel) {
|
||||||
|
setBackground(getSelectionBg());
|
||||||
|
setForeground(getSelectionFg());
|
||||||
|
} else {
|
||||||
|
setBackground(row % 2 == 0 ? getTableRowEven() : getTableRowOdd());
|
||||||
|
setForeground(getFgMain());
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
JTableHeader header = table.getTableHeader();
|
||||||
|
if (header != null) {
|
||||||
|
header.setBackground(getTableHeaderBg());
|
||||||
|
header.setForeground(getTableHeaderFg());
|
||||||
|
header.setFont(header.getFont().deriveFont(Font.BOLD));
|
||||||
|
header.setDefaultRenderer(new DefaultTableCellRenderer() {
|
||||||
|
@Override
|
||||||
|
public Component getTableCellRendererComponent(JTable tbl, Object val, boolean isSel, boolean hasFocus, int row, int col) {
|
||||||
|
super.getTableCellRendererComponent(tbl, val, isSel, hasFocus, row, col);
|
||||||
|
setBackground(getTableHeaderBg());
|
||||||
|
setForeground(getTableHeaderFg());
|
||||||
|
setFont(getFont().deriveFont(Font.BOLD));
|
||||||
|
setBorder(BorderFactory.createCompoundBorder(
|
||||||
|
BorderFactory.createMatteBorder(0, 0, 1, 1, getBorderColor()),
|
||||||
|
new EmptyBorder(4, 6, 4, 6)));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JTabbedPane styleTabbedPane(JTabbedPane tp) {
|
||||||
|
if (tp == null) return null;
|
||||||
|
tp.setUI(new StyledTabbedPaneUI());
|
||||||
|
tp.setBackground(getBgMain());
|
||||||
|
tp.setForeground(getFgMain());
|
||||||
|
return tp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JMenuBar styleMenuBar(JMenuBar bar) {
|
||||||
|
if (bar == null) return null;
|
||||||
|
bar.setUI(new StyledMenuBarUI());
|
||||||
|
bar.setBackground(getMenuBarBg());
|
||||||
|
bar.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, getBorder()));
|
||||||
|
return bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JMenu styleMenu(JMenu menu) {
|
||||||
|
if (menu == null) return null;
|
||||||
|
menu.setUI(new StyledMenuUI());
|
||||||
|
menu.setForeground(getMenuBarFg());
|
||||||
|
menu.setBackground(getMenuBarBg());
|
||||||
|
menu.setOpaque(false);
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JMenuItem styleMenuItem(JMenuItem item) {
|
||||||
|
if (item == null) return null;
|
||||||
|
item.setUI(new StyledMenuItemUI());
|
||||||
|
item.setBackground(getMenuPopupBg());
|
||||||
|
item.setForeground(getMenuItemFg());
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JPopupMenu stylePopupMenu(JPopupMenu popup) {
|
||||||
|
if (popup == null) return null;
|
||||||
|
popup.setUI(new StyledPopupMenuUI());
|
||||||
|
popup.setBackground(getMenuPopupBg());
|
||||||
|
popup.setBorder(BorderFactory.createCompoundBorder(
|
||||||
|
new LineBorder(getBorder(), 1),
|
||||||
|
new EmptyBorder(4, 0, 4, 0)));
|
||||||
|
return popup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JScrollPane styleScrollPane(JScrollPane sp) {
|
||||||
|
if (sp == null) return null;
|
||||||
|
sp.setBackground(getBgPanel());
|
||||||
|
if (sp.getViewport() != null) {
|
||||||
|
sp.getViewport().setBackground(getBgComponent());
|
||||||
|
}
|
||||||
|
sp.setBorder(new LineBorder(getBorder(), 1));
|
||||||
|
return sp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JSpinner styleSpinner(JSpinner sp) {
|
||||||
|
if (sp == null) return null;
|
||||||
|
sp.setBackground(getBgComponent());
|
||||||
|
sp.setForeground(getFgMain());
|
||||||
|
sp.setBorder(new LineBorder(getBorder(), 1));
|
||||||
|
JComponent editor = sp.getEditor();
|
||||||
|
if (editor instanceof JSpinner.DefaultEditor) {
|
||||||
|
JTextField tf = ((JSpinner.DefaultEditor) editor).getTextField();
|
||||||
|
tf.setBackground(getBgComponent());
|
||||||
|
tf.setForeground(getFgMain());
|
||||||
|
tf.setCaretColor(getFgMain());
|
||||||
|
tf.setBorder(new EmptyBorder(2, 4, 2, 4));
|
||||||
|
}
|
||||||
|
return sp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JCheckBox styleCheckBox(JCheckBox cb) {
|
||||||
|
if (cb == null) return null;
|
||||||
|
cb.setOpaque(false);
|
||||||
|
cb.setForeground(getFgMain());
|
||||||
|
cb.setFocusPainted(false);
|
||||||
|
return cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JRadioButton styleRadioButton(JRadioButton rb) {
|
||||||
|
if (rb == null) return null;
|
||||||
|
rb.setOpaque(false);
|
||||||
|
rb.setForeground(getFgMain());
|
||||||
|
rb.setFocusPainted(false);
|
||||||
|
return rb;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Border createTitledBorder(String title) {
|
||||||
|
return BorderFactory.createTitledBorder(
|
||||||
|
new LineBorder(getBorder(), 1),
|
||||||
|
title,
|
||||||
|
TitledBorder.DEFAULT_JUSTIFICATION,
|
||||||
|
TitledBorder.DEFAULT_POSITION,
|
||||||
|
new Font(Font.SANS_SERIF, Font.BOLD, 12),
|
||||||
|
getFgMain());
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Recursive Component Styling
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
public static void applyTheme(Component comp) {
|
||||||
|
applyTheme(comp, currentTheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void applyTheme(Component comp, UITheme theme) {
|
||||||
|
if (comp == null) return;
|
||||||
|
|
||||||
|
// Skip swatch preview panels whose background is the user-configured color
|
||||||
|
if ("colorSwatch".equals(comp.getName())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comp instanceof JMenuBar) {
|
||||||
|
styleMenuBar((JMenuBar) comp);
|
||||||
|
for (int i = 0; i < ((JMenuBar) comp).getMenuCount(); i++) {
|
||||||
|
applyTheme(((JMenuBar) comp).getMenu(i), theme);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comp instanceof JMenu) {
|
||||||
|
styleMenu((JMenu) comp);
|
||||||
|
JMenu menu = (JMenu) comp;
|
||||||
|
for (int i = 0; i < menu.getItemCount(); i++) {
|
||||||
|
JMenuItem item = menu.getItem(i);
|
||||||
|
if (item != null) applyTheme(item, theme);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comp instanceof JMenuItem) {
|
||||||
|
styleMenuItem((JMenuItem) comp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comp instanceof JPopupMenu) {
|
||||||
|
stylePopupMenu((JPopupMenu) comp);
|
||||||
|
for (Component child : ((JPopupMenu) comp).getComponents()) {
|
||||||
|
applyTheme(child, theme);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comp instanceof JTabbedPane) {
|
||||||
|
styleTabbedPane((JTabbedPane) comp);
|
||||||
|
JTabbedPane tp = (JTabbedPane) comp;
|
||||||
|
for (int i = 0; i < tp.getTabCount(); i++) {
|
||||||
|
applyTheme(tp.getComponentAt(i), theme);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comp instanceof JTable) {
|
||||||
|
styleTable((JTable) comp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comp instanceof JScrollPane) {
|
||||||
|
styleScrollPane((JScrollPane) comp);
|
||||||
|
JScrollPane sp = (JScrollPane) comp;
|
||||||
|
if (sp.getViewport() != null) {
|
||||||
|
applyTheme(sp.getViewport().getView(), theme);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comp instanceof JButton) {
|
||||||
|
JButton btn = (JButton) comp;
|
||||||
|
if (!(btn.getUI() instanceof StyledButtonUI)) {
|
||||||
|
styleButton(btn, ButtonVariant.DEFAULT);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comp instanceof JComboBox) {
|
||||||
|
styleComboBox((JComboBox<?>) comp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comp instanceof JSpinner) {
|
||||||
|
styleSpinner((JSpinner) comp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comp instanceof JTextArea) {
|
||||||
|
styleTextArea((JTextArea) comp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comp instanceof JTextField) {
|
||||||
|
styleTextField((JTextField) comp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comp instanceof JCheckBox) {
|
||||||
|
styleCheckBox((JCheckBox) comp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comp instanceof JRadioButton) {
|
||||||
|
styleRadioButton((JRadioButton) comp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comp instanceof JLabel) {
|
||||||
|
comp.setForeground(getFgMain(theme));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comp instanceof JPanel) {
|
||||||
|
JPanel panel = (JPanel) comp;
|
||||||
|
if (panel.isOpaque()) {
|
||||||
|
panel.setBackground(getBgPanel(theme));
|
||||||
|
}
|
||||||
|
panel.setForeground(getFgMain(theme));
|
||||||
|
Border b = panel.getBorder();
|
||||||
|
if (b instanceof TitledBorder) {
|
||||||
|
TitledBorder tb = (TitledBorder) b;
|
||||||
|
tb.setTitleColor(getFgMain(theme));
|
||||||
|
tb.setBorder(new LineBorder(getBorder(theme), 1));
|
||||||
|
}
|
||||||
|
} else if (comp instanceof Container) {
|
||||||
|
comp.setBackground(getBgMain(theme));
|
||||||
|
comp.setForeground(getFgMain(theme));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comp instanceof Container) {
|
||||||
|
for (Component child : ((Container) comp).getComponents()) {
|
||||||
|
applyTheme(child, theme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void applyThemeToWindow(Window window) {
|
||||||
|
if (window == null) return;
|
||||||
|
window.setBackground(getBgMain());
|
||||||
|
if (window instanceof RootPaneContainer) {
|
||||||
|
RootPaneContainer rpc = (RootPaneContainer) window;
|
||||||
|
if (rpc.getContentPane() != null) {
|
||||||
|
rpc.getContentPane().setBackground(getBgMain());
|
||||||
|
applyTheme(rpc.getContentPane());
|
||||||
|
}
|
||||||
|
if (rpc.getRootPane() != null && rpc.getRootPane().getJMenuBar() != null) {
|
||||||
|
applyTheme(rpc.getRootPane().getJMenuBar());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Custom UI Implementations
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
public static class StyledButtonUI extends BasicButtonUI {
|
||||||
|
private final ButtonVariant variant;
|
||||||
|
private boolean hover = false;
|
||||||
|
|
||||||
|
public StyledButtonUI(ButtonVariant variant) {
|
||||||
|
this.variant = variant != null ? variant : ButtonVariant.DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void installUI(JComponent c) {
|
||||||
|
super.installUI(c);
|
||||||
|
AbstractButton b = (AbstractButton) c;
|
||||||
|
b.addMouseListener(new MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mouseEntered(MouseEvent e) {
|
||||||
|
hover = true;
|
||||||
|
b.repaint();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void mouseExited(MouseEvent e) {
|
||||||
|
hover = false;
|
||||||
|
b.repaint();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paint(Graphics g, JComponent c) {
|
||||||
|
AbstractButton b = (AbstractButton) c;
|
||||||
|
Graphics2D g2 = (Graphics2D) g.create();
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
|
||||||
|
int w = c.getWidth();
|
||||||
|
int h = c.getHeight();
|
||||||
|
|
||||||
|
ButtonModel model = b.getModel();
|
||||||
|
boolean pressed = model.isArmed() && model.isPressed();
|
||||||
|
boolean enabled = b.isEnabled();
|
||||||
|
|
||||||
|
UITheme t = ThemeManager.getTheme();
|
||||||
|
Color bg = enabled ? ThemeManager.getButtonBg(variant, t, hover, pressed) : (t == UITheme.DARK ? new Color(40, 40, 40) : new Color(220, 220, 220));
|
||||||
|
Color fg = enabled ? ThemeManager.getButtonFg(variant, t) : ThemeManager.getFgDisabled(t);
|
||||||
|
|
||||||
|
// Background
|
||||||
|
g2.setColor(bg);
|
||||||
|
g2.fillRoundRect(0, 0, w, h, 6, 6);
|
||||||
|
|
||||||
|
// Border
|
||||||
|
if (variant == ButtonVariant.DEFAULT || variant == ButtonVariant.CANCEL) {
|
||||||
|
g2.setColor(ThemeManager.getBorder(t));
|
||||||
|
g2.drawRoundRect(0, 0, w - 1, h - 1, 6, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text / Icon
|
||||||
|
FontMetrics fm = g2.getFontMetrics(b.getFont());
|
||||||
|
String text = b.getText();
|
||||||
|
if (text != null && !text.isEmpty()) {
|
||||||
|
g2.setColor(fg);
|
||||||
|
g2.setFont(b.getFont());
|
||||||
|
int tx = (w - fm.stringWidth(text)) / 2;
|
||||||
|
int ty = (h - fm.getHeight()) / 2 + fm.getAscent();
|
||||||
|
g2.drawString(text, tx, ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
g2.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class StyledMenuBarUI extends BasicMenuBarUI {
|
||||||
|
@Override
|
||||||
|
public void paint(Graphics g, JComponent c) {
|
||||||
|
g.setColor(ThemeManager.getMenuBarBg());
|
||||||
|
g.fillRect(0, 0, c.getWidth(), c.getHeight());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class StyledMenuUI extends BasicMenuUI {
|
||||||
|
@Override
|
||||||
|
protected void paintBackground(Graphics g, JMenuItem menuItem, Color bgColor) {
|
||||||
|
ButtonModel model = menuItem.getModel();
|
||||||
|
if (model.isArmed() || model.isSelected()) {
|
||||||
|
g.setColor(ThemeManager.getMenuItemHoverBg());
|
||||||
|
g.fillRect(0, 0, menuItem.getWidth(), menuItem.getHeight());
|
||||||
|
} else {
|
||||||
|
g.setColor(ThemeManager.getMenuBarBg());
|
||||||
|
g.fillRect(0, 0, menuItem.getWidth(), menuItem.getHeight());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void paintText(Graphics g, JMenuItem menuItem, Rectangle textRect, String text) {
|
||||||
|
ButtonModel model = menuItem.getModel();
|
||||||
|
if (model.isArmed() || model.isSelected()) {
|
||||||
|
g.setColor(ThemeManager.getMenuItemHoverFg());
|
||||||
|
} else {
|
||||||
|
g.setColor(ThemeManager.getMenuBarFg());
|
||||||
|
}
|
||||||
|
super.paintText(g, menuItem, textRect, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class StyledMenuItemUI extends BasicMenuItemUI {
|
||||||
|
@Override
|
||||||
|
protected void paintBackground(Graphics g, JMenuItem menuItem, Color bgColor) {
|
||||||
|
ButtonModel model = menuItem.getModel();
|
||||||
|
if (model.isArmed() || model.isSelected()) {
|
||||||
|
g.setColor(ThemeManager.getSelectionBg());
|
||||||
|
g.fillRect(0, 0, menuItem.getWidth(), menuItem.getHeight());
|
||||||
|
} else {
|
||||||
|
g.setColor(ThemeManager.getMenuPopupBg());
|
||||||
|
g.fillRect(0, 0, menuItem.getWidth(), menuItem.getHeight());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void paintText(Graphics g, JMenuItem menuItem, Rectangle textRect, String text) {
|
||||||
|
ButtonModel model = menuItem.getModel();
|
||||||
|
if (model.isArmed() || model.isSelected()) {
|
||||||
|
g.setColor(ThemeManager.getSelectionFg());
|
||||||
|
} else {
|
||||||
|
g.setColor(ThemeManager.getMenuItemFg());
|
||||||
|
}
|
||||||
|
super.paintText(g, menuItem, textRect, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class StyledPopupMenuUI extends BasicPopupMenuUI {
|
||||||
|
@Override
|
||||||
|
public void paint(Graphics g, JComponent c) {
|
||||||
|
g.setColor(ThemeManager.getMenuPopupBg());
|
||||||
|
g.fillRect(0, 0, c.getWidth(), c.getHeight());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class StyledTabbedPaneUI extends BasicTabbedPaneUI {
|
||||||
|
@Override
|
||||||
|
protected void installDefaults() {
|
||||||
|
super.installDefaults();
|
||||||
|
tabInsets = new Insets(6, 16, 6, 16);
|
||||||
|
selectedTabPadInsets = new Insets(2, 2, 2, 2);
|
||||||
|
tabAreaInsets = new Insets(4, 4, 0, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void paintTabBackground(Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
|
||||||
|
Graphics2D g2 = (Graphics2D) g.create();
|
||||||
|
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
g2.setColor(isSelected ? ThemeManager.getTabSelectedBg() : ThemeManager.getTabBg());
|
||||||
|
g2.fillRoundRect(x, y, w, h + 4, 6, 6);
|
||||||
|
g2.setColor(ThemeManager.getBorder());
|
||||||
|
g2.drawRoundRect(x, y, w - 1, h + 4, 6, 6);
|
||||||
|
g2.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void paintText(Graphics g, int tabPlacement, Font font, FontMetrics metrics, int tabIndex, String title, Rectangle textRect, boolean isSelected) {
|
||||||
|
g.setFont(font);
|
||||||
|
g.setColor(isSelected ? ThemeManager.getTabSelectedFg() : ThemeManager.getTabFg());
|
||||||
|
g.drawString(title, textRect.x, textRect.y + metrics.getAscent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void paintContentBorder(Graphics g, int tabPlacement, int selectedIndex) {
|
||||||
|
int width = tabPane.getWidth();
|
||||||
|
int height = tabPane.getHeight();
|
||||||
|
Insets insets = tabPane.getInsets();
|
||||||
|
int x = insets.left;
|
||||||
|
int y = insets.top + calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
|
||||||
|
int w = width - insets.right - insets.left;
|
||||||
|
int h = height - insets.top - insets.bottom - calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
|
||||||
|
|
||||||
|
g.setColor(ThemeManager.getTabSelectedBg());
|
||||||
|
g.fillRect(x, y, w, h);
|
||||||
|
g.setColor(ThemeManager.getBorder());
|
||||||
|
g.drawRect(x, y, w - 1, h - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package haus.nightmare.j3270.ui;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supported UI themes for the j3270 Java desktop interface.
|
||||||
|
*/
|
||||||
|
public enum UITheme {
|
||||||
|
DARK("Dark Mode"),
|
||||||
|
LIGHT("Light Mode");
|
||||||
|
|
||||||
|
private final String displayName;
|
||||||
|
|
||||||
|
UITheme(String displayName) {
|
||||||
|
this.displayName = displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayName() {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UITheme fromString(String name) {
|
||||||
|
if (name == null) return DARK;
|
||||||
|
String s = name.trim().toUpperCase();
|
||||||
|
if ("LIGHT".equals(s) || "LIGHT MODE".equals(s) || "LIGHT_MODE".equals(s)) {
|
||||||
|
return LIGHT;
|
||||||
|
}
|
||||||
|
return DARK;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,25 +26,24 @@ public class UntrustedCertificateDialog extends JDialog {
|
|||||||
private void buildUI(String host, int port, X509Certificate[] chain, CertificateException exception) {
|
private void buildUI(String host, int port, X509Certificate[] chain, CertificateException exception) {
|
||||||
JPanel mainPanel = new JPanel(new BorderLayout(12, 12));
|
JPanel mainPanel = new JPanel(new BorderLayout(12, 12));
|
||||||
mainPanel.setBorder(BorderFactory.createEmptyBorder(16, 16, 16, 16));
|
mainPanel.setBorder(BorderFactory.createEmptyBorder(16, 16, 16, 16));
|
||||||
mainPanel.setBackground(new Color(30, 30, 30));
|
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
JPanel headerPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 0));
|
JPanel headerPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 0));
|
||||||
headerPanel.setBackground(new Color(30, 30, 30));
|
headerPanel.setOpaque(false);
|
||||||
JLabel iconLabel = new JLabel("⚠️");
|
JLabel iconLabel = new JLabel("⚠️");
|
||||||
iconLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 28));
|
iconLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 28));
|
||||||
headerPanel.add(iconLabel);
|
headerPanel.add(iconLabel);
|
||||||
|
|
||||||
JPanel titleBox = new JPanel(new GridLayout(2, 1, 0, 2));
|
JPanel titleBox = new JPanel(new GridLayout(2, 1, 0, 2));
|
||||||
titleBox.setBackground(new Color(30, 30, 30));
|
titleBox.setOpaque(false);
|
||||||
JLabel titleLabel = new JLabel("Untrusted SSL Certificate");
|
JLabel titleLabel = new JLabel("Untrusted SSL Certificate");
|
||||||
titleLabel.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 16));
|
titleLabel.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 16));
|
||||||
titleLabel.setForeground(new Color(255, 180, 80));
|
titleLabel.setForeground(ThemeManager.getOiaFgWarn());
|
||||||
titleBox.add(titleLabel);
|
titleBox.add(titleLabel);
|
||||||
|
|
||||||
JLabel subtitleLabel = new JLabel("The server certificate for " + host + ":" + port + " could not be verified.");
|
JLabel subtitleLabel = new JLabel("The server certificate for " + host + ":" + port + " could not be verified.");
|
||||||
subtitleLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12));
|
subtitleLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12));
|
||||||
subtitleLabel.setForeground(new Color(180, 180, 180));
|
subtitleLabel.setForeground(ThemeManager.getFgMuted());
|
||||||
titleBox.add(subtitleLabel);
|
titleBox.add(subtitleLabel);
|
||||||
headerPanel.add(titleBox);
|
headerPanel.add(titleBox);
|
||||||
mainPanel.add(headerPanel, BorderLayout.NORTH);
|
mainPanel.add(headerPanel, BorderLayout.NORTH);
|
||||||
@@ -71,30 +70,27 @@ public class UntrustedCertificateDialog extends JDialog {
|
|||||||
JTextArea detailsArea = new JTextArea(sb.toString());
|
JTextArea detailsArea = new JTextArea(sb.toString());
|
||||||
detailsArea.setEditable(false);
|
detailsArea.setEditable(false);
|
||||||
detailsArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
|
detailsArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
|
||||||
detailsArea.setBackground(new Color(20, 20, 20));
|
ThemeManager.styleTextArea(detailsArea);
|
||||||
detailsArea.setForeground(new Color(210, 210, 210));
|
|
||||||
detailsArea.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
|
detailsArea.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
|
||||||
|
|
||||||
JScrollPane scrollPane = new JScrollPane(detailsArea);
|
JScrollPane scrollPane = new JScrollPane(detailsArea);
|
||||||
scrollPane.setPreferredSize(new Dimension(520, 260));
|
scrollPane.setPreferredSize(new Dimension(520, 260));
|
||||||
scrollPane.setBorder(BorderFactory.createLineBorder(new Color(60, 60, 60)));
|
ThemeManager.styleScrollPane(scrollPane);
|
||||||
mainPanel.add(scrollPane, BorderLayout.CENTER);
|
mainPanel.add(scrollPane, BorderLayout.CENTER);
|
||||||
|
|
||||||
// Buttons
|
// Buttons
|
||||||
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 0));
|
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 0));
|
||||||
buttonPanel.setBackground(new Color(30, 30, 30));
|
buttonPanel.setOpaque(false);
|
||||||
|
|
||||||
JButton cancelBtn = new JButton("Cancel Connection");
|
JButton cancelBtn = new JButton("Cancel Connection");
|
||||||
cancelBtn.setBackground(new Color(60, 60, 60));
|
ThemeManager.styleButton(cancelBtn, ThemeManager.ButtonVariant.CANCEL);
|
||||||
cancelBtn.setForeground(new Color(220, 220, 220));
|
|
||||||
cancelBtn.addActionListener(e -> {
|
cancelBtn.addActionListener(e -> {
|
||||||
accepted = false;
|
accepted = false;
|
||||||
dispose();
|
dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
JButton trustBtn = new JButton("Connect Anyway");
|
JButton trustBtn = new JButton("Connect Anyway");
|
||||||
trustBtn.setBackground(new Color(180, 100, 40));
|
ThemeManager.styleButton(trustBtn, ThemeManager.ButtonVariant.DANGER);
|
||||||
trustBtn.setForeground(Color.WHITE);
|
|
||||||
trustBtn.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 13));
|
trustBtn.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 13));
|
||||||
trustBtn.addActionListener(e -> {
|
trustBtn.addActionListener(e -> {
|
||||||
accepted = true;
|
accepted = true;
|
||||||
@@ -106,6 +102,7 @@ public class UntrustedCertificateDialog extends JDialog {
|
|||||||
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
|
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
setContentPane(mainPanel);
|
setContentPane(mainPanel);
|
||||||
|
ThemeManager.applyThemeToWindow(this);
|
||||||
getRootPane().setDefaultButton(trustBtn);
|
getRootPane().setDefaultButton(trustBtn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package haus.nightmare.j3270.ui;
|
||||||
|
|
||||||
|
import haus.nightmare.j3270.J3270App;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.HeadlessException;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class MenuBarShortcutsTest {
|
||||||
|
|
||||||
|
private Map<String, JMenuItem> collectMenuItems(JMenu menu) {
|
||||||
|
Map<String, JMenuItem> map = new HashMap<>();
|
||||||
|
for (int i = 0; i < menu.getItemCount(); i++) {
|
||||||
|
JMenuItem item = menu.getItem(i);
|
||||||
|
if (item != null) {
|
||||||
|
map.put(item.getText(), item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActionAndViewShortcutsUseAlt() {
|
||||||
|
J3270App app;
|
||||||
|
try {
|
||||||
|
app = new J3270App();
|
||||||
|
} catch (HeadlessException e) {
|
||||||
|
// In automated/headless environments, JFrame cannot be initialized
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
JMenuBar mb = app.getJMenuBar();
|
||||||
|
assertNotNull(mb, "JMenuBar should be present");
|
||||||
|
|
||||||
|
JMenu viewMenu = null;
|
||||||
|
JMenu actionsMenu = null;
|
||||||
|
JMenu fileMenu = null;
|
||||||
|
JMenu editMenu = 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 ("Edit".equals(m.getText())) editMenu = m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertNotNull(viewMenu, "View menu should exist");
|
||||||
|
assertNotNull(actionsMenu, "Actions menu should exist");
|
||||||
|
assertNotNull(fileMenu, "File menu should exist");
|
||||||
|
assertNotNull(editMenu, "Edit menu should exist");
|
||||||
|
|
||||||
|
// Verify View Menu items use ALT_DOWN_MASK
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Verify Actions Menu items use ALT_DOWN_MASK
|
||||||
|
Map<String, JMenuItem> actionItems = collectMenuItems(actionsMenu);
|
||||||
|
assertAcceleratorUsesAlt(actionItems.get("Send Enter"), KeyEvent.VK_ENTER);
|
||||||
|
assertAcceleratorUsesAlt(actionItems.get("Clear"), KeyEvent.VK_K);
|
||||||
|
assertAcceleratorUsesAlt(actionItems.get("Reset"), KeyEvent.VK_R);
|
||||||
|
assertAcceleratorUsesAlt(actionItems.get("Erase Input"), KeyEvent.VK_E);
|
||||||
|
assertAcceleratorUsesAlt(actionItems.get("Attention"), KeyEvent.VK_A);
|
||||||
|
assertAcceleratorUsesAlt(actionItems.get("System Request"), KeyEvent.VK_S);
|
||||||
|
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);
|
||||||
|
} finally {
|
||||||
|
// Dispose frame
|
||||||
|
app.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertAcceleratorUsesAlt(JMenuItem item, int expectedKeyCode) {
|
||||||
|
assertNotNull(item, "Menu item must exist");
|
||||||
|
KeyStroke ks = item.getAccelerator();
|
||||||
|
assertNotNull(ks, "Menu item " + item.getText() + " should have an accelerator");
|
||||||
|
assertEquals(expectedKeyCode, ks.getKeyCode(), "Key code mismatch for " + item.getText());
|
||||||
|
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.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,59 @@
|
|||||||
|
package haus.nightmare.j3270.ui;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.ConnectionConfig;
|
||||||
|
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||||
|
import haus.nightmare.lib3270j.TerminalModel;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class ScreenExporterTest {
|
||||||
|
|
||||||
|
private Telnet3270Client client;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() {
|
||||||
|
ConnectionConfig config = new ConnectionConfig("localhost", 23, TerminalModel.IBM_3279_2, false);
|
||||||
|
client = new Telnet3270Client(config);
|
||||||
|
client.getInputProcessor().setKeyboardLocked(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPlainTextExport() throws IOException {
|
||||||
|
client.getPS().setText("TSO/E LOGON SCREEN - WELCOME", 0);
|
||||||
|
|
||||||
|
File tempTxt = File.createTempFile("export_test_", ".txt");
|
||||||
|
tempTxt.deleteOnExit();
|
||||||
|
|
||||||
|
ScreenExporter.exportToText(client, tempTxt);
|
||||||
|
|
||||||
|
assertTrue(tempTxt.exists());
|
||||||
|
assertTrue(tempTxt.length() > 0);
|
||||||
|
|
||||||
|
String content = Files.readString(tempTxt.toPath());
|
||||||
|
assertTrue(content.contains("TSO/E LOGON SCREEN - WELCOME"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHtmlExport() throws IOException {
|
||||||
|
client.getPS().setText("TSO/E LOGON SCREEN - WELCOME", 0);
|
||||||
|
|
||||||
|
File tempHtml = File.createTempFile("export_test_", ".html");
|
||||||
|
tempHtml.deleteOnExit();
|
||||||
|
|
||||||
|
ScreenExporter.exportToHtml(client, tempHtml);
|
||||||
|
|
||||||
|
assertTrue(tempHtml.exists());
|
||||||
|
assertTrue(tempHtml.length() > 0);
|
||||||
|
|
||||||
|
String html = Files.readString(tempHtml.toPath());
|
||||||
|
assertTrue(html.contains("<!DOCTYPE html>"));
|
||||||
|
assertTrue(html.contains("TSO/E LOGON SCREEN - WELCOME"));
|
||||||
|
assertTrue(html.contains("font-family: 'Courier New', Courier, monospace"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package haus.nightmare.j3270.ui;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.ConnectionConfig;
|
||||||
|
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||||
|
import haus.nightmare.lib3270j.TerminalModel;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class StatusBarTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStatusBarDoesNotContainLightPenButton() {
|
||||||
|
try {
|
||||||
|
StatusBar statusBar = new StatusBar();
|
||||||
|
|
||||||
|
// Ensure no JButton exists in StatusBar components (lightpen button removed)
|
||||||
|
for (Component comp : statusBar.getComponents()) {
|
||||||
|
assertFalse(comp instanceof JButton, "StatusBar should not contain any JButton (lightpen removed from bottom bar)");
|
||||||
|
}
|
||||||
|
} catch (HeadlessException e) {
|
||||||
|
// Ignored in headless environments
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStatusBarUpdatesWithClient() {
|
||||||
|
try {
|
||||||
|
ConnectionConfig config = new ConnectionConfig("mvs.example.com", 23, TerminalModel.IBM_3279_4, false);
|
||||||
|
config.setLuName("TSU001");
|
||||||
|
config.setCodePage("1047");
|
||||||
|
Telnet3270Client client = new Telnet3270Client(config);
|
||||||
|
|
||||||
|
StatusBar statusBar = new StatusBar();
|
||||||
|
statusBar.setClient(client, null);
|
||||||
|
statusBar.updateStatus();
|
||||||
|
|
||||||
|
// Check labels
|
||||||
|
boolean foundCodePage = false;
|
||||||
|
boolean foundModel = false;
|
||||||
|
for (Component comp : statusBar.getComponents()) {
|
||||||
|
if (comp instanceof JLabel) {
|
||||||
|
JLabel label = (JLabel) comp;
|
||||||
|
if ("CP1047".equals(label.getText())) {
|
||||||
|
foundCodePage = true;
|
||||||
|
}
|
||||||
|
if (label.getText() != null && label.getText().contains("3279-4")) {
|
||||||
|
foundModel = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(foundCodePage, "Should display CP1047");
|
||||||
|
assertTrue(foundModel, "Should display IBM-3279-4 model info");
|
||||||
|
} catch (HeadlessException e) {
|
||||||
|
// Ignored in headless environments
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
package haus.nightmare.j3270.ui;
|
||||||
|
|
||||||
|
import haus.nightmare.j3270.config.Settings;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.Border;
|
||||||
|
import javax.swing.table.DefaultTableModel;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class ThemeManagerTest {
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setup() {
|
||||||
|
ThemeManager.setTheme(UITheme.DARK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUIThemeEnum() {
|
||||||
|
assertEquals("Dark Mode", UITheme.DARK.getDisplayName());
|
||||||
|
assertEquals("Light Mode", UITheme.LIGHT.getDisplayName());
|
||||||
|
|
||||||
|
assertEquals(UITheme.DARK, UITheme.fromString("DARK"));
|
||||||
|
assertEquals(UITheme.DARK, UITheme.fromString("dark"));
|
||||||
|
assertEquals(UITheme.DARK, UITheme.fromString("Dark Mode"));
|
||||||
|
assertEquals(UITheme.LIGHT, UITheme.fromString("LIGHT"));
|
||||||
|
assertEquals(UITheme.LIGHT, UITheme.fromString("light"));
|
||||||
|
assertEquals(UITheme.LIGHT, UITheme.fromString("Light Mode"));
|
||||||
|
assertEquals(UITheme.DARK, UITheme.fromString("invalid_value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testColorTokensContrast() {
|
||||||
|
for (UITheme theme : UITheme.values()) {
|
||||||
|
Color bg = ThemeManager.getBgMain(theme);
|
||||||
|
Color fg = ThemeManager.getFgMain(theme);
|
||||||
|
assertNotNull(bg);
|
||||||
|
assertNotNull(fg);
|
||||||
|
|
||||||
|
// Compute luminance contrast
|
||||||
|
double bgLum = (0.299 * bg.getRed() + 0.587 * bg.getGreen() + 0.114 * bg.getBlue());
|
||||||
|
double fgLum = (0.299 * fg.getRed() + 0.587 * fg.getGreen() + 0.114 * fg.getBlue());
|
||||||
|
double lumDiff = Math.abs(bgLum - fgLum);
|
||||||
|
|
||||||
|
// Minimum luminance difference to guarantee readability
|
||||||
|
assertTrue(lumDiff > 120, "Theme " + theme + " text contrast luminance difference " + lumDiff + " must be > 120");
|
||||||
|
|
||||||
|
// Verify Menu Bar colors
|
||||||
|
Color menuBg = ThemeManager.getMenuBarBg(theme);
|
||||||
|
Color menuFg = ThemeManager.getMenuItemFg(theme);
|
||||||
|
double menuBgLum = (0.299 * menuBg.getRed() + 0.587 * menuBg.getGreen() + 0.114 * menuBg.getBlue());
|
||||||
|
double menuFgLum = (0.299 * menuFg.getRed() + 0.587 * menuFg.getGreen() + 0.114 * menuFg.getBlue());
|
||||||
|
assertTrue(Math.abs(menuBgLum - menuFgLum) > 100, "Menu contrast in " + theme + " must be high");
|
||||||
|
|
||||||
|
// Verify Status Bar colors
|
||||||
|
Color sbBg = ThemeManager.getStatusBarBg(theme);
|
||||||
|
Color sbNormal = ThemeManager.getOiaFgNormal(theme);
|
||||||
|
assertNotNull(sbBg);
|
||||||
|
assertNotNull(sbNormal);
|
||||||
|
|
||||||
|
// Verify Button colors
|
||||||
|
Color btnDefBg = ThemeManager.getButtonBg(ThemeManager.ButtonVariant.DEFAULT, theme);
|
||||||
|
Color btnDefFg = ThemeManager.getButtonFg(ThemeManager.ButtonVariant.DEFAULT, theme);
|
||||||
|
assertNotNull(btnDefBg);
|
||||||
|
assertNotNull(btnDefFg);
|
||||||
|
|
||||||
|
Color btnPriBg = ThemeManager.getButtonBg(ThemeManager.ButtonVariant.PRIMARY, theme);
|
||||||
|
Color btnPriFg = ThemeManager.getButtonFg(ThemeManager.ButtonVariant.PRIMARY, theme);
|
||||||
|
assertNotNull(btnPriBg);
|
||||||
|
assertNotNull(btnPriFg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testThemeChangeListeners() {
|
||||||
|
AtomicReference<UITheme> notifiedTheme = new AtomicReference<>(null);
|
||||||
|
ThemeManager.addThemeChangeListener(notifiedTheme::set);
|
||||||
|
|
||||||
|
ThemeManager.setTheme(UITheme.LIGHT);
|
||||||
|
assertEquals(UITheme.LIGHT, ThemeManager.getTheme());
|
||||||
|
assertEquals(UITheme.LIGHT, notifiedTheme.get());
|
||||||
|
assertTrue(ThemeManager.isLight());
|
||||||
|
assertFalse(ThemeManager.isDark());
|
||||||
|
|
||||||
|
ThemeManager.setTheme(UITheme.DARK);
|
||||||
|
assertEquals(UITheme.DARK, ThemeManager.getTheme());
|
||||||
|
assertEquals(UITheme.DARK, notifiedTheme.get());
|
||||||
|
assertTrue(ThemeManager.isDark());
|
||||||
|
assertFalse(ThemeManager.isLight());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testComponentStyling() {
|
||||||
|
try {
|
||||||
|
JButton btn = new JButton("Test");
|
||||||
|
ThemeManager.styleButton(btn, ThemeManager.ButtonVariant.PRIMARY);
|
||||||
|
assertNotNull(btn.getUI());
|
||||||
|
assertTrue(btn.getUI() instanceof ThemeManager.StyledButtonUI);
|
||||||
|
|
||||||
|
JTextField tf = new JTextField("Test");
|
||||||
|
ThemeManager.styleTextField(tf);
|
||||||
|
assertNotNull(tf.getCaretColor());
|
||||||
|
|
||||||
|
JTextArea ta = new JTextArea("Test area");
|
||||||
|
ThemeManager.styleTextArea(ta);
|
||||||
|
assertNotNull(ta.getCaretColor());
|
||||||
|
|
||||||
|
JComboBox<String> cb = new JComboBox<>(new String[]{"A", "B"});
|
||||||
|
ThemeManager.styleComboBox(cb);
|
||||||
|
assertNotNull(cb.getUI());
|
||||||
|
|
||||||
|
JTable table = new JTable(new DefaultTableModel(new Object[]{"Col1"}, 1));
|
||||||
|
ThemeManager.styleTable(table);
|
||||||
|
assertNotNull(table.getSelectionBackground());
|
||||||
|
|
||||||
|
JTabbedPane tp = new JTabbedPane();
|
||||||
|
tp.addTab("Tab1", new JPanel());
|
||||||
|
ThemeManager.styleTabbedPane(tp);
|
||||||
|
assertNotNull(tp.getUI());
|
||||||
|
|
||||||
|
JMenuBar mb = new JMenuBar();
|
||||||
|
ThemeManager.styleMenuBar(mb);
|
||||||
|
|
||||||
|
JMenu menu = new JMenu("File");
|
||||||
|
ThemeManager.styleMenu(menu);
|
||||||
|
|
||||||
|
JMenuItem mi = new JMenuItem("Open");
|
||||||
|
ThemeManager.styleMenuItem(mi);
|
||||||
|
|
||||||
|
JPopupMenu popup = new JPopupMenu();
|
||||||
|
ThemeManager.stylePopupMenu(popup);
|
||||||
|
|
||||||
|
JCheckBox chk = new JCheckBox("Check");
|
||||||
|
ThemeManager.styleCheckBox(chk);
|
||||||
|
|
||||||
|
JRadioButton rb = new JRadioButton("Radio");
|
||||||
|
ThemeManager.styleRadioButton(rb);
|
||||||
|
|
||||||
|
Border tb = ThemeManager.createTitledBorder("Title");
|
||||||
|
assertNotNull(tb);
|
||||||
|
} catch (HeadlessException e) {
|
||||||
|
// Ignored in headless environments
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRecursiveApplyTheme() {
|
||||||
|
try {
|
||||||
|
JPanel root = new JPanel(new BorderLayout());
|
||||||
|
JButton b = new JButton("OK");
|
||||||
|
JTextField f = new JTextField("Data");
|
||||||
|
JTabbedPane tp = new JTabbedPane();
|
||||||
|
JPanel tabContent = new JPanel();
|
||||||
|
JLabel lbl = new JLabel("Label");
|
||||||
|
tabContent.add(lbl);
|
||||||
|
tp.addTab("T1", tabContent);
|
||||||
|
|
||||||
|
root.add(b, BorderLayout.NORTH);
|
||||||
|
root.add(f, BorderLayout.CENTER);
|
||||||
|
root.add(tp, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
ThemeManager.setTheme(UITheme.LIGHT);
|
||||||
|
ThemeManager.applyTheme(root);
|
||||||
|
|
||||||
|
assertEquals(ThemeManager.getBgPanel(UITheme.LIGHT), root.getBackground());
|
||||||
|
assertEquals(ThemeManager.getFgMain(UITheme.LIGHT), lbl.getForeground());
|
||||||
|
|
||||||
|
ThemeManager.setTheme(UITheme.DARK);
|
||||||
|
ThemeManager.applyTheme(root);
|
||||||
|
|
||||||
|
assertEquals(ThemeManager.getBgPanel(UITheme.DARK), root.getBackground());
|
||||||
|
assertEquals(ThemeManager.getFgMain(UITheme.DARK), lbl.getForeground());
|
||||||
|
} catch (HeadlessException e) {
|
||||||
|
// Ignored in headless environments
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSettingsPersistence() throws Exception {
|
||||||
|
Settings.setJavaUiTheme(UITheme.LIGHT);
|
||||||
|
assertEquals(UITheme.LIGHT, Settings.getJavaUiTheme());
|
||||||
|
|
||||||
|
File tmpIni = File.createTempFile("j3270_test_config", ".ini");
|
||||||
|
tmpIni.deleteOnExit();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Write INI file with javaUiTheme = dark
|
||||||
|
try (PrintWriter pw = new PrintWriter(tmpIni)) {
|
||||||
|
pw.println("[j3270]");
|
||||||
|
pw.println("javaUiTheme = dark");
|
||||||
|
pw.println("fontFamily = Monospaced");
|
||||||
|
pw.println("fontSize = 16");
|
||||||
|
}
|
||||||
|
|
||||||
|
Settings.loadFromIniFile(tmpIni.getAbsolutePath());
|
||||||
|
assertEquals(UITheme.DARK, Settings.getJavaUiTheme());
|
||||||
|
|
||||||
|
// Switch to light and export
|
||||||
|
Settings.setJavaUiTheme(UITheme.LIGHT);
|
||||||
|
File exportFile = File.createTempFile("j3270_export_config", ".ini");
|
||||||
|
exportFile.deleteOnExit();
|
||||||
|
|
||||||
|
Settings.exportToIniFile(exportFile.getAbsolutePath());
|
||||||
|
String exportedContent = new String(Files.readAllBytes(exportFile.toPath()));
|
||||||
|
assertTrue(exportedContent.toLowerCase().contains("javauitheme = light"));
|
||||||
|
|
||||||
|
// Reload exported file
|
||||||
|
Settings.loadFromIniFile(exportFile.getAbsolutePath());
|
||||||
|
assertEquals(UITheme.LIGHT, Settings.getJavaUiTheme());
|
||||||
|
} finally {
|
||||||
|
tmpIni.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,10 @@ package haus.nightmare.lib3270j;
|
|||||||
*/
|
*/
|
||||||
public class ConnectionConfig {
|
public class ConnectionConfig {
|
||||||
|
|
||||||
|
public enum ProxyType {
|
||||||
|
NONE, HTTP, SOCKS4, SOCKS5
|
||||||
|
}
|
||||||
|
|
||||||
private String host;
|
private String host;
|
||||||
private int port = 23;
|
private int port = 23;
|
||||||
private TerminalModel model = TerminalModel.IBM_3279_4;
|
private TerminalModel model = TerminalModel.IBM_3279_4;
|
||||||
@@ -18,7 +22,31 @@ public class ConnectionConfig {
|
|||||||
private int nopIntervalSeconds = 0;
|
private int nopIntervalSeconds = 0;
|
||||||
private String terminalName = null; // override terminal type string
|
private String terminalName = null; // override terminal type string
|
||||||
private boolean tn3270eEnabled = true;
|
private boolean tn3270eEnabled = true;
|
||||||
|
private boolean tcpNoDelay = true;
|
||||||
|
private boolean soKeepAlive = true;
|
||||||
|
private int soTimeoutMs = 0;
|
||||||
|
private java.util.List<String> luNames = new java.util.ArrayList<>();
|
||||||
|
private boolean dynamicModel = false;
|
||||||
|
private int dynamicRows = 24;
|
||||||
|
private int dynamicCols = 80;
|
||||||
private haus.nightmare.lib3270j.graphics.GraphicsMode graphicsMode = haus.nightmare.lib3270j.graphics.GraphicsMode.BOTH;
|
private haus.nightmare.lib3270j.graphics.GraphicsMode graphicsMode = haus.nightmare.lib3270j.graphics.GraphicsMode.BOTH;
|
||||||
|
private String codePage = "037";
|
||||||
|
private String associatedPrinterLu = null;
|
||||||
|
private boolean nvtLocalEcho = false;
|
||||||
|
|
||||||
|
// Proxy configuration
|
||||||
|
private ProxyType proxyType = ProxyType.NONE;
|
||||||
|
private String proxyHost = null;
|
||||||
|
private int proxyPort = 0;
|
||||||
|
private String proxyUsername = null;
|
||||||
|
private String proxyPassword = null;
|
||||||
|
|
||||||
|
// STARTTLS (Telnet Option 46) dynamic socket elevation
|
||||||
|
private boolean startTlsEnabled = true;
|
||||||
|
|
||||||
|
// RFC 1572 / RFC 2877 Environment variables (Express Logon)
|
||||||
|
private java.util.Map<String, String> environmentVariables = new java.util.LinkedHashMap<>();
|
||||||
|
private java.util.Map<String, String> userVariables = new java.util.LinkedHashMap<>();
|
||||||
|
|
||||||
public ConnectionConfig() {}
|
public ConnectionConfig() {}
|
||||||
|
|
||||||
@@ -87,12 +115,99 @@ public class ConnectionConfig {
|
|||||||
this.graphicsMode = (mode != null) ? mode : haus.nightmare.lib3270j.graphics.GraphicsMode.NONE;
|
this.graphicsMode = (mode != null) ? mode : haus.nightmare.lib3270j.graphics.GraphicsMode.NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getCodePage() { return codePage; }
|
||||||
|
public void setCodePage(String codePage) {
|
||||||
|
this.codePage = (codePage != null && !codePage.trim().isEmpty()) ? codePage.trim() : "037";
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNvtLocalEcho() { return nvtLocalEcho; }
|
||||||
|
public void setNvtLocalEcho(boolean nvtLocalEcho) { this.nvtLocalEcho = nvtLocalEcho; }
|
||||||
|
|
||||||
public String getTerminalName() { return terminalName; }
|
public String getTerminalName() { return terminalName; }
|
||||||
public void setTerminalName(String name) { this.terminalName = name; }
|
public void setTerminalName(String name) { this.terminalName = name; }
|
||||||
|
|
||||||
|
public boolean isTcpNoDelay() { return tcpNoDelay; }
|
||||||
|
public void setTcpNoDelay(boolean tcpNoDelay) { this.tcpNoDelay = tcpNoDelay; }
|
||||||
|
|
||||||
|
public boolean isSoKeepAlive() { return soKeepAlive; }
|
||||||
|
public void setSoKeepAlive(boolean soKeepAlive) { this.soKeepAlive = soKeepAlive; }
|
||||||
|
|
||||||
|
public int getSoTimeoutMs() { return soTimeoutMs; }
|
||||||
|
public void setSoTimeoutMs(int soTimeoutMs) { this.soTimeoutMs = soTimeoutMs; }
|
||||||
|
|
||||||
|
public java.util.List<String> getLuNames() { return luNames; }
|
||||||
|
public void setLuNames(java.util.List<String> luNames) {
|
||||||
|
this.luNames = (luNames != null) ? new java.util.ArrayList<>(luNames) : new java.util.ArrayList<>();
|
||||||
|
}
|
||||||
|
public void addLuName(String luName) {
|
||||||
|
if (luName != null && !luName.trim().isEmpty()) {
|
||||||
|
this.luNames.add(luName.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDynamicModel() { return dynamicModel; }
|
||||||
|
public void setDynamicModel(boolean dynamicModel) { this.dynamicModel = dynamicModel; }
|
||||||
|
|
||||||
|
public int getDynamicRows() { return dynamicRows; }
|
||||||
|
public int getDynamicCols() { return dynamicCols; }
|
||||||
|
public void setDynamicDimensions(int rows, int cols) {
|
||||||
|
this.dynamicModel = true;
|
||||||
|
this.dynamicRows = rows;
|
||||||
|
this.dynamicCols = cols;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProxyType getProxyType() { return proxyType; }
|
||||||
|
public void setProxyType(ProxyType proxyType) { this.proxyType = proxyType != null ? proxyType : ProxyType.NONE; }
|
||||||
|
|
||||||
|
public String getProxyHost() { return proxyHost; }
|
||||||
|
public void setProxyHost(String proxyHost) { this.proxyHost = proxyHost; }
|
||||||
|
|
||||||
|
public int getProxyPort() { return proxyPort; }
|
||||||
|
public void setProxyPort(int proxyPort) { this.proxyPort = proxyPort; }
|
||||||
|
|
||||||
|
public String getProxyUsername() { return proxyUsername; }
|
||||||
|
public void setProxyUsername(String proxyUsername) { this.proxyUsername = proxyUsername; }
|
||||||
|
|
||||||
|
public String getProxyPassword() { return proxyPassword; }
|
||||||
|
public void setProxyPassword(String proxyPassword) { this.proxyPassword = proxyPassword; }
|
||||||
|
|
||||||
|
public void setProxy(ProxyType type, String host, int port, String username, String password) {
|
||||||
|
this.proxyType = type != null ? type : ProxyType.NONE;
|
||||||
|
this.proxyHost = host;
|
||||||
|
this.proxyPort = port;
|
||||||
|
this.proxyUsername = username;
|
||||||
|
this.proxyPassword = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isStartTlsEnabled() { return startTlsEnabled; }
|
||||||
|
public void setStartTlsEnabled(boolean enabled) { this.startTlsEnabled = enabled; }
|
||||||
|
|
||||||
|
public java.util.Map<String, String> getEnvironmentVariables() { return environmentVariables; }
|
||||||
|
public void setEnvironmentVariables(java.util.Map<String, String> vars) {
|
||||||
|
this.environmentVariables = (vars != null) ? new java.util.LinkedHashMap<>(vars) : new java.util.LinkedHashMap<>();
|
||||||
|
}
|
||||||
|
public void setEnvironmentVariable(String name, String value) {
|
||||||
|
if (name != null) {
|
||||||
|
if (value != null) this.environmentVariables.put(name, value);
|
||||||
|
else this.environmentVariables.remove(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public java.util.Map<String, String> getUserVariables() { return userVariables; }
|
||||||
|
public void setUserVariables(java.util.Map<String, String> vars) {
|
||||||
|
this.userVariables = (vars != null) ? new java.util.LinkedHashMap<>(vars) : new java.util.LinkedHashMap<>();
|
||||||
|
}
|
||||||
|
public void setUserVariable(String name, String value) {
|
||||||
|
if (name != null) {
|
||||||
|
if (value != null) this.userVariables.put(name, value);
|
||||||
|
else this.userVariables.remove(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a host connection string which may include prefixes for TLS (e.g. "L:host:port", "ssl:host:port", "y:host:port"),
|
* Parse a host connection string which may include prefixes for TLS (e.g. "L:host:port", "ssl:host:port", "y:host:port"),
|
||||||
* plain TN3270 (e.g. "P:host:port", "plain:host:port", "non-e:host:port"), or standard "host:port" formats.
|
* plain TN3270 (e.g. "P:host:port", "plain:host:port", "non-e:host:port"), proxy flags (e.g. "--proxy=http://proxy:8080 host:23"),
|
||||||
|
* or standard "host:port" formats.
|
||||||
*/
|
*/
|
||||||
public static ConnectionConfig parseHostString(String hostStr, int defaultPort, TerminalModel defaultModel) {
|
public static ConnectionConfig parseHostString(String hostStr, int defaultPort, TerminalModel defaultModel) {
|
||||||
if (hostStr == null || hostStr.trim().isEmpty()) {
|
if (hostStr == null || hostStr.trim().isEmpty()) {
|
||||||
@@ -102,6 +217,50 @@ public class ConnectionConfig {
|
|||||||
boolean tls = false;
|
boolean tls = false;
|
||||||
boolean tn3270e = true;
|
boolean tn3270e = true;
|
||||||
|
|
||||||
|
// Parse --proxy=<url> or -proxy=<url> flags
|
||||||
|
ProxyType pType = ProxyType.NONE;
|
||||||
|
String pHost = null;
|
||||||
|
int pPort = 0;
|
||||||
|
String pUser = null;
|
||||||
|
String pPass = null;
|
||||||
|
|
||||||
|
String[] tokens = s.split("\\s+");
|
||||||
|
StringBuilder remaining = new StringBuilder();
|
||||||
|
for (String tok : tokens) {
|
||||||
|
if (tok.startsWith("--proxy=") || tok.startsWith("-proxy=")) {
|
||||||
|
String proxyUrl = tok.substring(tok.indexOf('=') + 1).trim();
|
||||||
|
try {
|
||||||
|
java.net.URI uri = new java.net.URI(proxyUrl);
|
||||||
|
String scheme = uri.getScheme() != null ? uri.getScheme().toLowerCase() : "http";
|
||||||
|
if (scheme.equals("http") || scheme.equals("https")) {
|
||||||
|
pType = ProxyType.HTTP;
|
||||||
|
pPort = uri.getPort() > 0 ? uri.getPort() : 8080;
|
||||||
|
} else if (scheme.equals("socks4") || scheme.equals("socks4a")) {
|
||||||
|
pType = ProxyType.SOCKS4;
|
||||||
|
pPort = uri.getPort() > 0 ? uri.getPort() : 1080;
|
||||||
|
} else if (scheme.equals("socks5") || scheme.equals("socks")) {
|
||||||
|
pType = ProxyType.SOCKS5;
|
||||||
|
pPort = uri.getPort() > 0 ? uri.getPort() : 1080;
|
||||||
|
}
|
||||||
|
pHost = uri.getHost();
|
||||||
|
String userInfo = uri.getUserInfo();
|
||||||
|
if (userInfo != null) {
|
||||||
|
int colon = userInfo.indexOf(':');
|
||||||
|
if (colon >= 0) {
|
||||||
|
pUser = userInfo.substring(0, colon);
|
||||||
|
pPass = userInfo.substring(colon + 1);
|
||||||
|
} else {
|
||||||
|
pUser = userInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
} else {
|
||||||
|
if (remaining.length() > 0) remaining.append(" ");
|
||||||
|
remaining.append(tok);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s = remaining.toString().trim();
|
||||||
|
|
||||||
// Parse chained x3270-style prefixes (e.g. "L:P:host:port" or "P:host:port")
|
// Parse chained x3270-style prefixes (e.g. "L:P:host:port" or "P:host:port")
|
||||||
boolean prefixFound = true;
|
boolean prefixFound = true;
|
||||||
while (prefixFound) {
|
while (prefixFound) {
|
||||||
@@ -156,6 +315,9 @@ public class ConnectionConfig {
|
|||||||
ConnectionConfig config = new ConnectionConfig(host, port, defaultModel != null ? defaultModel : TerminalModel.IBM_3279_4);
|
ConnectionConfig config = new ConnectionConfig(host, port, defaultModel != null ? defaultModel : TerminalModel.IBM_3279_4);
|
||||||
config.setUseTls(tls);
|
config.setUseTls(tls);
|
||||||
config.setTn3270eEnabled(tn3270e);
|
config.setTn3270eEnabled(tn3270e);
|
||||||
|
if (pType != ProxyType.NONE && pHost != null) {
|
||||||
|
config.setProxy(pType, pHost, pPort, pUser, pPass);
|
||||||
|
}
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,6 +328,25 @@ public class ConnectionConfig {
|
|||||||
if (terminalName != null) {
|
if (terminalName != null) {
|
||||||
return terminalName;
|
return terminalName;
|
||||||
}
|
}
|
||||||
|
if (dynamicModel) {
|
||||||
|
return extendedDataStream ? "IBM-DYNAMIC-E" : "IBM-DYNAMIC";
|
||||||
|
}
|
||||||
return extendedDataStream ? model.getTerminalType() : model.getBaseTerminalType();
|
return extendedDataStream ? model.getTerminalType() : model.getBaseTerminalType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getAssociatedPrinterLu() { return associatedPrinterLu; }
|
||||||
|
public void setAssociatedPrinterLu(String printerLu) { this.associatedPrinterLu = printerLu; }
|
||||||
|
|
||||||
|
public haus.nightmare.lib3270j.printer.PrinterConfig toPrinterConfig() {
|
||||||
|
haus.nightmare.lib3270j.printer.PrinterConfig pcfg = new haus.nightmare.lib3270j.printer.PrinterConfig(host, port);
|
||||||
|
pcfg.setUseTls(useTls);
|
||||||
|
pcfg.setTlsVerifyCert(tlsVerifyCert);
|
||||||
|
pcfg.setCertificateVerifier(certificateVerifier);
|
||||||
|
pcfg.setSslProtocol(sslProtocol);
|
||||||
|
pcfg.setConnectTimeoutMs(connectTimeoutMs);
|
||||||
|
pcfg.setPrinterLuName(associatedPrinterLu);
|
||||||
|
pcfg.setAssociatedDisplayLuName(luName);
|
||||||
|
pcfg.setCodePage(codePage);
|
||||||
|
return pcfg;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,4 +59,32 @@ public enum ConnectionState {
|
|||||||
public boolean isFullSession() {
|
public boolean isFullSession() {
|
||||||
return isNvt() || is3270();
|
return isNvt() || is3270();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map to IBM Host On-Demand ECL connection state integer codes.
|
||||||
|
* 0 = Disconnected, 1 = Connecting/Resolving, 2 = Connected (NVT/Unbound), 3 = Bound (Full 3270 session).
|
||||||
|
*/
|
||||||
|
public int toHoDStateCode() {
|
||||||
|
switch (this) {
|
||||||
|
case NOT_CONNECTED:
|
||||||
|
return 0;
|
||||||
|
case RECONNECTING:
|
||||||
|
case RESOLVING:
|
||||||
|
case TCP_PENDING:
|
||||||
|
case TLS_PENDING:
|
||||||
|
case PROXY_PENDING:
|
||||||
|
case TELNET_PENDING:
|
||||||
|
return 1;
|
||||||
|
case CONNECTED_NVT:
|
||||||
|
case CONNECTED_NVT_CHAR:
|
||||||
|
case CONNECTED_UNBOUND:
|
||||||
|
case CONNECTED_E_NVT:
|
||||||
|
case CONNECTED_SSCP:
|
||||||
|
return 2;
|
||||||
|
case CONNECTED_3270:
|
||||||
|
case CONNECTED_TN3270E:
|
||||||
|
default:
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,22 +39,46 @@ public class Telnet3270Client {
|
|||||||
private final DataStreamProcessor dsProcessor;
|
private final DataStreamProcessor dsProcessor;
|
||||||
private final TelnetFSM fsm;
|
private final TelnetFSM fsm;
|
||||||
private final InputProcessor inputProcessor;
|
private final InputProcessor inputProcessor;
|
||||||
|
private final haus.nightmare.lib3270j.ecl.ECLPS ps;
|
||||||
|
private final haus.nightmare.lib3270j.ecl.ECLOIA oia;
|
||||||
|
private final haus.nightmare.lib3270j.ecl.ECLXfer xfer;
|
||||||
private TelnetConnection connection;
|
private TelnetConnection connection;
|
||||||
|
|
||||||
public Telnet3270Client(ConnectionConfig config) {
|
public Telnet3270Client(ConnectionConfig config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.translator = new EbcdicTranslator();
|
this.translator = new EbcdicTranslator(config.getCodePage());
|
||||||
this.screenBuffer = new ScreenBuffer(config.getModel(), translator);
|
this.screenBuffer = new ScreenBuffer(config.getModel(), translator);
|
||||||
this.dsProcessor = new DataStreamProcessor(screenBuffer, translator);
|
this.dsProcessor = new DataStreamProcessor(screenBuffer, translator);
|
||||||
this.dsProcessor.getQueryReplyBuilder().setGraphicsMode(config.getGraphicsMode());
|
this.dsProcessor.getQueryReplyBuilder().setGraphicsMode(config.getGraphicsMode());
|
||||||
this.fsm = new TelnetFSM(config, screenBuffer, dsProcessor);
|
this.fsm = new TelnetFSM(config, screenBuffer, dsProcessor);
|
||||||
this.inputProcessor = new InputProcessor(screenBuffer, translator, fsm);
|
this.inputProcessor = new InputProcessor(screenBuffer, translator, fsm);
|
||||||
|
this.ps = new haus.nightmare.lib3270j.ecl.ECLPS(screenBuffer, inputProcessor, translator);
|
||||||
|
this.oia = new haus.nightmare.lib3270j.ecl.ECLOIA(screenBuffer, inputProcessor, fsm);
|
||||||
|
this.inputProcessor.setOIA(oia);
|
||||||
|
this.xfer = new haus.nightmare.lib3270j.ecl.ECLXfer(screenBuffer, inputProcessor, dsProcessor, translator);
|
||||||
|
|
||||||
// Wire up the output sender: DSProcessor -> FSM -> TelnetConnection
|
// Wire up the output sender: DSProcessor -> FSM -> TelnetConnection
|
||||||
dsProcessor.setOutputSender(fsm::send3270Data);
|
dsProcessor.setOutputSender(fsm::send3270Data);
|
||||||
dsProcessor.setInputProcessor(inputProcessor);
|
dsProcessor.setInputProcessor(inputProcessor);
|
||||||
inputProcessor.setGraphicsPlane(dsProcessor.getGraphicsPlane());
|
inputProcessor.setGraphicsPlane(dsProcessor.getGraphicsPlane());
|
||||||
inputProcessor.setGocaDecoder(dsProcessor.getGocaDecoder());
|
inputProcessor.setGocaDecoder(dsProcessor.getGocaDecoder());
|
||||||
|
|
||||||
|
// Wire screen update to ECLXfer for CUT mode screen tracking and ECLPS for event dispatching
|
||||||
|
addScreenUpdateListener(new haus.nightmare.lib3270j.listener.ScreenUpdateListener() {
|
||||||
|
@Override public void onScreenUpdated() {
|
||||||
|
xfer.onScreenUpdated();
|
||||||
|
ps.notifyPSUpdate(0, 0, screenBuffer.getRows() - 1, screenBuffer.getCols() - 1, true);
|
||||||
|
}
|
||||||
|
@Override public void onCursorMoved(int oldAddress, int newAddress) {
|
||||||
|
ps.notifyCursorMoved(oldAddress, newAddress);
|
||||||
|
}
|
||||||
|
@Override public void onScreenSizeChanged(int rows, int cols) {
|
||||||
|
ps.notifyScreenResized(rows, cols);
|
||||||
|
}
|
||||||
|
@Override public void onSoundAlarm() {
|
||||||
|
ps.notifyAlarm();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -76,6 +100,52 @@ public class Telnet3270Client {
|
|||||||
fsm.onConnected();
|
fsm.onConnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to the configured host synchronously, blocking until the full data session is established
|
||||||
|
* (or timeout expires).
|
||||||
|
* @param timeoutMs maximum time to wait in milliseconds
|
||||||
|
* @return true if successfully connected, false if timed out
|
||||||
|
* @throws IOException if network connection fails
|
||||||
|
*/
|
||||||
|
public boolean connect(long timeoutMs) throws IOException {
|
||||||
|
connect();
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
while (System.currentTimeMillis() - start < timeoutMs) {
|
||||||
|
ConnectionState state = getConnectionState();
|
||||||
|
if (state.isFullSession() || (state.isFullyConnected() && isConnected())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (state == ConnectionState.NOT_CONNECTED && !isConnected()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(25);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getConnectionState().isFullSession() || isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect to the configured host synchronously and block until the presentation space matches
|
||||||
|
* the specified screen descriptor.
|
||||||
|
* @param timeoutMs maximum time to wait in milliseconds
|
||||||
|
* @param desc ECLScreenDesc descriptor to match against
|
||||||
|
* @return true if successfully connected and screen matched, false otherwise
|
||||||
|
* @throws IOException if network connection fails
|
||||||
|
*/
|
||||||
|
public boolean connect(long timeoutMs, haus.nightmare.lib3270j.ecl.ECLScreenDesc desc) throws IOException {
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
if (!connect(timeoutMs)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
long elapsed = System.currentTimeMillis() - start;
|
||||||
|
long remaining = Math.max(1, timeoutMs - elapsed);
|
||||||
|
return ps.waitForScreen(desc, remaining);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disconnect from the host.
|
* Disconnect from the host.
|
||||||
*/
|
*/
|
||||||
@@ -87,6 +157,35 @@ public class Telnet3270Client {
|
|||||||
fsm.onDisconnect();
|
fsm.onDisconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect from host with synchronous teardown.
|
||||||
|
* @param timeoutMs maximum time to wait for graceful disconnect in milliseconds
|
||||||
|
*/
|
||||||
|
public void disconnect(long timeoutMs) {
|
||||||
|
disconnect();
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
while (System.currentTimeMillis() - start < timeoutMs) {
|
||||||
|
if (getConnectionState() == ConnectionState.NOT_CONNECTED && !isConnected()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(20);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** HoD StopCommunication compatibility alias. */
|
||||||
|
public void stopCommunication() {
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopCommunication(long timeoutMs) {
|
||||||
|
disconnect(timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if connected (any state past TCP pending).
|
* Check if connected (any state past TCP pending).
|
||||||
*/
|
*/
|
||||||
@@ -100,11 +199,28 @@ public class Telnet3270Client {
|
|||||||
fsm.addConnectionListener(l);
|
fsm.addConnectionListener(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeConnectionListener(ConnectionListener l) {
|
||||||
|
fsm.removeConnectionListener(l);
|
||||||
|
}
|
||||||
|
|
||||||
public void addScreenUpdateListener(ScreenUpdateListener l) {
|
public void addScreenUpdateListener(ScreenUpdateListener l) {
|
||||||
fsm.addScreenUpdateListener(l);
|
fsm.addScreenUpdateListener(l);
|
||||||
dsProcessor.addScreenUpdateListener(l);
|
dsProcessor.addScreenUpdateListener(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeScreenUpdateListener(ScreenUpdateListener l) {
|
||||||
|
fsm.removeScreenUpdateListener(l);
|
||||||
|
dsProcessor.removeScreenUpdateListener(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addSCSInboundListener(haus.nightmare.lib3270j.listener.SCSInboundListener l) {
|
||||||
|
fsm.addSCSInboundListener(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeSCSInboundListener(haus.nightmare.lib3270j.listener.SCSInboundListener l) {
|
||||||
|
fsm.removeSCSInboundListener(l);
|
||||||
|
}
|
||||||
|
|
||||||
// ========== Screen access ==========
|
// ========== Screen access ==========
|
||||||
|
|
||||||
/** Get the screen buffer for rendering. */
|
/** Get the screen buffer for rendering. */
|
||||||
@@ -113,6 +229,12 @@ public class Telnet3270Client {
|
|||||||
/** Get the EBCDIC translator. */
|
/** Get the EBCDIC translator. */
|
||||||
public EbcdicTranslator getTranslator() { return translator; }
|
public EbcdicTranslator getTranslator() { return translator; }
|
||||||
|
|
||||||
|
/** Get the active Code Page identifier. */
|
||||||
|
public String getCodePage() { return translator.getCodePageId(); }
|
||||||
|
|
||||||
|
/** Set the active Code Page identifier. */
|
||||||
|
public void setCodePage(String codePageId) { translator.setCodePage(codePageId); }
|
||||||
|
|
||||||
/** Get the current connection state. */
|
/** Get the current connection state. */
|
||||||
public ConnectionState getConnectionState() { return fsm.getConnectionState(); }
|
public ConnectionState getConnectionState() { return fsm.getConnectionState(); }
|
||||||
|
|
||||||
@@ -125,6 +247,21 @@ public class Telnet3270Client {
|
|||||||
/** Get the connection config. */
|
/** Get the connection config. */
|
||||||
public ConnectionConfig getConfig() { return config; }
|
public ConnectionConfig getConfig() { return config; }
|
||||||
|
|
||||||
|
/** Get the ECL Presentation Space API. */
|
||||||
|
public haus.nightmare.lib3270j.ecl.ECLPS getPS() { return ps; }
|
||||||
|
|
||||||
|
/** Get the ECL Operator Information Area API. */
|
||||||
|
public haus.nightmare.lib3270j.ecl.ECLOIA getOIA() { return oia; }
|
||||||
|
|
||||||
|
/** Get the ECL File Transfer API. */
|
||||||
|
public haus.nightmare.lib3270j.ecl.ECLXfer getXfer() { return xfer; }
|
||||||
|
|
||||||
|
/** Get the list of all fields currently on screen. */
|
||||||
|
public haus.nightmare.lib3270j.ecl.ECLFieldList getFieldList() { return ps.getFieldList(); }
|
||||||
|
|
||||||
|
/** Send IBM ECL bracketed mnemonic keystrokes (e.g. "USER[tab]PASS[enter]"). */
|
||||||
|
public void sendKeys(String keys) { inputProcessor.sendKeys(keys); }
|
||||||
|
|
||||||
/** Set a custom or interactive TLS certificate verifier callback. */
|
/** Set a custom or interactive TLS certificate verifier callback. */
|
||||||
public void setTlsCertificateVerifier(haus.nightmare.lib3270j.tls.TlsCertificateVerifier verifier) {
|
public void setTlsCertificateVerifier(haus.nightmare.lib3270j.tls.TlsCertificateVerifier verifier) {
|
||||||
config.setCertificateVerifier(verifier);
|
config.setCertificateVerifier(verifier);
|
||||||
@@ -132,14 +269,71 @@ public class Telnet3270Client {
|
|||||||
|
|
||||||
/** Get the active SSLSession if connected over TLS, or null. */
|
/** Get the active SSLSession if connected over TLS, or null. */
|
||||||
public javax.net.ssl.SSLSession getSslSession() {
|
public javax.net.ssl.SSLSession getSslSession() {
|
||||||
return connection.getSslSession();
|
return connection != null ? connection.getSslSession() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Convenience input methods ==========
|
/** Get the NVT processor for ASCII / ANSI terminal processing. */
|
||||||
|
public haus.nightmare.lib3270j.nvt.NvtProcessor getNvtProcessor() {
|
||||||
|
return fsm.getNvtProcessor();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the Telnet state machine. */
|
||||||
|
public TelnetFSM getTelnetFSM() {
|
||||||
|
return fsm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Send an NVT ASCII character in NVT mode. */
|
||||||
|
public void sendNVTChar(char c) throws IOException {
|
||||||
|
fsm.sendNVTChar(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Send an NVT ASCII string in NVT mode. */
|
||||||
|
public void sendNVTString(String s) throws IOException {
|
||||||
|
fsm.sendNVTString(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Send an NVT key event mapped through NVT processor. */
|
||||||
|
public boolean sendNVTKey(int keyCode, char keyChar, boolean shift, boolean ctrl, boolean alt) throws IOException {
|
||||||
|
haus.nightmare.lib3270j.nvt.NvtProcessor nvt = getNvtProcessor();
|
||||||
|
if (nvt != null) {
|
||||||
|
String seq = nvt.mapKey(keyCode, keyChar, shift, ctrl, alt);
|
||||||
|
if (seq != null && !seq.isEmpty()) {
|
||||||
|
fsm.sendNvtData(seq.getBytes(java.nio.charset.StandardCharsets.US_ASCII));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addNvtTitleListener(haus.nightmare.lib3270j.nvt.NvtProcessor.NvtTitleListener l) {
|
||||||
|
if (getNvtProcessor() != null) {
|
||||||
|
getNvtProcessor().addTitleListener(l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeNvtTitleListener(haus.nightmare.lib3270j.nvt.NvtProcessor.NvtTitleListener l) {
|
||||||
|
if (getNvtProcessor() != null) {
|
||||||
|
getNvtProcessor().removeTitleListener(l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNvtClipboardHandler(haus.nightmare.lib3270j.nvt.NvtProcessor.ClipboardHandler h) {
|
||||||
|
if (getNvtProcessor() != null) {
|
||||||
|
getNvtProcessor().setClipboardHandler(h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Convenience input methods & HoD compatibility ==========
|
||||||
|
|
||||||
/** Type a character at the cursor position. */
|
/** Type a character at the cursor position. */
|
||||||
public void typeCharacter(char ch) { inputProcessor.typeCharacter(ch); }
|
public void typeCharacter(char ch) { inputProcessor.typeCharacter(ch); }
|
||||||
|
|
||||||
|
/** Process a character at the current cursor position. */
|
||||||
|
public void processChar(char ch) { inputProcessor.processChar(ch); }
|
||||||
|
|
||||||
|
/** Process a character with explicit position and insert mode flag. */
|
||||||
|
public void processChar(char ch, int pos, boolean insert) { inputProcessor.processChar(ch, pos, insert); }
|
||||||
|
|
||||||
/** Type a string at the cursor position. */
|
/** Type a string at the cursor position. */
|
||||||
public void typeString(String s) {
|
public void typeString(String s) {
|
||||||
for (char ch : s.toCharArray()) {
|
for (char ch : s.toCharArray()) {
|
||||||
@@ -156,6 +350,7 @@ public class Telnet3270Client {
|
|||||||
public void sendEnter() {
|
public void sendEnter() {
|
||||||
inputProcessor.sendAid(haus.nightmare.lib3270j.protocol.DS3270Constants.AID_ENTER);
|
inputProcessor.sendAid(haus.nightmare.lib3270j.protocol.DS3270Constants.AID_ENTER);
|
||||||
}
|
}
|
||||||
|
public void processEnter() { inputProcessor.processEnter(); }
|
||||||
|
|
||||||
/** Send a PF key (1-24). */
|
/** Send a PF key (1-24). */
|
||||||
public void sendPF(int number) {
|
public void sendPF(int number) {
|
||||||
@@ -177,6 +372,7 @@ public class Telnet3270Client {
|
|||||||
}
|
}
|
||||||
inputProcessor.sendAid(aid);
|
inputProcessor.sendAid(aid);
|
||||||
}
|
}
|
||||||
|
public void processPF(int number) { inputProcessor.processPF(number); }
|
||||||
|
|
||||||
/** Send a PA key (1-3). */
|
/** Send a PA key (1-3). */
|
||||||
public void sendPA(int number) {
|
public void sendPA(int number) {
|
||||||
@@ -189,40 +385,104 @@ public class Telnet3270Client {
|
|||||||
}
|
}
|
||||||
inputProcessor.sendAid(aid);
|
inputProcessor.sendAid(aid);
|
||||||
}
|
}
|
||||||
|
public void processPA(int number) { inputProcessor.processPA(number); }
|
||||||
|
|
||||||
/** Send Clear key. */
|
/** Send Clear key. */
|
||||||
public void sendClear() {
|
public void sendClear() {
|
||||||
inputProcessor.sendAid(haus.nightmare.lib3270j.protocol.DS3270Constants.AID_CLEAR);
|
inputProcessor.sendAid(haus.nightmare.lib3270j.protocol.DS3270Constants.AID_CLEAR);
|
||||||
}
|
}
|
||||||
|
public void processClear() { inputProcessor.processClear(); }
|
||||||
|
|
||||||
/** Move cursor up. */
|
/** Move cursor up. */
|
||||||
public void cursorUp() { inputProcessor.cursorUp(); }
|
public void cursorUp() { inputProcessor.cursorUp(); }
|
||||||
|
public void processCursorUp() { inputProcessor.processCursorUp(); }
|
||||||
|
|
||||||
/** Move cursor down. */
|
/** Move cursor down. */
|
||||||
public void cursorDown() { inputProcessor.cursorDown(); }
|
public void cursorDown() { inputProcessor.cursorDown(); }
|
||||||
|
public void processCursorDown() { inputProcessor.processCursorDown(); }
|
||||||
|
|
||||||
/** Move cursor left. */
|
/** Move cursor left. */
|
||||||
public void cursorLeft() { inputProcessor.cursorLeft(); }
|
public void cursorLeft() { inputProcessor.cursorLeft(); }
|
||||||
|
public void processCursorLeft() { inputProcessor.processCursorLeft(); }
|
||||||
|
|
||||||
/** Move cursor right. */
|
/** Move cursor right. */
|
||||||
public void cursorRight() { inputProcessor.cursorRight(); }
|
public void cursorRight() { inputProcessor.cursorRight(); }
|
||||||
|
public void processCursorRight() { inputProcessor.processCursorRight(); }
|
||||||
|
|
||||||
/** Move cursor to home position. */
|
/** Move cursor to home position. */
|
||||||
public void cursorHome() { inputProcessor.cursorHome(); }
|
public void cursorHome() { inputProcessor.cursorHome(); }
|
||||||
|
public void processHome() { inputProcessor.processHome(); }
|
||||||
|
|
||||||
/** Tab to next unprotected field. */
|
/** Tab to next unprotected field. */
|
||||||
public void tab() { inputProcessor.tab(); }
|
public void tab() { inputProcessor.tab(); }
|
||||||
|
public void processTab() { inputProcessor.processTab(); }
|
||||||
|
|
||||||
/** Back-tab to previous unprotected field. */
|
/** Back-tab to previous unprotected field. */
|
||||||
public void backTab() { inputProcessor.backTab(); }
|
public void backTab() { inputProcessor.backTab(); }
|
||||||
|
public void processBackTab() { inputProcessor.processBackTab(); }
|
||||||
|
|
||||||
/** Move cursor to next line. */
|
/** Move cursor to next line. */
|
||||||
public void newline() { inputProcessor.newline(); }
|
public void newline() { inputProcessor.newline(); }
|
||||||
|
public void processNewline() { inputProcessor.processNewline(); }
|
||||||
|
|
||||||
|
/** Delete character under cursor. */
|
||||||
|
public void deleteChar() { inputProcessor.deleteChar(); }
|
||||||
|
public void processDelete() { inputProcessor.processDelete(); }
|
||||||
|
|
||||||
|
/** Backspace character before cursor. */
|
||||||
|
public void backspace() { inputProcessor.backspace(); }
|
||||||
|
public void processBackspace() { inputProcessor.processBackspace(); }
|
||||||
|
|
||||||
|
/** Erase to end of field. */
|
||||||
|
public void eraseEof() { inputProcessor.eraseEof(); }
|
||||||
|
public void processEraseEOF() { inputProcessor.processEraseEOF(); }
|
||||||
|
|
||||||
/** Erase all unprotected fields. */
|
/** Erase all unprotected fields. */
|
||||||
public void eraseInput() { inputProcessor.eraseInput(); }
|
public void eraseInput() { inputProcessor.eraseInput(); }
|
||||||
|
public void processEraseInput() { inputProcessor.processEraseInput(); }
|
||||||
|
|
||||||
/** Insert Duplicate order. */
|
/** Insert Duplicate order. */
|
||||||
public void dup() { inputProcessor.dup(); }
|
public void dup() { inputProcessor.dup(); }
|
||||||
|
public void processDup() { inputProcessor.processDup(); }
|
||||||
|
|
||||||
/** Insert Field Mark order. */
|
/** Insert Field Mark order. */
|
||||||
public void fieldMark() { inputProcessor.fieldMark(); }
|
public void fieldMark() { inputProcessor.fieldMark(); }
|
||||||
|
public void processFieldMark() { inputProcessor.processFieldMark(); }
|
||||||
|
|
||||||
|
/** Toggle Insert Mode. */
|
||||||
|
public void toggleInsert() { inputProcessor.setInsertMode(!inputProcessor.isInsertMode()); }
|
||||||
|
public void processToggleInsert() { inputProcessor.processToggleInsert(); }
|
||||||
|
|
||||||
|
/** Move word left. */
|
||||||
|
public void processWordLeft() { inputProcessor.processWordLeft(); }
|
||||||
|
|
||||||
|
/** Move word right. */
|
||||||
|
public void processWordRight() { inputProcessor.processWordRight(); }
|
||||||
|
|
||||||
|
/** Move to field end. */
|
||||||
|
public void processFieldEnd() { inputProcessor.processFieldEnd(); }
|
||||||
|
|
||||||
/** Attention key. */
|
/** Attention key. */
|
||||||
public void attn() { inputProcessor.attn(); }
|
public void attn() { inputProcessor.attn(); }
|
||||||
|
public void processAttn() { inputProcessor.processAttn(); }
|
||||||
|
|
||||||
/** SysReq key. */
|
/** SysReq key. */
|
||||||
public void sysReq() { inputProcessor.sysReq(); }
|
public void sysReq() { inputProcessor.sysReq(); }
|
||||||
/** Reset (unlock keyboard). */
|
public void processSysReq() { inputProcessor.processSysReq(); }
|
||||||
|
|
||||||
|
/** Reset (unlock keyboard, reset OIA). */
|
||||||
public void reset() { inputProcessor.reset(); }
|
public void reset() { inputProcessor.reset(); }
|
||||||
|
public void processReset() { inputProcessor.processReset(); }
|
||||||
|
|
||||||
|
/** Trigger 3270 CURSR SEL (Cursor Select) key at current cursor position. */
|
||||||
|
public boolean cursorSelect() { return inputProcessor.cursorSelect(); }
|
||||||
|
public boolean processCurSel() { return inputProcessor.processCurSel(); }
|
||||||
|
public boolean processCursorSelect() { return inputProcessor.processCursorSelect(); }
|
||||||
|
|
||||||
|
/** Trigger Light Pen selection at the specified screen address or cursor position. */
|
||||||
|
public boolean lightPenSelect(int address) { return inputProcessor.lightPenSelect(address); }
|
||||||
|
public boolean processLightPen() { return inputProcessor.processLightPen(); }
|
||||||
|
public boolean processLightPen(int addr) { return inputProcessor.processLightPen(addr); }
|
||||||
|
|
||||||
public haus.nightmare.lib3270j.graphics.ProgramSymbolManager getProgramSymbolManager() {
|
public haus.nightmare.lib3270j.graphics.ProgramSymbolManager getProgramSymbolManager() {
|
||||||
return dsProcessor.getProgramSymbolManager();
|
return dsProcessor.getProgramSymbolManager();
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base implementation for Single-Byte Character Set (SBCS) EBCDIC Code Pages.
|
||||||
|
*/
|
||||||
|
public abstract class AbstractCodePage implements CodePage {
|
||||||
|
|
||||||
|
protected final String id;
|
||||||
|
protected final String description;
|
||||||
|
protected final int cpgid;
|
||||||
|
protected final int cgcsgid;
|
||||||
|
|
||||||
|
protected final int[] toUnicode = new int[256];
|
||||||
|
protected final int[] toEbcdic = new int[256];
|
||||||
|
protected final Map<Character, Integer> extendedToEbcdic = new HashMap<>();
|
||||||
|
|
||||||
|
public AbstractCodePage(String id, String description, int cpgid, int cgcsgid, int[] unicodeMapping) {
|
||||||
|
this(id, description, cpgid, cgcsgid, null, unicodeMapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractCodePage(String id, String description, int cpgid, int cgcsgid, String javaCharsetName, int[] unicodeMapping) {
|
||||||
|
this.id = id;
|
||||||
|
this.description = description;
|
||||||
|
this.cpgid = cpgid;
|
||||||
|
this.cgcsgid = cgcsgid;
|
||||||
|
|
||||||
|
int[] map = loadMapping(javaCharsetName, unicodeMapping);
|
||||||
|
System.arraycopy(map, 0, this.toUnicode, 0, 256);
|
||||||
|
initReverseMapping();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int[] loadMapping(String charsetName, int[] fallback) {
|
||||||
|
if (charsetName != null) {
|
||||||
|
String[] candidates = {
|
||||||
|
charsetName,
|
||||||
|
"IBM" + charsetName,
|
||||||
|
"Cp" + charsetName,
|
||||||
|
"IBM-" + charsetName,
|
||||||
|
"x-IBM" + charsetName
|
||||||
|
};
|
||||||
|
for (String name : candidates) {
|
||||||
|
try {
|
||||||
|
if (Charset.isSupported(name)) {
|
||||||
|
Charset cs = Charset.forName(name);
|
||||||
|
int[] map = new int[256];
|
||||||
|
for (int i = 0; i < 256; i++) {
|
||||||
|
byte[] b = new byte[]{(byte) i};
|
||||||
|
String s = new String(b, cs);
|
||||||
|
map[i] = (!s.isEmpty() && s.charAt(0) != '\uFFFD') ? s.charAt(0) : (fallback != null ? fallback[i] : 0);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fallback != null ? fallback : new int[256];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void initReverseMapping() {
|
||||||
|
Arrays.fill(toEbcdic, -1);
|
||||||
|
for (int i = 0; i < 256; i++) {
|
||||||
|
int uc = toUnicode[i];
|
||||||
|
if (uc >= 0 && uc < 256) {
|
||||||
|
if (toEbcdic[uc] == -1) {
|
||||||
|
toEbcdic[uc] = i;
|
||||||
|
}
|
||||||
|
} else if (uc >= 256) {
|
||||||
|
extendedToEbcdic.putIfAbsent((char) uc, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCodePageId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCpgid() {
|
||||||
|
return cpgid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCgcsgid() {
|
||||||
|
return cgcsgid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDBCS() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char ebcdicToUnicode(int ebc) {
|
||||||
|
return (char) toUnicode[ebc & 0xFF];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int unicodeToEbcdic(char unicode) {
|
||||||
|
int u = unicode;
|
||||||
|
if (u >= 0 && u < 256) {
|
||||||
|
return toEbcdic[u];
|
||||||
|
}
|
||||||
|
Integer val = extendedToEbcdic.get(unicode);
|
||||||
|
return val != null ? val : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte unicodeToEbcdicSafe(char unicode) {
|
||||||
|
int ebc = unicodeToEbcdic(unicode);
|
||||||
|
return (byte) (ebc >= 0 ? ebc : 0x40);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char dbcsToUnicode(int b1, int b2) {
|
||||||
|
return '\uFFFD';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int unicodeToDbcs(char unicode) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String ebcdicToString(byte[] ebcdic, int offset, int length) {
|
||||||
|
if (ebcdic == null || length <= 0) return "";
|
||||||
|
char[] chars = new char[length];
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
chars[i] = ebcdicToUnicode(ebcdic[offset + i]);
|
||||||
|
}
|
||||||
|
return new String(chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] stringToEbcdic(String s) {
|
||||||
|
if (s == null || s.isEmpty()) return new byte[0];
|
||||||
|
byte[] bytes = new byte[s.length()];
|
||||||
|
for (int i = 0; i < s.length(); i++) {
|
||||||
|
bytes[i] = unicodeToEbcdicSafe(s.charAt(i));
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return id + " (" + description + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.CharBuffer;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.CharsetDecoder;
|
||||||
|
import java.nio.charset.CharsetEncoder;
|
||||||
|
import java.nio.charset.CodingErrorAction;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base implementation for Mixed Double-Byte Character Set (DBCS) EBCDIC Code Pages
|
||||||
|
* (e.g. Japanese Katakana 930, Japanese Latin 939, Chinese 935/937/1388, Korean 933).
|
||||||
|
*/
|
||||||
|
public abstract class AbstractDBCSCodePage extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final int SO = 0x0E; // Shift Out (enter DBCS mode)
|
||||||
|
public static final int SI = 0x0F; // Shift In (enter SBCS mode)
|
||||||
|
|
||||||
|
protected final Map<Integer, Character> dbcsToUnicodeMap = new HashMap<>();
|
||||||
|
protected final Map<Character, Integer> unicodeToDbcsMap = new HashMap<>();
|
||||||
|
protected Charset nioCharset;
|
||||||
|
|
||||||
|
public AbstractDBCSCodePage(String id, String description, int cpgid, int cgcsgid,
|
||||||
|
int[] sbcsMapping, String nioCharsetName) {
|
||||||
|
super(id, description, cpgid, cgcsgid, sbcsMapping);
|
||||||
|
if (nioCharsetName != null) {
|
||||||
|
try {
|
||||||
|
if (Charset.isSupported(nioCharsetName)) {
|
||||||
|
this.nioCharset = Charset.forName(nioCharsetName);
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
// Initialize ideographic space (0x4040 <-> \u3000)
|
||||||
|
registerDbcsPair(0x4040, '\u3000');
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerDbcsPair(int ebcdicDBCS, char unicodeChar) {
|
||||||
|
dbcsToUnicodeMap.put(ebcdicDBCS, unicodeChar);
|
||||||
|
unicodeToDbcsMap.put(unicodeChar, ebcdicDBCS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDBCS() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char dbcsToUnicode(int b1, int b2) {
|
||||||
|
int key = ((b1 & 0xFF) << 8) | (b2 & 0xFF);
|
||||||
|
Character c = dbcsToUnicodeMap.get(key);
|
||||||
|
if (c != null) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nioCharset != null) {
|
||||||
|
try {
|
||||||
|
byte[] raw = new byte[] { (byte) SO, (byte) b1, (byte) b2, (byte) SI };
|
||||||
|
CharBuffer cb = nioCharset.decode(ByteBuffer.wrap(raw));
|
||||||
|
if (cb.hasRemaining()) {
|
||||||
|
char decoded = cb.get();
|
||||||
|
if (decoded != '\uFFFD' && decoded != 0) {
|
||||||
|
registerDbcsPair(key, decoded);
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '?';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int unicodeToDbcs(char unicode) {
|
||||||
|
Integer val = unicodeToDbcsMap.get(unicode);
|
||||||
|
if (val != null) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nioCharset != null) {
|
||||||
|
try {
|
||||||
|
CharsetEncoder encoder = nioCharset.newEncoder()
|
||||||
|
.onMalformedInput(CodingErrorAction.REPLACE)
|
||||||
|
.onUnmappableCharacter(CodingErrorAction.REPLACE);
|
||||||
|
ByteBuffer bb = encoder.encode(CharBuffer.wrap(new char[]{unicode}));
|
||||||
|
byte[] bytes = new byte[bb.remaining()];
|
||||||
|
bb.get(bytes);
|
||||||
|
|
||||||
|
// Check if encoded as SO + b1 + b2 + SI or b1 + b2
|
||||||
|
if (bytes.length >= 4 && (bytes[0] & 0xFF) == SO) {
|
||||||
|
int key = ((bytes[1] & 0xFF) << 8) | (bytes[2] & 0xFF);
|
||||||
|
registerDbcsPair(key, unicode);
|
||||||
|
return key;
|
||||||
|
} else if (bytes.length == 2) {
|
||||||
|
int key = ((bytes[0] & 0xFF) << 8) | (bytes[1] & 0xFF);
|
||||||
|
registerDbcsPair(key, unicode);
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String ebcdicToString(byte[] ebcdic, int offset, int length) {
|
||||||
|
return ebcdicToString(ebcdic, offset, length, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate an EBCDIC byte array slice to a Unicode String with optional SO/SI escape preservation.
|
||||||
|
*
|
||||||
|
* @param ebcdic source byte array
|
||||||
|
* @param offset start offset
|
||||||
|
* @param length number of bytes
|
||||||
|
* @param preserveSOSI if true, preserves Shift-Out (\u000E) and Shift-In (\u000F) control characters
|
||||||
|
* @return translated Unicode String
|
||||||
|
*/
|
||||||
|
public String ebcdicToString(byte[] ebcdic, int offset, int length, boolean preserveSOSI) {
|
||||||
|
if (ebcdic == null || length <= 0) return "";
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder(length);
|
||||||
|
boolean inDBCS = false;
|
||||||
|
int end = Math.min(ebcdic.length, offset + length);
|
||||||
|
int i = offset;
|
||||||
|
|
||||||
|
while (i < end) {
|
||||||
|
int b = ebcdic[i] & 0xFF;
|
||||||
|
if (b == SO) {
|
||||||
|
inDBCS = true;
|
||||||
|
if (preserveSOSI) {
|
||||||
|
sb.append((char) SO);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
} else if (b == SI) {
|
||||||
|
inDBCS = false;
|
||||||
|
if (preserveSOSI) {
|
||||||
|
sb.append((char) SI);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
} else if (inDBCS) {
|
||||||
|
if (i + 1 < end) {
|
||||||
|
int b2 = ebcdic[i + 1] & 0xFF;
|
||||||
|
if (b2 == SI) {
|
||||||
|
// Orphaned single byte before SI
|
||||||
|
inDBCS = false;
|
||||||
|
if (preserveSOSI) {
|
||||||
|
sb.append(ebcdicToUnicode(b));
|
||||||
|
sb.append((char) SI);
|
||||||
|
}
|
||||||
|
i += 2;
|
||||||
|
} else if (b2 == SO) {
|
||||||
|
// Unmatched byte followed by another SO
|
||||||
|
sb.append(ebcdicToUnicode(b));
|
||||||
|
i++;
|
||||||
|
} else {
|
||||||
|
sb.append(dbcsToUnicode(b, b2));
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Trailing single byte inside unclosed DBCS shift
|
||||||
|
sb.append(ebcdicToUnicode(b));
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sb.append(ebcdicToUnicode(b));
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] stringToEbcdic(String s) {
|
||||||
|
return stringToEbcdic(s, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate a Unicode String to EBCDIC byte array with optional parsing of embedded SO/SI markers.
|
||||||
|
*
|
||||||
|
* @param s input Unicode string
|
||||||
|
* @param parseSOSIMarkers whether to interpret embedded \u000E (SO) and \u000F (SI) characters
|
||||||
|
* @return EBCDIC byte array with balanced SO/SI framing
|
||||||
|
*/
|
||||||
|
public byte[] stringToEbcdic(String s, boolean parseSOSIMarkers) {
|
||||||
|
if (s == null || s.isEmpty()) return new byte[0];
|
||||||
|
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream(s.length() * 2);
|
||||||
|
boolean inDBCS = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < s.length(); i++) {
|
||||||
|
char c = s.charAt(i);
|
||||||
|
|
||||||
|
if (parseSOSIMarkers && c == (char) SO) {
|
||||||
|
if (!inDBCS) {
|
||||||
|
out.write(SO);
|
||||||
|
inDBCS = true;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
} else if (parseSOSIMarkers && c == (char) SI) {
|
||||||
|
if (inDBCS) {
|
||||||
|
out.write(SI);
|
||||||
|
inDBCS = false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dbcsCode = unicodeToDbcs(c);
|
||||||
|
|
||||||
|
if (dbcsCode >= 0) {
|
||||||
|
// Character is DBCS
|
||||||
|
if (!inDBCS) {
|
||||||
|
out.write(SO);
|
||||||
|
inDBCS = true;
|
||||||
|
}
|
||||||
|
out.write((dbcsCode >> 8) & 0xFF);
|
||||||
|
out.write(dbcsCode & 0xFF);
|
||||||
|
} else {
|
||||||
|
// Character is SBCS
|
||||||
|
if (inDBCS) {
|
||||||
|
out.write(SI);
|
||||||
|
inDBCS = false;
|
||||||
|
}
|
||||||
|
out.write(unicodeToEbcdicSafe(c));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-close unclosed DBCS shift sequence
|
||||||
|
if (inDBCS) {
|
||||||
|
out.write(SI);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to decode an EBCDIC byte array to UTF-8 String preserving SO/SI control codes.
|
||||||
|
*/
|
||||||
|
public String ebcdicToUtf8WithSOSI(byte[] ebcdic) {
|
||||||
|
if (ebcdic == null) return "";
|
||||||
|
return ebcdicToString(ebcdic, 0, ebcdic.length, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to encode a UTF-8/Unicode String with embedded SO/SI markers to EBCDIC.
|
||||||
|
*/
|
||||||
|
public byte[] utf8WithSOSIToEbcdic(String s) {
|
||||||
|
return stringToEbcdic(s, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface defining character translation and metadata for EBCDIC code pages
|
||||||
|
* conforming to IBM Host On-Demand (HoD v14) converters specification.
|
||||||
|
*/
|
||||||
|
public interface CodePage {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the primary identifier for this code page (e.g. "037", "1047", "500", "273").
|
||||||
|
*/
|
||||||
|
String getCodePageId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a human-readable description (e.g. "US / Canada - EBCDIC").
|
||||||
|
*/
|
||||||
|
String getDescription();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Code Page Global ID (CPGID / CCSID) for 3270 query replies.
|
||||||
|
*/
|
||||||
|
int getCpgid();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Graphic Character Set Global ID (GCSGID) for 3270 query replies.
|
||||||
|
*/
|
||||||
|
int getCgcsgid();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate an EBCDIC byte (0x00-0xFF) to a Unicode character.
|
||||||
|
*/
|
||||||
|
char ebcdicToUnicode(int ebc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate a Unicode character to an EBCDIC byte value.
|
||||||
|
* Returns -1 if unmappable.
|
||||||
|
*/
|
||||||
|
int unicodeToEbcdic(char unicode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate a Unicode character to an EBCDIC byte value, returning a safe
|
||||||
|
* fallback (default EBCDIC space 0x40) if unmappable.
|
||||||
|
*/
|
||||||
|
byte unicodeToEbcdicSafe(char unicode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate an EBCDIC byte array slice to a Unicode String.
|
||||||
|
*/
|
||||||
|
String ebcdicToString(byte[] ebcdic, int offset, int length);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate a Unicode String to an EBCDIC byte array.
|
||||||
|
*/
|
||||||
|
byte[] stringToEbcdic(String s);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this code page is a double-byte (DBCS) / mixed SBCS+DBCS code page.
|
||||||
|
*/
|
||||||
|
boolean isDBCS();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate a double-byte EBCDIC pair (b1, b2) to a Unicode character.
|
||||||
|
* Returns '\uFFFD' or '?' if unmappable.
|
||||||
|
*/
|
||||||
|
char dbcsToUnicode(int b1, int b2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate a Unicode character to a double-byte EBCDIC code ((b1 << 8) | b2).
|
||||||
|
* Returns -1 if unmappable.
|
||||||
|
*/
|
||||||
|
int unicodeToDbcs(char unicode);
|
||||||
|
}
|
||||||
@@ -0,0 +1,367 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registry and factory for 3270 EBCDIC Code Pages.
|
||||||
|
* Manages built-in SBCS/DBCS codepages, alias normalization, and dynamic JVM Charset resolution.
|
||||||
|
*/
|
||||||
|
public class CodePageRegistry {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(CodePageRegistry.class.getName());
|
||||||
|
|
||||||
|
private static final Map<String, CodePage> CODE_PAGES = new LinkedHashMap<>();
|
||||||
|
private static final Map<String, String> ALIASES = new HashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
// Register core SBCS codepages
|
||||||
|
register(new Cp037());
|
||||||
|
register(new Cp1047());
|
||||||
|
register(new Cp500());
|
||||||
|
register(new Cp273());
|
||||||
|
register(new Cp277());
|
||||||
|
register(new Cp278());
|
||||||
|
register(new Cp280());
|
||||||
|
register(new Cp284());
|
||||||
|
register(new Cp285());
|
||||||
|
register(new Cp297());
|
||||||
|
register(new Cp870());
|
||||||
|
register(new Cp871());
|
||||||
|
register(new Cp875());
|
||||||
|
register(new Cp1026());
|
||||||
|
|
||||||
|
// Register Euro variants (1140-1149)
|
||||||
|
register(new CpEuroVariants.Cp1140());
|
||||||
|
register(new CpEuroVariants.Cp1141());
|
||||||
|
register(new CpEuroVariants.Cp1142());
|
||||||
|
register(new CpEuroVariants.Cp1143());
|
||||||
|
register(new CpEuroVariants.Cp1144());
|
||||||
|
register(new CpEuroVariants.Cp1145());
|
||||||
|
register(new CpEuroVariants.Cp1146());
|
||||||
|
register(new CpEuroVariants.Cp1147());
|
||||||
|
register(new CpEuroVariants.Cp1148());
|
||||||
|
register(new CpEuroVariants.Cp1149());
|
||||||
|
|
||||||
|
// Register DBCS mixed codepages
|
||||||
|
register(new Cp930());
|
||||||
|
register(new Cp939());
|
||||||
|
register(new Cp935());
|
||||||
|
register(new Cp935.Cp1388());
|
||||||
|
register(new Cp937());
|
||||||
|
register(new Cp937.Cp1371());
|
||||||
|
register(new Cp933());
|
||||||
|
|
||||||
|
// Register Phase 8 Extended EBCDIC Codepages (Arabic, Hebrew, Thai, Cyrillic, Turkish, Extended DBCS)
|
||||||
|
registerExtendedCodepages();
|
||||||
|
|
||||||
|
// Setup common aliases
|
||||||
|
addAlias("us", "037");
|
||||||
|
addAlias("usa", "037");
|
||||||
|
addAlias("ebcdic-cp-us", "037");
|
||||||
|
addAlias("ebcdic-cp-ca", "037");
|
||||||
|
addAlias("ebcdic-cp-nl", "037");
|
||||||
|
|
||||||
|
addAlias("posix", "1047");
|
||||||
|
addAlias("unix", "1047");
|
||||||
|
addAlias("open-systems", "1047");
|
||||||
|
addAlias("zos-unix", "1047");
|
||||||
|
|
||||||
|
addAlias("intl", "500");
|
||||||
|
addAlias("international", "500");
|
||||||
|
addAlias("ebcdic-cp-ch", "500");
|
||||||
|
|
||||||
|
addAlias("de", "273");
|
||||||
|
addAlias("germany", "273");
|
||||||
|
addAlias("austria", "273");
|
||||||
|
addAlias("ebcdic-cp-de", "273");
|
||||||
|
|
||||||
|
addAlias("dk", "277");
|
||||||
|
addAlias("no", "277");
|
||||||
|
addAlias("denmark", "277");
|
||||||
|
addAlias("norway", "277");
|
||||||
|
addAlias("ebcdic-cp-dk", "277");
|
||||||
|
addAlias("ebcdic-cp-no", "277");
|
||||||
|
|
||||||
|
addAlias("se", "278");
|
||||||
|
addAlias("fi", "278");
|
||||||
|
addAlias("sweden", "278");
|
||||||
|
addAlias("finland", "278");
|
||||||
|
addAlias("ebcdic-cp-se", "278");
|
||||||
|
addAlias("ebcdic-cp-fi", "278");
|
||||||
|
|
||||||
|
addAlias("it", "280");
|
||||||
|
addAlias("italy", "280");
|
||||||
|
addAlias("ebcdic-cp-it", "280");
|
||||||
|
|
||||||
|
addAlias("es", "284");
|
||||||
|
addAlias("spain", "284");
|
||||||
|
addAlias("latin-america", "284");
|
||||||
|
addAlias("ebcdic-cp-es", "284");
|
||||||
|
|
||||||
|
addAlias("uk", "285");
|
||||||
|
addAlias("gb", "285");
|
||||||
|
addAlias("great-britain", "285");
|
||||||
|
addAlias("ebcdic-cp-gb", "285");
|
||||||
|
|
||||||
|
addAlias("fr", "297");
|
||||||
|
addAlias("france", "297");
|
||||||
|
addAlias("ebcdic-cp-fr", "297");
|
||||||
|
|
||||||
|
addAlias("latin2", "870");
|
||||||
|
addAlias("pl", "870");
|
||||||
|
addAlias("cz", "870");
|
||||||
|
addAlias("hu", "870");
|
||||||
|
addAlias("ebcdic-cp-roece", "870");
|
||||||
|
|
||||||
|
addAlias("is", "871");
|
||||||
|
addAlias("iceland", "871");
|
||||||
|
addAlias("ebcdic-cp-is", "871");
|
||||||
|
|
||||||
|
addAlias("gr", "875");
|
||||||
|
addAlias("greece", "875");
|
||||||
|
addAlias("greek", "875");
|
||||||
|
addAlias("ebcdic-cp-gr", "875");
|
||||||
|
|
||||||
|
addAlias("tr", "1026");
|
||||||
|
addAlias("turkey", "1026");
|
||||||
|
addAlias("turkish", "1026");
|
||||||
|
addAlias("ebcdic-cp-tr", "1026");
|
||||||
|
|
||||||
|
addAlias("ja", "930");
|
||||||
|
addAlias("japanese", "930");
|
||||||
|
addAlias("katakana", "930");
|
||||||
|
addAlias("ja-latin", "939");
|
||||||
|
|
||||||
|
addAlias("zh", "935");
|
||||||
|
addAlias("chinese-simplified", "935");
|
||||||
|
addAlias("zh-tw", "937");
|
||||||
|
addAlias("chinese-traditional", "937");
|
||||||
|
|
||||||
|
addAlias("ko", "933");
|
||||||
|
addAlias("korean", "933");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void register(CodePage cp) {
|
||||||
|
if (cp != null) {
|
||||||
|
CODE_PAGES.put(cp.getCodePageId(), cp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addAlias(String alias, String targetId) {
|
||||||
|
if (alias != null && targetId != null) {
|
||||||
|
ALIASES.put(normalizeKey(alias), targetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String normalizeKey(String name) {
|
||||||
|
if (name == null) return "";
|
||||||
|
String s = name.trim().toLowerCase();
|
||||||
|
s = s.replace("_", "").replace("-", "");
|
||||||
|
if (s.startsWith("ebcdiccp")) {
|
||||||
|
s = s.substring(8);
|
||||||
|
} else if (s.startsWith("ebcdic")) {
|
||||||
|
s = s.substring(6);
|
||||||
|
} else if (s.startsWith("ibm")) {
|
||||||
|
s = s.substring(3);
|
||||||
|
} else if (s.startsWith("cp")) {
|
||||||
|
s = s.substring(2);
|
||||||
|
} else if (s.startsWith("ccsid")) {
|
||||||
|
s = s.substring(5);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look up a code page by ID or alias.
|
||||||
|
* If not found in built-ins, attempts to load via java.nio.charset.Charset.
|
||||||
|
* Falls back to CP037 if completely unresolvable.
|
||||||
|
*/
|
||||||
|
public static CodePage getCodePage(String name) {
|
||||||
|
if (name == null || name.trim().isEmpty()) {
|
||||||
|
return CODE_PAGES.get("037");
|
||||||
|
}
|
||||||
|
|
||||||
|
String raw = name.trim();
|
||||||
|
// Direct match
|
||||||
|
CodePage cp = CODE_PAGES.get(raw);
|
||||||
|
if (cp != null) return cp;
|
||||||
|
|
||||||
|
// Normalized key lookup
|
||||||
|
String norm = normalizeKey(raw);
|
||||||
|
cp = CODE_PAGES.get(norm);
|
||||||
|
if (cp != null) return cp;
|
||||||
|
|
||||||
|
// Alias lookup
|
||||||
|
String targetId = ALIASES.get(norm);
|
||||||
|
if (targetId != null) {
|
||||||
|
cp = CODE_PAGES.get(targetId);
|
||||||
|
if (cp != null) return cp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try standard NIO Charset dynamic adapter
|
||||||
|
try {
|
||||||
|
if (Charset.isSupported(raw)) {
|
||||||
|
return new NioCodePageAdapter(raw);
|
||||||
|
}
|
||||||
|
String ibmName = "IBM" + norm;
|
||||||
|
if (Charset.isSupported(ibmName)) {
|
||||||
|
return new NioCodePageAdapter(ibmName);
|
||||||
|
}
|
||||||
|
String cpName = "Cp" + norm;
|
||||||
|
if (Charset.isSupported(cpName)) {
|
||||||
|
return new NioCodePageAdapter(cpName);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.fine("Dynamic charset loading failed for " + name + ": " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
log.warning("CodePage not recognized: '" + name + "'; falling back to CP037");
|
||||||
|
return CODE_PAGES.get("037");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register extended EBCDIC codepages from HoD v14 converters:
|
||||||
|
* - Arabic: Cp420, Cp424
|
||||||
|
* - Hebrew: Cp424, Cp803
|
||||||
|
* - Thai: Cp838, Cp1160
|
||||||
|
* - Cyrillic: Cp1025, Cp1123, Cp1154, Cp880
|
||||||
|
* - Turkish: Cp1155, Cp905
|
||||||
|
* - Extended Chinese DBCS: Cp1388, Cp1371
|
||||||
|
*/
|
||||||
|
public static void registerExtendedCodepages() {
|
||||||
|
// Arabic & Hebrew
|
||||||
|
register(new Cp420());
|
||||||
|
register(new Cp424());
|
||||||
|
register(new Cp803());
|
||||||
|
|
||||||
|
// Thai
|
||||||
|
register(new Cp838());
|
||||||
|
register(new Cp1160());
|
||||||
|
|
||||||
|
// Cyrillic / Russian / Ukrainian
|
||||||
|
register(new Cp1025());
|
||||||
|
register(new Cp1123());
|
||||||
|
register(new Cp1154());
|
||||||
|
register(new Cp880());
|
||||||
|
|
||||||
|
// Turkish
|
||||||
|
register(new Cp1155());
|
||||||
|
register(new Cp905());
|
||||||
|
|
||||||
|
// Extended Chinese DBCS
|
||||||
|
register(new Cp1388());
|
||||||
|
register(new Cp1371());
|
||||||
|
|
||||||
|
// Setup Extended Aliases
|
||||||
|
addAlias("ar", "420");
|
||||||
|
addAlias("arabic", "420");
|
||||||
|
addAlias("ebcdic-cp-ar", "420");
|
||||||
|
|
||||||
|
addAlias("he", "424");
|
||||||
|
addAlias("hebrew", "424");
|
||||||
|
addAlias("hebrew-lowercase", "424");
|
||||||
|
addAlias("ebcdic-cp-he", "424");
|
||||||
|
|
||||||
|
addAlias("hebrew-old", "803");
|
||||||
|
addAlias("israel", "803");
|
||||||
|
addAlias("iw", "803");
|
||||||
|
addAlias("ebcdic-cp-he-old", "803");
|
||||||
|
|
||||||
|
addAlias("th", "838");
|
||||||
|
addAlias("thai", "838");
|
||||||
|
addAlias("ebcdic-cp-th", "838");
|
||||||
|
addAlias("thai-euro", "1160");
|
||||||
|
|
||||||
|
addAlias("ru", "1025");
|
||||||
|
addAlias("russian", "1025");
|
||||||
|
addAlias("cyrillic", "1025");
|
||||||
|
addAlias("ebcdic-cp-ru", "1025");
|
||||||
|
|
||||||
|
addAlias("ukraine", "1123");
|
||||||
|
addAlias("ukrainian", "1123");
|
||||||
|
addAlias("ebcdic-cp-ua", "1123");
|
||||||
|
|
||||||
|
addAlias("cyrillic-euro", "1154");
|
||||||
|
addAlias("ru-euro", "1154");
|
||||||
|
addAlias("russian-euro", "1154");
|
||||||
|
|
||||||
|
addAlias("cyrillic-russian", "880");
|
||||||
|
addAlias("ru-old", "880");
|
||||||
|
|
||||||
|
addAlias("turkish-euro", "1155");
|
||||||
|
addAlias("tr-euro", "1155");
|
||||||
|
addAlias("turkish-latin3", "905");
|
||||||
|
addAlias("tr-latin3", "905");
|
||||||
|
|
||||||
|
addAlias("chinese-ext-simplified", "1388");
|
||||||
|
addAlias("zh-simplified-ext", "1388");
|
||||||
|
addAlias("zh-ext", "1388");
|
||||||
|
addAlias("chinese-ext-traditional", "1371");
|
||||||
|
addAlias("zh-traditional-ext", "1371");
|
||||||
|
addAlias("zh-tw-ext", "1371");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a code page is registered.
|
||||||
|
*/
|
||||||
|
public static boolean hasCodePage(String name) {
|
||||||
|
if (name == null || name.trim().isEmpty()) return false;
|
||||||
|
String raw = name.trim();
|
||||||
|
if (CODE_PAGES.containsKey(raw)) return true;
|
||||||
|
String norm = normalizeKey(raw);
|
||||||
|
if (CODE_PAGES.containsKey(norm)) return true;
|
||||||
|
return ALIASES.containsKey(norm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an unmodifiable list of all registered built-in CodePages.
|
||||||
|
*/
|
||||||
|
public static List<CodePage> getAvailableCodePages() {
|
||||||
|
return Collections.unmodifiableList(new ArrayList<>(CODE_PAGES.values()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an array of all registered built-in CodePage IDs.
|
||||||
|
*/
|
||||||
|
public static String[] getAvailableCodePageIds() {
|
||||||
|
return CODE_PAGES.keySet().toArray(new String[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dynamic fallback adapter wrapping any JVM java.nio.charset.Charset.
|
||||||
|
*/
|
||||||
|
public static class NioCodePageAdapter extends AbstractCodePage {
|
||||||
|
private final Charset charset;
|
||||||
|
|
||||||
|
public NioCodePageAdapter(String charsetName) {
|
||||||
|
this(Charset.forName(charsetName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public NioCodePageAdapter(Charset charset) {
|
||||||
|
super(charset.name(), "JVM Charset: " + charset.displayName(), parseCpgid(charset.name()), 697, buildMappingFromCharset(charset));
|
||||||
|
this.charset = charset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int parseCpgid(String name) {
|
||||||
|
try {
|
||||||
|
String num = name.replaceAll("\\D+", "");
|
||||||
|
if (!num.isEmpty()) {
|
||||||
|
return Integer.parseInt(num);
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
return 37;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int[] buildMappingFromCharset(Charset cs) {
|
||||||
|
int[] map = new int[256];
|
||||||
|
for (int i = 0; i < 256; i++) {
|
||||||
|
byte[] b = new byte[]{(byte) i};
|
||||||
|
String s = new String(b, cs);
|
||||||
|
map[i] = !s.isEmpty() ? s.charAt(0) : (char) i;
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 037 (US / Canada / Netherlands / Portugal / Brazil).
|
||||||
|
* CCSID / CPGID: 37, GCSGID: 697.
|
||||||
|
*/
|
||||||
|
public class Cp037 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "037";
|
||||||
|
public static final String DESCRIPTION = "US / Canada / Brazil - EBCDIC";
|
||||||
|
public static final int CPGID = 37;
|
||||||
|
public static final int GCSGID = 697;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = {
|
||||||
|
// 00-0F
|
||||||
|
0x0000, 0x0001, 0x0002, 0x0003, 0x009C, 0x0009, 0x0086, 0x007F,
|
||||||
|
0x0097, 0x008D, 0x008E, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
|
||||||
|
// 10-1F
|
||||||
|
0x0010, 0x0011, 0x0012, 0x0013, 0x009D, 0x0085, 0x0008, 0x0087,
|
||||||
|
0x0018, 0x0019, 0x0092, 0x008F, 0x001C, 0x001D, 0x001E, 0x001F,
|
||||||
|
// 20-2F
|
||||||
|
0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x000A, 0x0017, 0x001B,
|
||||||
|
0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x0005, 0x0006, 0x0007,
|
||||||
|
// 30-3F
|
||||||
|
0x0090, 0x0091, 0x0016, 0x0093, 0x0094, 0x0095, 0x0096, 0x0004,
|
||||||
|
0x0098, 0x0099, 0x009A, 0x009B, 0x0014, 0x0015, 0x009E, 0x001A,
|
||||||
|
// 40-4F (space, accent chars, punctuation)
|
||||||
|
0x0020, 0x00A0, 0x00E2, 0x00E4, 0x00E0, 0x00E1, 0x00E3, 0x00E5,
|
||||||
|
0x00E7, 0x00F1, 0x00A2, 0x002E, 0x003C, 0x0028, 0x002B, 0x007C,
|
||||||
|
// 50-5F
|
||||||
|
0x0026, 0x00E9, 0x00EA, 0x00EB, 0x00E8, 0x00ED, 0x00EE, 0x00EF,
|
||||||
|
0x00EC, 0x00DF, 0x0021, 0x0024, 0x002A, 0x0029, 0x003B, 0x00AC,
|
||||||
|
// 60-6F
|
||||||
|
0x002D, 0x002F, 0x00C2, 0x00C4, 0x00C0, 0x00C1, 0x00C3, 0x00C5,
|
||||||
|
0x00C7, 0x00D1, 0x00A6, 0x002C, 0x0025, 0x005F, 0x003E, 0x003F,
|
||||||
|
// 70-7F
|
||||||
|
0x00F8, 0x00C9, 0x00CA, 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF,
|
||||||
|
0x00CC, 0x0060, 0x003A, 0x0023, 0x0040, 0x0027, 0x003D, 0x0022,
|
||||||
|
// 80-8F (lowercase a-i)
|
||||||
|
0x00D8, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
|
||||||
|
0x0068, 0x0069, 0x00AB, 0x00BB, 0x00F0, 0x00FD, 0x00FE, 0x00B1,
|
||||||
|
// 90-9F (lowercase j-r)
|
||||||
|
0x00B0, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070,
|
||||||
|
0x0071, 0x0072, 0x00AA, 0x00BA, 0x00E6, 0x00B8, 0x00C6, 0x00A4,
|
||||||
|
// A0-AF (lowercase s-z)
|
||||||
|
0x00B5, 0x007E, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078,
|
||||||
|
0x0079, 0x007A, 0x00A1, 0x00BF, 0x00D0, 0x00DD, 0x00DE, 0x00AE,
|
||||||
|
// B0-BF
|
||||||
|
0x005E, 0x00A3, 0x00A5, 0x00B7, 0x00A9, 0x00A7, 0x00B6, 0x00BC,
|
||||||
|
0x00BD, 0x00BE, 0x005B, 0x005D, 0x00AF, 0x00A8, 0x00B4, 0x00D7,
|
||||||
|
// C0-CF (uppercase A-I)
|
||||||
|
0x007B, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
|
||||||
|
0x0048, 0x0049, 0x00AD, 0x00F4, 0x00F6, 0x00F2, 0x00F3, 0x00F5,
|
||||||
|
// D0-DF (uppercase J-R)
|
||||||
|
0x007D, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050,
|
||||||
|
0x0051, 0x0052, 0x00B9, 0x00FB, 0x00FC, 0x00F9, 0x00FA, 0x00FF,
|
||||||
|
// E0-EF (uppercase S-Z)
|
||||||
|
0x005C, 0x00F7, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058,
|
||||||
|
0x0059, 0x005A, 0x00B2, 0x00D4, 0x00D6, 0x00D2, 0x00D3, 0x00D5,
|
||||||
|
// F0-FF (digits 0-9)
|
||||||
|
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
|
||||||
|
0x0038, 0x0039, 0x00B3, 0x00DB, 0x00DC, 0x00D9, 0x00DA, 0x009F,
|
||||||
|
};
|
||||||
|
|
||||||
|
public Cp037() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "037", MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 1025 (Cyrillic Multilingual EBCDIC - Russian, Bulgarian, Belarusian, Serbian, Macedonian).
|
||||||
|
* CCSID / CPGID: 1025, GCSGID: 1150.
|
||||||
|
*/
|
||||||
|
public class Cp1025 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "1025";
|
||||||
|
public static final String DESCRIPTION = "Cyrillic Multilingual EBCDIC";
|
||||||
|
public static final int CPGID = 1025;
|
||||||
|
public static final int GCSGID = 1150;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = {
|
||||||
|
// 00-0F
|
||||||
|
0x0000, 0x0001, 0x0002, 0x0003, 0x009C, 0x0009, 0x0086, 0x007F,
|
||||||
|
0x0097, 0x008D, 0x008E, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
|
||||||
|
// 10-1F
|
||||||
|
0x0010, 0x0011, 0x0012, 0x0013, 0x009D, 0x0085, 0x0008, 0x0087,
|
||||||
|
0x0018, 0x0019, 0x0092, 0x008F, 0x001C, 0x001D, 0x001E, 0x001F,
|
||||||
|
// 20-2F
|
||||||
|
0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x000A, 0x0017, 0x001B,
|
||||||
|
0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x0005, 0x0006, 0x0007,
|
||||||
|
// 30-3F
|
||||||
|
0x0090, 0x0091, 0x0016, 0x0093, 0x0094, 0x0095, 0x0096, 0x0004,
|
||||||
|
0x0098, 0x0099, 0x009A, 0x009B, 0x0014, 0x0015, 0x009E, 0x001A,
|
||||||
|
// 40-4F
|
||||||
|
0x0020, 0x044F, 0x044E, 0x044D, 0x044C, 0x044B, 0x044A, 0x0449,
|
||||||
|
0x0448, 0x0447, 0x005B, 0x002E, 0x003C, 0x0028, 0x002B, 0x0021,
|
||||||
|
// 50-5F
|
||||||
|
0x0026, 0x0446, 0x0445, 0x0444, 0x0443, 0x0442, 0x0441, 0x0440,
|
||||||
|
0x043F, 0x043E, 0x005D, 0x0024, 0x002A, 0x0029, 0x003B, 0x005E,
|
||||||
|
// 60-6F
|
||||||
|
0x002D, 0x002F, 0x043D, 0x043C, 0x043B, 0x043A, 0x0439, 0x0438,
|
||||||
|
0x0437, 0x0436, 0x00A6, 0x002C, 0x0025, 0x005F, 0x003E, 0x003F,
|
||||||
|
// 70-7F
|
||||||
|
0x0435, 0x0434, 0x0433, 0x0432, 0x0431, 0x0430, 0x0451, 0x0453,
|
||||||
|
0x0452, 0x0060, 0x003A, 0x0023, 0x0040, 0x0027, 0x003D, 0x0022,
|
||||||
|
// 80-8F
|
||||||
|
0x0454, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
|
||||||
|
0x0068, 0x0069, 0x0455, 0x0456, 0x0457, 0x0458, 0x0459, 0x045A,
|
||||||
|
// 90-9F
|
||||||
|
0x045B, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070,
|
||||||
|
0x0071, 0x0072, 0x045C, 0x045E, 0x045F, 0x0401, 0x0402, 0x0403,
|
||||||
|
// A0-AF
|
||||||
|
0x0404, 0x007E, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078,
|
||||||
|
0x0079, 0x007A, 0x0405, 0x0406, 0x0407, 0x0408, 0x0409, 0x040A,
|
||||||
|
// B0-BF
|
||||||
|
0x040B, 0x00A3, 0x040C, 0x00B7, 0x00A9, 0x040E, 0x040F, 0x0410,
|
||||||
|
0x0411, 0x0412, 0x00AC, 0x007C, 0x0413, 0x0414, 0x0415, 0x0416,
|
||||||
|
// C0-CF
|
||||||
|
0x007B, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
|
||||||
|
0x0048, 0x0049, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C,
|
||||||
|
// D0-DF
|
||||||
|
0x007D, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050,
|
||||||
|
0x0051, 0x0052, 0x041D, 0x041E, 0x041F, 0x0420, 0x0421, 0x0422,
|
||||||
|
// E0-EF
|
||||||
|
0x005C, 0x00F7, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058,
|
||||||
|
0x0059, 0x005A, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428,
|
||||||
|
// F0-FF
|
||||||
|
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
|
||||||
|
0x0038, 0x0039, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E,
|
||||||
|
};
|
||||||
|
|
||||||
|
public Cp1025() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "1025", MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 1026 (Turkey - Turkish Latin-5 EBCDIC).
|
||||||
|
* CCSID / CPGID: 1026, GCSGID: 1152.
|
||||||
|
*/
|
||||||
|
public class Cp1026 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "1026";
|
||||||
|
public static final String DESCRIPTION = "Turkey - Turkish Latin-5 EBCDIC";
|
||||||
|
public static final int CPGID = 1026;
|
||||||
|
public static final int GCSGID = 1152;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = {
|
||||||
|
// 00-0F
|
||||||
|
0x0000, 0x0001, 0x0002, 0x0003, 0x009C, 0x0009, 0x0086, 0x007F,
|
||||||
|
0x0097, 0x008D, 0x008E, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
|
||||||
|
// 10-1F
|
||||||
|
0x0010, 0x0011, 0x0012, 0x0013, 0x009D, 0x0085, 0x0008, 0x0087,
|
||||||
|
0x0018, 0x0019, 0x0092, 0x008F, 0x001C, 0x001D, 0x001E, 0x001F,
|
||||||
|
// 20-2F
|
||||||
|
0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x000A, 0x0017, 0x001B,
|
||||||
|
0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x0005, 0x0006, 0x0007,
|
||||||
|
// 30-3F
|
||||||
|
0x0090, 0x0091, 0x0016, 0x0093, 0x0094, 0x0095, 0x0096, 0x0004,
|
||||||
|
0x0098, 0x0099, 0x009A, 0x009B, 0x0014, 0x0015, 0x009E, 0x001A,
|
||||||
|
// 40-4F
|
||||||
|
0x0020, 0x00A0, 0x00E2, 0x00E4, 0x00E0, 0x00E1, 0x00E3, 0x00E5,
|
||||||
|
0x00E7, 0x00F1, 0x005B, 0x002E, 0x003C, 0x0028, 0x002B, 0x0021,
|
||||||
|
// 50-5F
|
||||||
|
0x0026, 0x00E9, 0x00EA, 0x00EB, 0x00E8, 0x00ED, 0x00EE, 0x00EF,
|
||||||
|
0x00EC, 0x00DF, 0x005D, 0x0024, 0x002A, 0x0029, 0x003B, 0x005E,
|
||||||
|
// 60-6F
|
||||||
|
0x002D, 0x002F, 0x00C2, 0x00C4, 0x00C0, 0x00C1, 0x00C3, 0x00C5,
|
||||||
|
0x00C7, 0x00D1, 0x015E, 0x002C, 0x0025, 0x005F, 0x003E, 0x003F,
|
||||||
|
// 70-7F
|
||||||
|
0x00F8, 0x00C9, 0x00CA, 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF,
|
||||||
|
0x00CC, 0x0060, 0x003A, 0x00D6, 0x00C7, 0x00DC, 0x003D, 0x0022,
|
||||||
|
// 80-8F
|
||||||
|
0x00D8, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
|
||||||
|
0x0068, 0x0069, 0x00AB, 0x00BB, 0x00F0, 0x00FD, 0x00FE, 0x00B1,
|
||||||
|
// 90-9F
|
||||||
|
0x00B0, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070,
|
||||||
|
0x0071, 0x0072, 0x00AA, 0x00BA, 0x00E6, 0x00B8, 0x00C6, 0x00A4,
|
||||||
|
// A0-AF
|
||||||
|
0x00B5, 0x007E, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078,
|
||||||
|
0x0079, 0x007A, 0x00A1, 0x00BF, 0x00D0, 0x00DD, 0x00DE, 0x00AE,
|
||||||
|
// B0-BF
|
||||||
|
0x0130, 0x00A3, 0x00A5, 0x00B7, 0x00A9, 0x00A7, 0x00B6, 0x00BC,
|
||||||
|
0x00BD, 0x00BE, 0x00AC, 0x007C, 0x00AF, 0x00A8, 0x00B4, 0x00D7,
|
||||||
|
// C0-CF
|
||||||
|
0x00F6, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
|
||||||
|
0x0048, 0x0049, 0x011E, 0x00F4, 0x0131, 0x00F2, 0x00F3, 0x00F5,
|
||||||
|
// D0-DF
|
||||||
|
0x00E7, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050,
|
||||||
|
0x0051, 0x0052, 0x011F, 0x00FB, 0x015F, 0x00F9, 0x00FA, 0x00FF,
|
||||||
|
// E0-EF
|
||||||
|
0x00FC, 0x00F7, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058,
|
||||||
|
0x0059, 0x005A, 0x00B2, 0x00D4, 0x00A6, 0x00D2, 0x00D3, 0x00D5,
|
||||||
|
// F0-FF
|
||||||
|
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
|
||||||
|
0x0038, 0x0039, 0x00B3, 0x00DB, 0x00DC, 0x00D9, 0x00DA, 0x009F,
|
||||||
|
};
|
||||||
|
|
||||||
|
public Cp1026() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "1026", MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 1047 (IBM Open Systems / z/OS Unix System Services Latin-1).
|
||||||
|
* CCSID / CPGID: 1047, GCSGID: 103.
|
||||||
|
*/
|
||||||
|
public class Cp1047 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "1047";
|
||||||
|
public static final String DESCRIPTION = "IBM Open Systems / z/OS Unix Latin-1";
|
||||||
|
public static final int CPGID = 1047;
|
||||||
|
public static final int GCSGID = 103;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = {
|
||||||
|
// 00-0F
|
||||||
|
0x0000, 0x0001, 0x0002, 0x0003, 0x009C, 0x0009, 0x0086, 0x007F,
|
||||||
|
0x0097, 0x008D, 0x008E, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
|
||||||
|
// 10-1F
|
||||||
|
0x0010, 0x0011, 0x0012, 0x0013, 0x009D, 0x000A, 0x0008, 0x0087,
|
||||||
|
0x0018, 0x0019, 0x0092, 0x008F, 0x001C, 0x001D, 0x001E, 0x001F,
|
||||||
|
// 20-2F
|
||||||
|
0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x000A, 0x0017, 0x001B,
|
||||||
|
0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x0005, 0x0006, 0x0007,
|
||||||
|
// 30-3F
|
||||||
|
0x0090, 0x0091, 0x0016, 0x0093, 0x0094, 0x0095, 0x0096, 0x0004,
|
||||||
|
0x0098, 0x0099, 0x009A, 0x009B, 0x0014, 0x0015, 0x009E, 0x001A,
|
||||||
|
// 40-4F
|
||||||
|
0x0020, 0x00A0, 0x00E2, 0x00E4, 0x00E0, 0x00E1, 0x00E3, 0x00E5,
|
||||||
|
0x00E7, 0x00F1, 0x00A2, 0x002E, 0x003C, 0x0028, 0x002B, 0x007C,
|
||||||
|
// 50-5F
|
||||||
|
0x0026, 0x00E9, 0x00EA, 0x00EB, 0x00E8, 0x00ED, 0x00EE, 0x00EF,
|
||||||
|
0x00EC, 0x00DF, 0x0021, 0x0024, 0x002A, 0x0029, 0x003B, 0x005E,
|
||||||
|
// 60-6F
|
||||||
|
0x002D, 0x002F, 0x00C2, 0x00C4, 0x00C0, 0x00C1, 0x00C3, 0x00C5,
|
||||||
|
0x00C7, 0x00D1, 0x00A6, 0x002C, 0x0025, 0x005F, 0x003E, 0x003F,
|
||||||
|
// 70-7F
|
||||||
|
0x00F8, 0x00C9, 0x00CA, 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF,
|
||||||
|
0x00CC, 0x0060, 0x003A, 0x0023, 0x0040, 0x0027, 0x003D, 0x0022,
|
||||||
|
// 80-8F
|
||||||
|
0x00D8, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
|
||||||
|
0x0068, 0x0069, 0x00AB, 0x00BB, 0x00F0, 0x00FD, 0x00FE, 0x00B1,
|
||||||
|
// 90-9F
|
||||||
|
0x00B0, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070,
|
||||||
|
0x0071, 0x0072, 0x00AA, 0x00BA, 0x00E6, 0x00B8, 0x00C6, 0x00A4,
|
||||||
|
// A0-AF
|
||||||
|
0x00B5, 0x007E, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078,
|
||||||
|
0x0079, 0x007A, 0x00A1, 0x00BF, 0x00D0, 0x005B, 0x00DE, 0x00AE,
|
||||||
|
// B0-BF
|
||||||
|
0x00AC, 0x00A3, 0x00A5, 0x00B7, 0x00A9, 0x00A7, 0x00B6, 0x00BC,
|
||||||
|
0x00BD, 0x00BE, 0x00DD, 0x005D, 0x00AF, 0x00A8, 0x00B4, 0x00D7,
|
||||||
|
// C0-CF
|
||||||
|
0x007B, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
|
||||||
|
0x0048, 0x0049, 0x00AD, 0x00F4, 0x00F6, 0x00F2, 0x00F3, 0x00F5,
|
||||||
|
// D0-DF
|
||||||
|
0x007D, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050,
|
||||||
|
0x0051, 0x0052, 0x00B9, 0x00FB, 0x00FC, 0x00F9, 0x00FA, 0x00FF,
|
||||||
|
// E0-EF
|
||||||
|
0x005C, 0x00F7, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058,
|
||||||
|
0x0059, 0x005A, 0x00B2, 0x00D4, 0x00D6, 0x00D2, 0x00D3, 0x00D5,
|
||||||
|
// F0-FF
|
||||||
|
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
|
||||||
|
0x0038, 0x0039, 0x00B3, 0x00DB, 0x00DC, 0x00D9, 0x00DA, 0x009F,
|
||||||
|
};
|
||||||
|
|
||||||
|
public Cp1047() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "1047", MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 1123 (Cyrillic Ukraine EBCDIC).
|
||||||
|
* CCSID / CPGID: 1123, GCSGID: 1399.
|
||||||
|
*/
|
||||||
|
public class Cp1123 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "1123";
|
||||||
|
public static final String DESCRIPTION = "Cyrillic Ukraine EBCDIC";
|
||||||
|
public static final int CPGID = 1123;
|
||||||
|
public static final int GCSGID = 1399;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = {
|
||||||
|
// 00-0F
|
||||||
|
0x0000, 0x0001, 0x0002, 0x0003, 0x009C, 0x0009, 0x0086, 0x007F,
|
||||||
|
0x0097, 0x008D, 0x008E, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
|
||||||
|
// 10-1F
|
||||||
|
0x0010, 0x0011, 0x0012, 0x0013, 0x009D, 0x0085, 0x0008, 0x0087,
|
||||||
|
0x0018, 0x0019, 0x0092, 0x008F, 0x001C, 0x001D, 0x001E, 0x001F,
|
||||||
|
// 20-2F
|
||||||
|
0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x000A, 0x0017, 0x001B,
|
||||||
|
0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x0005, 0x0006, 0x0007,
|
||||||
|
// 30-3F
|
||||||
|
0x0090, 0x0091, 0x0016, 0x0093, 0x0094, 0x0095, 0x0096, 0x0004,
|
||||||
|
0x0098, 0x0099, 0x009A, 0x009B, 0x0014, 0x0015, 0x009E, 0x001A,
|
||||||
|
// 40-4F
|
||||||
|
0x0020, 0x044F, 0x044E, 0x044D, 0x044C, 0x044B, 0x044A, 0x0449,
|
||||||
|
0x0448, 0x0447, 0x005B, 0x002E, 0x003C, 0x0028, 0x002B, 0x0021,
|
||||||
|
// 50-5F
|
||||||
|
0x0026, 0x0446, 0x0445, 0x0444, 0x0443, 0x0442, 0x0441, 0x0440,
|
||||||
|
0x043F, 0x043E, 0x005D, 0x0024, 0x002A, 0x0029, 0x003B, 0x005E,
|
||||||
|
// 60-6F
|
||||||
|
0x002D, 0x002F, 0x043D, 0x043C, 0x043B, 0x043A, 0x0439, 0x0438,
|
||||||
|
0x0437, 0x0436, 0x00A6, 0x002C, 0x0025, 0x005F, 0x003E, 0x003F,
|
||||||
|
// 70-7F
|
||||||
|
0x0435, 0x0434, 0x0433, 0x0432, 0x0431, 0x0430, 0x0451, 0x0491,
|
||||||
|
0x0454, 0x0060, 0x003A, 0x0023, 0x0040, 0x0027, 0x003D, 0x0022,
|
||||||
|
// 80-8F
|
||||||
|
0x0456, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
|
||||||
|
0x0068, 0x0069, 0x0457, 0x00BB, 0x00F0, 0x00FD, 0x00FE, 0x00B1,
|
||||||
|
// 90-9F
|
||||||
|
0x00B0, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070,
|
||||||
|
0x0071, 0x0072, 0x00AA, 0x00BA, 0x00E6, 0x00B8, 0x00C6, 0x00A4,
|
||||||
|
// A0-AF
|
||||||
|
0x00B5, 0x007E, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078,
|
||||||
|
0x0079, 0x007A, 0x00A1, 0x00BF, 0x00D0, 0x00DD, 0x00DE, 0x00AE,
|
||||||
|
// B0-BF
|
||||||
|
0x00A2, 0x00A3, 0x00A5, 0x00B7, 0x00A9, 0x00A7, 0x00B6, 0x00BC,
|
||||||
|
0x00BD, 0x00BE, 0x00AC, 0x007C, 0x00AF, 0x00A8, 0x00B4, 0x00D7,
|
||||||
|
// C0-CF
|
||||||
|
0x007B, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
|
||||||
|
0x0048, 0x0049, 0x0417, 0x0418, 0x0419, 0x041A, 0x041B, 0x041C,
|
||||||
|
// D0-DF
|
||||||
|
0x007D, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050,
|
||||||
|
0x0051, 0x0052, 0x041D, 0x041E, 0x041F, 0x0420, 0x0421, 0x0422,
|
||||||
|
// E0-EF
|
||||||
|
0x005C, 0x00F7, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058,
|
||||||
|
0x0059, 0x005A, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, 0x0428,
|
||||||
|
// F0-FF
|
||||||
|
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
|
||||||
|
0x0038, 0x0039, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E,
|
||||||
|
};
|
||||||
|
|
||||||
|
public Cp1123() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "1123", MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 1154 (Cyrillic Multilingual with Euro \u20AC).
|
||||||
|
* CCSID / CPGID: 1154, GCSGID: 1305.
|
||||||
|
*/
|
||||||
|
public class Cp1154 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "1154";
|
||||||
|
public static final String DESCRIPTION = "Cyrillic Multilingual EBCDIC (with Euro \u20AC)";
|
||||||
|
public static final int CPGID = 1154;
|
||||||
|
public static final int GCSGID = 1305;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = new int[256];
|
||||||
|
|
||||||
|
static {
|
||||||
|
System.arraycopy(Cp1025.MAPPING, 0, MAPPING, 0, 256);
|
||||||
|
MAPPING[0x9F] = 0x20AC; // Euro character \u20AC
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cp1154() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "1154", MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 1155 (Turkey - Turkish Latin-5 with Euro \u20AC).
|
||||||
|
* CCSID / CPGID: 1155, GCSGID: 1306.
|
||||||
|
*/
|
||||||
|
public class Cp1155 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "1155";
|
||||||
|
public static final String DESCRIPTION = "Turkey - Turkish Latin-5 (with Euro \u20AC)";
|
||||||
|
public static final int CPGID = 1155;
|
||||||
|
public static final int GCSGID = 1306;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = new int[256];
|
||||||
|
|
||||||
|
static {
|
||||||
|
System.arraycopy(Cp1026.MAPPING, 0, MAPPING, 0, 256);
|
||||||
|
MAPPING[0x9F] = 0x20AC; // Euro character \u20AC
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cp1155() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "1155", MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 1160 (Thai EBCDIC with Euro \u20AC).
|
||||||
|
* CCSID / CPGID: 1160, GCSGID: 1176.
|
||||||
|
*/
|
||||||
|
public class Cp1160 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "1160";
|
||||||
|
public static final String DESCRIPTION = "Thai EBCDIC (with Euro \u20AC)";
|
||||||
|
public static final int CPGID = 1160;
|
||||||
|
public static final int GCSGID = 1176;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = new int[256];
|
||||||
|
|
||||||
|
static {
|
||||||
|
System.arraycopy(Cp838.MAPPING, 0, MAPPING, 0, 256);
|
||||||
|
MAPPING[0x9F] = 0x20AC; // Euro character \u20AC
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cp1160() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "1160", MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 1371 (Traditional Chinese Extended Mixed DBCS).
|
||||||
|
* CCSID / CPGID: 1371, GCSGID: 1174.
|
||||||
|
*/
|
||||||
|
public class Cp1371 extends AbstractDBCSCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "1371";
|
||||||
|
public static final String DESCRIPTION = "Traditional Chinese Extended Mixed DBCS";
|
||||||
|
public static final int CPGID = 1371;
|
||||||
|
public static final int GCSGID = 1174;
|
||||||
|
|
||||||
|
public Cp1371() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, Cp037.MAPPING, "x-IBM1371");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 1388 (Simplified Chinese Extended Mixed DBCS).
|
||||||
|
* CCSID / CPGID: 1388, GCSGID: 1175.
|
||||||
|
*/
|
||||||
|
public class Cp1388 extends AbstractDBCSCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "1388";
|
||||||
|
public static final String DESCRIPTION = "Simplified Chinese Extended Mixed DBCS";
|
||||||
|
public static final int CPGID = 1388;
|
||||||
|
public static final int GCSGID = 1175;
|
||||||
|
|
||||||
|
public Cp1388() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, Cp037.MAPPING, "x-IBM1388");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 273 (Germany / Austria).
|
||||||
|
* CCSID / CPGID: 273, GCSGID: 697.
|
||||||
|
*/
|
||||||
|
public class Cp273 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "273";
|
||||||
|
public static final String DESCRIPTION = "Germany / Austria - EBCDIC";
|
||||||
|
public static final int CPGID = 273;
|
||||||
|
public static final int GCSGID = 697;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = {
|
||||||
|
// 00-0F
|
||||||
|
0x0000, 0x0001, 0x0002, 0x0003, 0x009C, 0x0009, 0x0086, 0x007F,
|
||||||
|
0x0097, 0x008D, 0x008E, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
|
||||||
|
// 10-1F
|
||||||
|
0x0010, 0x0011, 0x0012, 0x0013, 0x009D, 0x0085, 0x0008, 0x0087,
|
||||||
|
0x0018, 0x0019, 0x0092, 0x008F, 0x001C, 0x001D, 0x001E, 0x001F,
|
||||||
|
// 20-2F
|
||||||
|
0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x000A, 0x0017, 0x001B,
|
||||||
|
0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x0005, 0x0006, 0x0007,
|
||||||
|
// 30-3F
|
||||||
|
0x0090, 0x0091, 0x0016, 0x0093, 0x0094, 0x0095, 0x0096, 0x0004,
|
||||||
|
0x0098, 0x0099, 0x009A, 0x009B, 0x0014, 0x0015, 0x009E, 0x001A,
|
||||||
|
// 40-4F
|
||||||
|
0x0020, 0x00A0, 0x00E2, 0x007B, 0x00E0, 0x00E1, 0x00E3, 0x00E5,
|
||||||
|
0x00E7, 0x00F1, 0x00A2, 0x002E, 0x003C, 0x0028, 0x002B, 0x0021,
|
||||||
|
// 50-5F
|
||||||
|
0x0026, 0x00E9, 0x00EA, 0x00EB, 0x00E8, 0x00ED, 0x00EE, 0x00EF,
|
||||||
|
0x00EC, 0x00DF, 0x005D, 0x0024, 0x002A, 0x0029, 0x003B, 0x005E,
|
||||||
|
// 60-6F
|
||||||
|
0x002D, 0x002F, 0x00C2, 0x005B, 0x00C0, 0x00C1, 0x00C3, 0x00C5,
|
||||||
|
0x00C7, 0x00D1, 0x00A6, 0x002C, 0x0025, 0x005F, 0x003E, 0x003F,
|
||||||
|
// 70-7F
|
||||||
|
0x00F8, 0x00C9, 0x00CA, 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF,
|
||||||
|
0x00CC, 0x0060, 0x003A, 0x00E4, 0x00F6, 0x00FC, 0x003D, 0x0022,
|
||||||
|
// 80-8F
|
||||||
|
0x00D8, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
|
||||||
|
0x0068, 0x0069, 0x00AB, 0x00BB, 0x00F0, 0x00FD, 0x00FE, 0x00B1,
|
||||||
|
// 90-9F
|
||||||
|
0x00B0, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070,
|
||||||
|
0x0071, 0x0072, 0x00AA, 0x00BA, 0x00E6, 0x00B8, 0x00C6, 0x00A4,
|
||||||
|
// A0-AF
|
||||||
|
0x00B5, 0x007E, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078,
|
||||||
|
0x0079, 0x007A, 0x00A1, 0x00BF, 0x00D0, 0x00DD, 0x00DE, 0x00AE,
|
||||||
|
// B0-BF
|
||||||
|
0x00A2, 0x00A3, 0x00A5, 0x00B7, 0x00A9, 0x00A7, 0x00B6, 0x00BC,
|
||||||
|
0x00BD, 0x00BE, 0x00AC, 0x007C, 0x00AF, 0x00A8, 0x00B4, 0x00D7,
|
||||||
|
// C0-CF
|
||||||
|
0x00E4, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
|
||||||
|
0x0048, 0x0049, 0x00AD, 0x00F4, 0x00F6, 0x00F2, 0x00F3, 0x00F5,
|
||||||
|
// D0-DF
|
||||||
|
0x00FC, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050,
|
||||||
|
0x0051, 0x0052, 0x00B9, 0x00FB, 0x00DC, 0x00F9, 0x00FA, 0x00FF,
|
||||||
|
// E0-EF
|
||||||
|
0x005C, 0x00F7, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058,
|
||||||
|
0x0059, 0x005A, 0x00B2, 0x00D4, 0x00D6, 0x00D2, 0x00D3, 0x00D5,
|
||||||
|
// F0-FF
|
||||||
|
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
|
||||||
|
0x0038, 0x0039, 0x00B3, 0x00DB, 0x00DC, 0x00D9, 0x00DA, 0x009F,
|
||||||
|
};
|
||||||
|
|
||||||
|
public Cp273() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "273", MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 277 (Denmark / Norway).
|
||||||
|
* CCSID / CPGID: 277, GCSGID: 697.
|
||||||
|
*/
|
||||||
|
public class Cp277 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "277";
|
||||||
|
public static final String DESCRIPTION = "Denmark / Norway - EBCDIC";
|
||||||
|
public static final int CPGID = 277;
|
||||||
|
public static final int GCSGID = 697;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = {
|
||||||
|
// 00-0F
|
||||||
|
0x0000, 0x0001, 0x0002, 0x0003, 0x009C, 0x0009, 0x0086, 0x007F,
|
||||||
|
0x0097, 0x008D, 0x008E, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
|
||||||
|
// 10-1F
|
||||||
|
0x0010, 0x0011, 0x0012, 0x0013, 0x009D, 0x0085, 0x0008, 0x0087,
|
||||||
|
0x0018, 0x0019, 0x0092, 0x008F, 0x001C, 0x001D, 0x001E, 0x001F,
|
||||||
|
// 20-2F
|
||||||
|
0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x000A, 0x0017, 0x001B,
|
||||||
|
0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x0005, 0x0006, 0x0007,
|
||||||
|
// 30-3F
|
||||||
|
0x0090, 0x0091, 0x0016, 0x0093, 0x0094, 0x0095, 0x0096, 0x0004,
|
||||||
|
0x0098, 0x0099, 0x009A, 0x009B, 0x0014, 0x0015, 0x009E, 0x001A,
|
||||||
|
// 40-4F
|
||||||
|
0x0020, 0x00A0, 0x00E2, 0x00E4, 0x00E0, 0x00E1, 0x00E3, 0x00E5,
|
||||||
|
0x00E7, 0x00F1, 0x00A2, 0x002E, 0x003C, 0x0028, 0x002B, 0x0021,
|
||||||
|
// 50-5F
|
||||||
|
0x0026, 0x00E9, 0x00EA, 0x00EB, 0x00E8, 0x00ED, 0x00EE, 0x00EF,
|
||||||
|
0x00EC, 0x00DF, 0x00A4, 0x0024, 0x002A, 0x0029, 0x003B, 0x005E,
|
||||||
|
// 60-6F
|
||||||
|
0x002D, 0x002F, 0x00C2, 0x00C4, 0x00C0, 0x00C1, 0x00C3, 0x00C5,
|
||||||
|
0x00C7, 0x00D1, 0x00A6, 0x002C, 0x0025, 0x005F, 0x003E, 0x003F,
|
||||||
|
// 70-7F
|
||||||
|
0x00F8, 0x00C9, 0x00CA, 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF,
|
||||||
|
0x00CC, 0x0060, 0x003A, 0x00E6, 0x00F8, 0x00E5, 0x003D, 0x0022,
|
||||||
|
// 80-8F
|
||||||
|
0x00D8, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
|
||||||
|
0x0068, 0x0069, 0x00AB, 0x00BB, 0x00F0, 0x00FD, 0x00FE, 0x00B1,
|
||||||
|
// 90-9F
|
||||||
|
0x00B0, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070,
|
||||||
|
0x0071, 0x0072, 0x00AA, 0x00BA, 0x00E6, 0x00B8, 0x00C6, 0x00A4,
|
||||||
|
// A0-AF
|
||||||
|
0x00B5, 0x007E, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078,
|
||||||
|
0x0079, 0x007A, 0x00A1, 0x00BF, 0x00D0, 0x00DD, 0x00DE, 0x00AE,
|
||||||
|
// B0-BF
|
||||||
|
0x00A2, 0x00A3, 0x00A5, 0x00B7, 0x00A9, 0x00A7, 0x00B6, 0x00BC,
|
||||||
|
0x00BD, 0x00BE, 0x00AC, 0x007C, 0x00AF, 0x00A8, 0x00B4, 0x00D7,
|
||||||
|
// C0-CF
|
||||||
|
0x00C6, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
|
||||||
|
0x0048, 0x0049, 0x00AD, 0x00F4, 0x00F6, 0x00F2, 0x00F3, 0x00F5,
|
||||||
|
// D0-DF
|
||||||
|
0x00D8, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050,
|
||||||
|
0x0051, 0x0052, 0x00B9, 0x00FB, 0x00FC, 0x00F9, 0x00FA, 0x00FF,
|
||||||
|
// E0-EF
|
||||||
|
0x00C5, 0x00F7, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058,
|
||||||
|
0x0059, 0x005A, 0x00B2, 0x00D4, 0x00D6, 0x00D2, 0x00D3, 0x00D5,
|
||||||
|
// F0-FF
|
||||||
|
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
|
||||||
|
0x0038, 0x0039, 0x00B3, 0x00DB, 0x00DC, 0x00D9, 0x00DA, 0x009F,
|
||||||
|
};
|
||||||
|
|
||||||
|
public Cp277() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "277", MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 278 (Sweden / Finland).
|
||||||
|
* CCSID / CPGID: 278, GCSGID: 697.
|
||||||
|
*/
|
||||||
|
public class Cp278 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "278";
|
||||||
|
public static final String DESCRIPTION = "Sweden / Finland - EBCDIC";
|
||||||
|
public static final int CPGID = 278;
|
||||||
|
public static final int GCSGID = 697;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = {
|
||||||
|
// 00-0F
|
||||||
|
0x0000, 0x0001, 0x0002, 0x0003, 0x009C, 0x0009, 0x0086, 0x007F,
|
||||||
|
0x0097, 0x008D, 0x008E, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
|
||||||
|
// 10-1F
|
||||||
|
0x0010, 0x0011, 0x0012, 0x0013, 0x009D, 0x0085, 0x0008, 0x0087,
|
||||||
|
0x0018, 0x0019, 0x0092, 0x008F, 0x001C, 0x001D, 0x001E, 0x001F,
|
||||||
|
// 20-2F
|
||||||
|
0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x000A, 0x0017, 0x001B,
|
||||||
|
0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x0005, 0x0006, 0x0007,
|
||||||
|
// 30-3F
|
||||||
|
0x0090, 0x0091, 0x0016, 0x0093, 0x0094, 0x0095, 0x0096, 0x0004,
|
||||||
|
0x0098, 0x0099, 0x009A, 0x009B, 0x0014, 0x0015, 0x009E, 0x001A,
|
||||||
|
// 40-4F
|
||||||
|
0x0020, 0x00A0, 0x00E2, 0x007B, 0x00E0, 0x00E1, 0x00E3, 0x00E5,
|
||||||
|
0x00E7, 0x00F1, 0x00A2, 0x002E, 0x003C, 0x0028, 0x002B, 0x0021,
|
||||||
|
// 50-5F
|
||||||
|
0x0026, 0x00E9, 0x00EA, 0x00EB, 0x00E8, 0x00ED, 0x00EE, 0x00EF,
|
||||||
|
0x00EC, 0x00DF, 0x00A4, 0x0024, 0x002A, 0x0029, 0x003B, 0x005E,
|
||||||
|
// 60-6F
|
||||||
|
0x002D, 0x002F, 0x00C2, 0x005B, 0x00C0, 0x00C1, 0x00C3, 0x00C5,
|
||||||
|
0x00C7, 0x00D1, 0x00A6, 0x002C, 0x0025, 0x005F, 0x003E, 0x003F,
|
||||||
|
// 70-7F
|
||||||
|
0x00F8, 0x00C9, 0x00CA, 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF,
|
||||||
|
0x00CC, 0x0060, 0x003A, 0x00E4, 0x00F6, 0x00E5, 0x003D, 0x0022,
|
||||||
|
// 80-8F
|
||||||
|
0x00D8, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
|
||||||
|
0x0068, 0x0069, 0x00AB, 0x00BB, 0x00F0, 0x00FD, 0x00FE, 0x00B1,
|
||||||
|
// 90-9F
|
||||||
|
0x00B0, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070,
|
||||||
|
0x0071, 0x0072, 0x00AA, 0x00BA, 0x00E6, 0x00B8, 0x00C6, 0x00A4,
|
||||||
|
// A0-AF
|
||||||
|
0x00B5, 0x007E, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078,
|
||||||
|
0x0079, 0x007A, 0x00A1, 0x00BF, 0x00D0, 0x00DD, 0x00DE, 0x00AE,
|
||||||
|
// B0-BF
|
||||||
|
0x00A2, 0x00A3, 0x00A5, 0x00B7, 0x00A9, 0x00A7, 0x00B6, 0x00BC,
|
||||||
|
0x00BD, 0x00BE, 0x00AC, 0x007C, 0x00AF, 0x00A8, 0x00B4, 0x00D7,
|
||||||
|
// C0-CF
|
||||||
|
0x00C4, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
|
||||||
|
0x0048, 0x0049, 0x00AD, 0x00F4, 0x00F6, 0x00F2, 0x00F3, 0x00F5,
|
||||||
|
// D0-DF
|
||||||
|
0x00D6, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050,
|
||||||
|
0x0051, 0x0052, 0x00B9, 0x00FB, 0x00FC, 0x00F9, 0x00FA, 0x00FF,
|
||||||
|
// E0-EF
|
||||||
|
0x00C5, 0x00F7, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058,
|
||||||
|
0x0059, 0x005A, 0x00B2, 0x00D4, 0x00D6, 0x00D2, 0x00D3, 0x00D5,
|
||||||
|
// F0-FF
|
||||||
|
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
|
||||||
|
0x0038, 0x0039, 0x00B3, 0x00DB, 0x00DC, 0x00D9, 0x00DA, 0x009F,
|
||||||
|
};
|
||||||
|
|
||||||
|
public Cp278() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "278", MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 280 (Italy).
|
||||||
|
* CCSID / CPGID: 280, GCSGID: 697.
|
||||||
|
*/
|
||||||
|
public class Cp280 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "280";
|
||||||
|
public static final String DESCRIPTION = "Italy - EBCDIC";
|
||||||
|
public static final int CPGID = 280;
|
||||||
|
public static final int GCSGID = 697;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = {
|
||||||
|
// 00-0F
|
||||||
|
0x0000, 0x0001, 0x0002, 0x0003, 0x009C, 0x0009, 0x0086, 0x007F,
|
||||||
|
0x0097, 0x008D, 0x008E, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
|
||||||
|
// 10-1F
|
||||||
|
0x0010, 0x0011, 0x0012, 0x0013, 0x009D, 0x0085, 0x0008, 0x0087,
|
||||||
|
0x0018, 0x0019, 0x0092, 0x008F, 0x001C, 0x001D, 0x001E, 0x001F,
|
||||||
|
// 20-2F
|
||||||
|
0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x000A, 0x0017, 0x001B,
|
||||||
|
0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x0005, 0x0006, 0x0007,
|
||||||
|
// 30-3F
|
||||||
|
0x0090, 0x0091, 0x0016, 0x0093, 0x0094, 0x0095, 0x0096, 0x0004,
|
||||||
|
0x0098, 0x0099, 0x009A, 0x009B, 0x0014, 0x0015, 0x009E, 0x001A,
|
||||||
|
// 40-4F
|
||||||
|
0x0020, 0x00A0, 0x00E2, 0x007B, 0x00E0, 0x00E1, 0x00E3, 0x00E5,
|
||||||
|
0x00E7, 0x00F1, 0x00B0, 0x002E, 0x003C, 0x0028, 0x002B, 0x0021,
|
||||||
|
// 50-5F
|
||||||
|
0x0026, 0x00E9, 0x00EA, 0x00EB, 0x00E8, 0x00ED, 0x00EE, 0x00EF,
|
||||||
|
0x00EC, 0x00DF, 0x005D, 0x0024, 0x002A, 0x0029, 0x003B, 0x005E,
|
||||||
|
// 60-6F
|
||||||
|
0x002D, 0x002F, 0x00C2, 0x005B, 0x00C0, 0x00C1, 0x00C3, 0x00C5,
|
||||||
|
0x00C7, 0x00D1, 0x00A6, 0x002C, 0x0025, 0x005F, 0x003E, 0x003F,
|
||||||
|
// 70-7F
|
||||||
|
0x00F8, 0x00C9, 0x00CA, 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF,
|
||||||
|
0x00CC, 0x0060, 0x003A, 0x00A3, 0x00A7, 0x00E9, 0x003D, 0x0022,
|
||||||
|
// 80-8F
|
||||||
|
0x00D8, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
|
||||||
|
0x0068, 0x0069, 0x00AB, 0x00BB, 0x00F0, 0x00FD, 0x00FE, 0x00B1,
|
||||||
|
// 90-9F
|
||||||
|
0x00B0, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070,
|
||||||
|
0x0071, 0x0072, 0x00AA, 0x00BA, 0x00E6, 0x00B8, 0x00C6, 0x00A4,
|
||||||
|
// A0-AF
|
||||||
|
0x00B5, 0x007E, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078,
|
||||||
|
0x0079, 0x007A, 0x00A1, 0x00BF, 0x00D0, 0x00DD, 0x00DE, 0x00AE,
|
||||||
|
// B0-BF
|
||||||
|
0x00A2, 0x00A3, 0x00A5, 0x00B7, 0x00A9, 0x00A7, 0x00B6, 0x00BC,
|
||||||
|
0x00BD, 0x00BE, 0x00AC, 0x007C, 0x00AF, 0x00A8, 0x00B4, 0x00D7,
|
||||||
|
// C0-CF
|
||||||
|
0x00F2, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
|
||||||
|
0x0048, 0x0049, 0x00AD, 0x00F4, 0x00F6, 0x00F2, 0x00F3, 0x00F5,
|
||||||
|
// D0-DF
|
||||||
|
0x00F9, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050,
|
||||||
|
0x0051, 0x0052, 0x00B9, 0x00FB, 0x00FC, 0x00F9, 0x00FA, 0x00FF,
|
||||||
|
// E0-EF
|
||||||
|
0x00E0, 0x00F7, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058,
|
||||||
|
0x0059, 0x005A, 0x00B2, 0x00D4, 0x00D6, 0x00D2, 0x00D3, 0x00D5,
|
||||||
|
// F0-FF
|
||||||
|
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
|
||||||
|
0x0038, 0x0039, 0x00B3, 0x00DB, 0x00DC, 0x00D9, 0x00DA, 0x009F,
|
||||||
|
};
|
||||||
|
|
||||||
|
public Cp280() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "280", MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 284 (Spain / Latin America).
|
||||||
|
* CCSID / CPGID: 284, GCSGID: 697.
|
||||||
|
*/
|
||||||
|
public class Cp284 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "284";
|
||||||
|
public static final String DESCRIPTION = "Spain / Latin America - EBCDIC";
|
||||||
|
public static final int CPGID = 284;
|
||||||
|
public static final int GCSGID = 697;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = {
|
||||||
|
// 00-0F
|
||||||
|
0x0000, 0x0001, 0x0002, 0x0003, 0x009C, 0x0009, 0x0086, 0x007F,
|
||||||
|
0x0097, 0x008D, 0x008E, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
|
||||||
|
// 10-1F
|
||||||
|
0x0010, 0x0011, 0x0012, 0x0013, 0x009D, 0x0085, 0x0008, 0x0087,
|
||||||
|
0x0018, 0x0019, 0x0092, 0x008F, 0x001C, 0x001D, 0x001E, 0x001F,
|
||||||
|
// 20-2F
|
||||||
|
0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x000A, 0x0017, 0x001B,
|
||||||
|
0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x0005, 0x0006, 0x0007,
|
||||||
|
// 30-3F
|
||||||
|
0x0090, 0x0091, 0x0016, 0x0093, 0x0094, 0x0095, 0x0096, 0x0004,
|
||||||
|
0x0098, 0x0099, 0x009A, 0x009B, 0x0014, 0x0015, 0x009E, 0x001A,
|
||||||
|
// 40-4F
|
||||||
|
0x0020, 0x00A0, 0x00E2, 0x00E4, 0x00E0, 0x00E1, 0x00E3, 0x00E5,
|
||||||
|
0x00E7, 0x00F1, 0x00A2, 0x002E, 0x003C, 0x0028, 0x002B, 0x007C,
|
||||||
|
// 50-5F
|
||||||
|
0x0026, 0x00E9, 0x00EA, 0x00EB, 0x00E8, 0x00ED, 0x00EE, 0x00EF,
|
||||||
|
0x00EC, 0x00DF, 0x0021, 0x0024, 0x002A, 0x0029, 0x003B, 0x00AC,
|
||||||
|
// 60-6F
|
||||||
|
0x002D, 0x002F, 0x00C2, 0x00C4, 0x00C0, 0x00C1, 0x00C3, 0x00C5,
|
||||||
|
0x00C7, 0x00D1, 0x00A6, 0x002C, 0x0025, 0x005F, 0x003E, 0x003F,
|
||||||
|
// 70-7F
|
||||||
|
0x00F8, 0x00C9, 0x00CA, 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF,
|
||||||
|
0x00CC, 0x0060, 0x003A, 0x00F1, 0x00D1, 0x00E7, 0x003D, 0x0022,
|
||||||
|
// 80-8F
|
||||||
|
0x00D8, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
|
||||||
|
0x0068, 0x0069, 0x00AB, 0x00BB, 0x00F0, 0x00FD, 0x00FE, 0x00B1,
|
||||||
|
// 90-9F
|
||||||
|
0x00B0, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070,
|
||||||
|
0x0071, 0x0072, 0x00AA, 0x00BA, 0x00E6, 0x00B8, 0x00C6, 0x00A4,
|
||||||
|
// A0-AF
|
||||||
|
0x00B5, 0x007E, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078,
|
||||||
|
0x0079, 0x007A, 0x00A1, 0x00BF, 0x00D0, 0x00DD, 0x00DE, 0x00AE,
|
||||||
|
// B0-BF
|
||||||
|
0x005E, 0x00A3, 0x00A5, 0x00B7, 0x00A9, 0x00A7, 0x00B6, 0x00BC,
|
||||||
|
0x00BD, 0x00BE, 0x005B, 0x005D, 0x00AF, 0x00A8, 0x00B4, 0x00D7,
|
||||||
|
// C0-CF
|
||||||
|
0x007B, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
|
||||||
|
0x0048, 0x0049, 0x00AD, 0x00F4, 0x00F6, 0x00F2, 0x00F3, 0x00F5,
|
||||||
|
// D0-DF
|
||||||
|
0x007D, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050,
|
||||||
|
0x0051, 0x0052, 0x00B9, 0x00FB, 0x00FC, 0x00F9, 0x00FA, 0x00FF,
|
||||||
|
// E0-EF
|
||||||
|
0x005C, 0x00F7, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058,
|
||||||
|
0x0059, 0x005A, 0x00B2, 0x00D4, 0x00D6, 0x00D2, 0x00D3, 0x00D5,
|
||||||
|
// F0-FF
|
||||||
|
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
|
||||||
|
0x0038, 0x0039, 0x00B3, 0x00DB, 0x00DC, 0x00D9, 0x00DA, 0x009F,
|
||||||
|
};
|
||||||
|
|
||||||
|
public Cp284() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "284", MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 285 (United Kingdom).
|
||||||
|
* CCSID / CPGID: 285, GCSGID: 697.
|
||||||
|
*/
|
||||||
|
public class Cp285 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "285";
|
||||||
|
public static final String DESCRIPTION = "United Kingdom - EBCDIC";
|
||||||
|
public static final int CPGID = 285;
|
||||||
|
public static final int GCSGID = 697;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = {
|
||||||
|
// 00-0F
|
||||||
|
0x0000, 0x0001, 0x0002, 0x0003, 0x009C, 0x0009, 0x0086, 0x007F,
|
||||||
|
0x0097, 0x008D, 0x008E, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
|
||||||
|
// 10-1F
|
||||||
|
0x0010, 0x0011, 0x0012, 0x0013, 0x009D, 0x0085, 0x0008, 0x0087,
|
||||||
|
0x0018, 0x0019, 0x0092, 0x008F, 0x001C, 0x001D, 0x001E, 0x001F,
|
||||||
|
// 20-2F
|
||||||
|
0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x000A, 0x0017, 0x001B,
|
||||||
|
0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x0005, 0x0006, 0x0007,
|
||||||
|
// 30-3F
|
||||||
|
0x0090, 0x0091, 0x0016, 0x0093, 0x0094, 0x0095, 0x0096, 0x0004,
|
||||||
|
0x0098, 0x0099, 0x009A, 0x009B, 0x0014, 0x0015, 0x009E, 0x001A,
|
||||||
|
// 40-4F
|
||||||
|
0x0020, 0x00A0, 0x00E2, 0x00E4, 0x00E0, 0x00E1, 0x00E3, 0x00E5,
|
||||||
|
0x00E7, 0x00F1, 0x00A2, 0x002E, 0x003C, 0x0028, 0x002B, 0x007C,
|
||||||
|
// 50-5F
|
||||||
|
0x0026, 0x00E9, 0x00EA, 0x00EB, 0x00E8, 0x00ED, 0x00EE, 0x00EF,
|
||||||
|
0x00EC, 0x00DF, 0x0021, 0x0024, 0x002A, 0x0029, 0x003B, 0x00AC,
|
||||||
|
// 60-6F
|
||||||
|
0x002D, 0x002F, 0x00C2, 0x00C4, 0x00C0, 0x00C1, 0x00C3, 0x00C5,
|
||||||
|
0x00C7, 0x00D1, 0x00A6, 0x002C, 0x0025, 0x005F, 0x003E, 0x003F,
|
||||||
|
// 70-7F
|
||||||
|
0x00F8, 0x00C9, 0x00CA, 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF,
|
||||||
|
0x00CC, 0x0060, 0x003A, 0x00A3, 0x0040, 0x0027, 0x003D, 0x0022,
|
||||||
|
// 80-8F
|
||||||
|
0x00D8, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
|
||||||
|
0x0068, 0x0069, 0x00AB, 0x00BB, 0x00F0, 0x00FD, 0x00FE, 0x00B1,
|
||||||
|
// 90-9F
|
||||||
|
0x00B0, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070,
|
||||||
|
0x0071, 0x0072, 0x00AA, 0x00BA, 0x00E6, 0x00B8, 0x00C6, 0x00A4,
|
||||||
|
// A0-AF
|
||||||
|
0x00B5, 0x007E, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078,
|
||||||
|
0x0079, 0x007A, 0x00A1, 0x00BF, 0x00D0, 0x00DD, 0x00DE, 0x00AE,
|
||||||
|
// B0-BF
|
||||||
|
0x005E, 0x00A3, 0x00A5, 0x00B7, 0x00A9, 0x00A7, 0x00B6, 0x00BC,
|
||||||
|
0x00BD, 0x00BE, 0x005B, 0x005D, 0x00AF, 0x00A8, 0x00B4, 0x00D7,
|
||||||
|
// C0-CF
|
||||||
|
0x007B, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
|
||||||
|
0x0048, 0x0049, 0x00AD, 0x00F4, 0x00F6, 0x00F2, 0x00F3, 0x00F5,
|
||||||
|
// D0-DF
|
||||||
|
0x007D, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050,
|
||||||
|
0x0051, 0x0052, 0x00B9, 0x00FB, 0x00FC, 0x00F9, 0x00FA, 0x00FF,
|
||||||
|
// E0-EF
|
||||||
|
0x005C, 0x00F7, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058,
|
||||||
|
0x0059, 0x005A, 0x00B2, 0x00D4, 0x00D6, 0x00D2, 0x00D3, 0x00D5,
|
||||||
|
// F0-FF
|
||||||
|
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
|
||||||
|
0x0038, 0x0039, 0x00B3, 0x00DB, 0x00DC, 0x00D9, 0x00DA, 0x009F,
|
||||||
|
};
|
||||||
|
|
||||||
|
public Cp285() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "285", MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 297 (France).
|
||||||
|
* CCSID / CPGID: 297, GCSGID: 697.
|
||||||
|
*/
|
||||||
|
public class Cp297 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "297";
|
||||||
|
public static final String DESCRIPTION = "France - EBCDIC";
|
||||||
|
public static final int CPGID = 297;
|
||||||
|
public static final int GCSGID = 697;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = {
|
||||||
|
// 00-0F
|
||||||
|
0x0000, 0x0001, 0x0002, 0x0003, 0x009C, 0x0009, 0x0086, 0x007F,
|
||||||
|
0x0097, 0x008D, 0x008E, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
|
||||||
|
// 10-1F
|
||||||
|
0x0010, 0x0011, 0x0012, 0x0013, 0x009D, 0x0085, 0x0008, 0x0087,
|
||||||
|
0x0018, 0x0019, 0x0092, 0x008F, 0x001C, 0x001D, 0x001E, 0x001F,
|
||||||
|
// 20-2F
|
||||||
|
0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x000A, 0x0017, 0x001B,
|
||||||
|
0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x0005, 0x0006, 0x0007,
|
||||||
|
// 30-3F
|
||||||
|
0x0090, 0x0091, 0x0016, 0x0093, 0x0094, 0x0095, 0x0096, 0x0004,
|
||||||
|
0x0098, 0x0099, 0x009A, 0x009B, 0x0014, 0x0015, 0x009E, 0x001A,
|
||||||
|
// 40-4F
|
||||||
|
0x0020, 0x00A0, 0x00E2, 0x007B, 0x00E0, 0x00E1, 0x00E3, 0x00E5,
|
||||||
|
0x00E7, 0x00F1, 0x00B0, 0x002E, 0x003C, 0x0028, 0x002B, 0x0021,
|
||||||
|
// 50-5F
|
||||||
|
0x0026, 0x00E9, 0x00EA, 0x00EB, 0x00E8, 0x00ED, 0x00EE, 0x00EF,
|
||||||
|
0x00EC, 0x00DF, 0x005D, 0x0024, 0x002A, 0x0029, 0x003B, 0x005E,
|
||||||
|
// 60-6F
|
||||||
|
0x002D, 0x002F, 0x00C2, 0x005B, 0x00C0, 0x00C1, 0x00C3, 0x00C5,
|
||||||
|
0x00C7, 0x00D1, 0x00A6, 0x002C, 0x0025, 0x005F, 0x003E, 0x003F,
|
||||||
|
// 70-7F
|
||||||
|
0x00F8, 0x00C9, 0x00CA, 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF,
|
||||||
|
0x00CC, 0x0060, 0x003A, 0x00E0, 0x00E9, 0x00E8, 0x003D, 0x0022,
|
||||||
|
// 80-8F
|
||||||
|
0x00D8, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
|
||||||
|
0x0068, 0x0069, 0x00AB, 0x00BB, 0x00F0, 0x00FD, 0x00FE, 0x00B1,
|
||||||
|
// 90-9F
|
||||||
|
0x00B0, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070,
|
||||||
|
0x0071, 0x0072, 0x00AA, 0x00BA, 0x00E6, 0x00B8, 0x00C6, 0x00A4,
|
||||||
|
// A0-AF
|
||||||
|
0x00B5, 0x007E, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078,
|
||||||
|
0x0079, 0x007A, 0x00A1, 0x00BF, 0x00D0, 0x00DD, 0x00DE, 0x00AE,
|
||||||
|
// B0-BF
|
||||||
|
0x00A2, 0x00A3, 0x00A5, 0x00B7, 0x00A9, 0x00A7, 0x00B6, 0x00BC,
|
||||||
|
0x00BD, 0x00BE, 0x00AC, 0x007C, 0x00AF, 0x00A8, 0x00B4, 0x00D7,
|
||||||
|
// C0-CF
|
||||||
|
0x00E9, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
|
||||||
|
0x0048, 0x0049, 0x00AD, 0x00F4, 0x00F6, 0x00F2, 0x00F3, 0x00F5,
|
||||||
|
// D0-DF
|
||||||
|
0x00E8, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050,
|
||||||
|
0x0051, 0x0052, 0x00B9, 0x00FB, 0x00FC, 0x00F9, 0x00FA, 0x00FF,
|
||||||
|
// E0-EF
|
||||||
|
0x005C, 0x00F7, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058,
|
||||||
|
0x0059, 0x005A, 0x00B2, 0x00D4, 0x00D6, 0x00D2, 0x00D3, 0x00D5,
|
||||||
|
// F0-FF
|
||||||
|
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
|
||||||
|
0x0038, 0x0039, 0x00B3, 0x00DB, 0x00DC, 0x00D9, 0x00DA, 0x009F,
|
||||||
|
};
|
||||||
|
|
||||||
|
public Cp297() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "297", MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 420 (Arabic Bilingual EBCDIC).
|
||||||
|
* CCSID / CPGID: 420, GCSGID: 235.
|
||||||
|
*/
|
||||||
|
public class Cp420 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "420";
|
||||||
|
public static final String DESCRIPTION = "Arabic Bilingual EBCDIC";
|
||||||
|
public static final int CPGID = 420;
|
||||||
|
public static final int GCSGID = 235;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = {
|
||||||
|
// 00-0F
|
||||||
|
0x0000, 0x0001, 0x0002, 0x0003, 0x009C, 0x0009, 0x0086, 0x007F,
|
||||||
|
0x0097, 0x008D, 0x008E, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
|
||||||
|
// 10-1F
|
||||||
|
0x0010, 0x0011, 0x0012, 0x0013, 0x009D, 0x0085, 0x0008, 0x0087,
|
||||||
|
0x0018, 0x0019, 0x0092, 0x008F, 0x001C, 0x001D, 0x001E, 0x001F,
|
||||||
|
// 20-2F
|
||||||
|
0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x000A, 0x0017, 0x001B,
|
||||||
|
0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x0005, 0x0006, 0x0007,
|
||||||
|
// 30-3F
|
||||||
|
0x0090, 0x0091, 0x0016, 0x0093, 0x0094, 0x0095, 0x0096, 0x0004,
|
||||||
|
0x0098, 0x0099, 0x009A, 0x009B, 0x0014, 0x0015, 0x009E, 0x001A,
|
||||||
|
// 40-4F
|
||||||
|
0x0020, 0x0640, 0xFE83, 0xFE87, 0xFE85, 0xFE81, 0xFE80, 0x0621,
|
||||||
|
0xFE8D, 0xFE8B, 0x005B, 0x002E, 0x003C, 0x0028, 0x002B, 0x0021,
|
||||||
|
// 50-5F
|
||||||
|
0x0026, 0xFE8E, 0xFE8F, 0xFE91, 0xFE93, 0xFE95, 0xFE97, 0xFE99,
|
||||||
|
0xFE9B, 0xFE9D, 0x005D, 0x0024, 0x002A, 0x0029, 0x003B, 0x005E,
|
||||||
|
// 60-6F
|
||||||
|
0x002D, 0x002F, 0xFE9F, 0xFEA1, 0xFEA3, 0xFEA5, 0xFEA7, 0xFEA9,
|
||||||
|
0xFEAB, 0xFEAD, 0x00A6, 0x002C, 0x0025, 0x005F, 0x003E, 0x003F,
|
||||||
|
// 70-7F
|
||||||
|
0xFEAF, 0xFEB1, 0xFEB3, 0xFEB5, 0xFEB7, 0xFEB9, 0xFEBB, 0xFEBD,
|
||||||
|
0xFEBF, 0x0060, 0x003A, 0x0023, 0x0040, 0x0027, 0x003D, 0x0022,
|
||||||
|
// 80-8F
|
||||||
|
0xFEC1, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
|
||||||
|
0x0068, 0x0069, 0xFEC3, 0xFEC5, 0xFEC7, 0xFEC9, 0xFECB, 0xFECD,
|
||||||
|
// 90-9F
|
||||||
|
0xFECF, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070,
|
||||||
|
0x0071, 0x0072, 0xFED1, 0xFED3, 0xFED5, 0xFED7, 0xFED9, 0xFEDB,
|
||||||
|
// A0-AF
|
||||||
|
0xFEDD, 0x007E, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078,
|
||||||
|
0x0079, 0x007A, 0xFEDF, 0xFEE1, 0xFEE3, 0xFEE5, 0xFEE7, 0xFEE9,
|
||||||
|
// B0-BF
|
||||||
|
0xFEEB, 0xFEED, 0xFEEF, 0xFEF1, 0xFEF3, 0xFEF5, 0xFEF7, 0xFEF9,
|
||||||
|
0xFEFB, 0x00AE, 0x00AC, 0x007C, 0x00AF, 0x00A8, 0x00B4, 0x00D7,
|
||||||
|
// C0-CF
|
||||||
|
0x007B, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
|
||||||
|
0x0048, 0x0049, 0x060C, 0x061B, 0x061F, 0x0628, 0x062A, 0x062B,
|
||||||
|
// D0-DF
|
||||||
|
0x007D, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050,
|
||||||
|
0x0051, 0x0052, 0x062C, 0x062D, 0x062E, 0x062F, 0x0630, 0x0631,
|
||||||
|
// E0-EF
|
||||||
|
0x005C, 0x0632, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058,
|
||||||
|
0x0059, 0x005A, 0x0633, 0x0634, 0x0635, 0x0636, 0x0637, 0x0638,
|
||||||
|
// F0-FF
|
||||||
|
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
|
||||||
|
0x0038, 0x0039, 0x0639, 0x063A, 0x0641, 0x0642, 0x0643, 0x009F,
|
||||||
|
};
|
||||||
|
|
||||||
|
public Cp420() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "420", MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 424 (Hebrew with Lowercase EBCDIC).
|
||||||
|
* CCSID / CPGID: 424, GCSGID: 941.
|
||||||
|
*/
|
||||||
|
public class Cp424 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "424";
|
||||||
|
public static final String DESCRIPTION = "Hebrew (with Lowercase) EBCDIC";
|
||||||
|
public static final int CPGID = 424;
|
||||||
|
public static final int GCSGID = 941;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = {
|
||||||
|
// 00-0F
|
||||||
|
0x0000, 0x0001, 0x0002, 0x0003, 0x009C, 0x0009, 0x0086, 0x007F,
|
||||||
|
0x0097, 0x008D, 0x008E, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
|
||||||
|
// 10-1F
|
||||||
|
0x0010, 0x0011, 0x0012, 0x0013, 0x009D, 0x0085, 0x0008, 0x0087,
|
||||||
|
0x0018, 0x0019, 0x0092, 0x008F, 0x001C, 0x001D, 0x001E, 0x001F,
|
||||||
|
// 20-2F
|
||||||
|
0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x000A, 0x0017, 0x001B,
|
||||||
|
0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x0005, 0x0006, 0x0007,
|
||||||
|
// 30-3F
|
||||||
|
0x0090, 0x0091, 0x0016, 0x0093, 0x0094, 0x0095, 0x0096, 0x0004,
|
||||||
|
0x0098, 0x0099, 0x009A, 0x009B, 0x0014, 0x0015, 0x009E, 0x001A,
|
||||||
|
// 40-4F
|
||||||
|
0x0020, 0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6,
|
||||||
|
0x05D7, 0x05D8, 0x005B, 0x002E, 0x003C, 0x0028, 0x002B, 0x0021,
|
||||||
|
// 50-5F
|
||||||
|
0x0026, 0x05D9, 0x05DA, 0x05DB, 0x05DC, 0x05DD, 0x05DE, 0x05DF,
|
||||||
|
0x05E0, 0x05E1, 0x005D, 0x0024, 0x002A, 0x0029, 0x003B, 0x005E,
|
||||||
|
// 60-6F
|
||||||
|
0x002D, 0x002F, 0x05E2, 0x05E3, 0x05E4, 0x05E5, 0x05E6, 0x05E7,
|
||||||
|
0x05E8, 0x05E9, 0x00A6, 0x002C, 0x0025, 0x005F, 0x003E, 0x003F,
|
||||||
|
// 70-7F
|
||||||
|
0x05EA, 0x00A0, 0x2017, 0x00B8, 0x00A8, 0x00B4, 0x00AA, 0x00BA,
|
||||||
|
0x00DF, 0x0060, 0x003A, 0x0023, 0x0040, 0x0027, 0x003D, 0x0022,
|
||||||
|
// 80-8F
|
||||||
|
0x00D8, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
|
||||||
|
0x0068, 0x0069, 0x00AB, 0x00BB, 0x00F0, 0x00FD, 0x00FE, 0x00B1,
|
||||||
|
// 90-9F
|
||||||
|
0x00B0, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070,
|
||||||
|
0x0071, 0x0072, 0x00AA, 0x00BA, 0x00E6, 0x00B8, 0x00C6, 0x00A4,
|
||||||
|
// A0-AF
|
||||||
|
0x00B5, 0x007E, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078,
|
||||||
|
0x0079, 0x007A, 0x00A1, 0x00BF, 0x00D0, 0x00DD, 0x00DE, 0x00AE,
|
||||||
|
// B0-BF
|
||||||
|
0x00A2, 0x00A3, 0x00A5, 0x00B7, 0x00A9, 0x00A7, 0x00B6, 0x00BC,
|
||||||
|
0x00BD, 0x00BE, 0x00AC, 0x007C, 0x00AF, 0x00A8, 0x00B4, 0x00D7,
|
||||||
|
// C0-CF
|
||||||
|
0x007B, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
|
||||||
|
0x0048, 0x0049, 0x00AD, 0x00F4, 0x00F6, 0x00F2, 0x00F3, 0x00F5,
|
||||||
|
// D0-DF
|
||||||
|
0x007D, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050,
|
||||||
|
0x0051, 0x0052, 0x00B9, 0x00FB, 0x00FC, 0x00F9, 0x00FA, 0x00FF,
|
||||||
|
// E0-EF
|
||||||
|
0x005C, 0x00F7, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058,
|
||||||
|
0x0059, 0x005A, 0x00B2, 0x00D4, 0x00D6, 0x00D2, 0x00D3, 0x00D5,
|
||||||
|
// F0-FF
|
||||||
|
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
|
||||||
|
0x0038, 0x0039, 0x00B3, 0x00DB, 0x00DC, 0x00D9, 0x00DA, 0x009F,
|
||||||
|
};
|
||||||
|
|
||||||
|
public Cp424() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "424", MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 500 (International / Western Europe Latin-1).
|
||||||
|
* CCSID / CPGID: 500, GCSGID: 697.
|
||||||
|
*/
|
||||||
|
public class Cp500 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "500";
|
||||||
|
public static final String DESCRIPTION = "International / Western Europe Latin-1";
|
||||||
|
public static final int CPGID = 500;
|
||||||
|
public static final int GCSGID = 697;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = {
|
||||||
|
// 00-0F
|
||||||
|
0x0000, 0x0001, 0x0002, 0x0003, 0x009C, 0x0009, 0x0086, 0x007F,
|
||||||
|
0x0097, 0x008D, 0x008E, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
|
||||||
|
// 10-1F
|
||||||
|
0x0010, 0x0011, 0x0012, 0x0013, 0x009D, 0x0085, 0x0008, 0x0087,
|
||||||
|
0x0018, 0x0019, 0x0092, 0x008F, 0x001C, 0x001D, 0x001E, 0x001F,
|
||||||
|
// 20-2F
|
||||||
|
0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x000A, 0x0017, 0x001B,
|
||||||
|
0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x0005, 0x0006, 0x0007,
|
||||||
|
// 30-3F
|
||||||
|
0x0090, 0x0091, 0x0016, 0x0093, 0x0094, 0x0095, 0x0096, 0x0004,
|
||||||
|
0x0098, 0x0099, 0x009A, 0x009B, 0x0014, 0x0015, 0x009E, 0x001A,
|
||||||
|
// 40-4F
|
||||||
|
0x0020, 0x00A0, 0x00E2, 0x00E4, 0x00E0, 0x00E1, 0x00E3, 0x00E5,
|
||||||
|
0x00E7, 0x00F1, 0x005B, 0x002E, 0x003C, 0x0028, 0x002B, 0x0021,
|
||||||
|
// 50-5F
|
||||||
|
0x0026, 0x00E9, 0x00EA, 0x00EB, 0x00E8, 0x00ED, 0x00EE, 0x00EF,
|
||||||
|
0x00EC, 0x00DF, 0x005D, 0x0024, 0x002A, 0x0029, 0x003B, 0x005E,
|
||||||
|
// 60-6F
|
||||||
|
0x002D, 0x002F, 0x00C2, 0x00C4, 0x00C0, 0x00C1, 0x00C3, 0x00C5,
|
||||||
|
0x00C7, 0x00D1, 0x00A6, 0x002C, 0x0025, 0x005F, 0x003E, 0x003F,
|
||||||
|
// 70-7F
|
||||||
|
0x00F8, 0x00C9, 0x00CA, 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF,
|
||||||
|
0x00CC, 0x0060, 0x003A, 0x0023, 0x0040, 0x0027, 0x003D, 0x0022,
|
||||||
|
// 80-8F
|
||||||
|
0x00D8, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
|
||||||
|
0x0068, 0x0069, 0x00AB, 0x00BB, 0x00F0, 0x00FD, 0x00FE, 0x00B1,
|
||||||
|
// 90-9F
|
||||||
|
0x00B0, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070,
|
||||||
|
0x0071, 0x0072, 0x00AA, 0x00BA, 0x00E6, 0x00B8, 0x00C6, 0x00A4,
|
||||||
|
// A0-AF
|
||||||
|
0x00B5, 0x007E, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078,
|
||||||
|
0x0079, 0x007A, 0x00A1, 0x00BF, 0x00D0, 0x00DD, 0x00DE, 0x00AE,
|
||||||
|
// B0-BF
|
||||||
|
0x00A2, 0x00A3, 0x00A5, 0x00B7, 0x00A9, 0x00A7, 0x00B6, 0x00BC,
|
||||||
|
0x00BD, 0x00BE, 0x00AC, 0x007C, 0x00AF, 0x00A8, 0x00B4, 0x00D7,
|
||||||
|
// C0-CF
|
||||||
|
0x007B, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
|
||||||
|
0x0048, 0x0049, 0x00AD, 0x00F4, 0x00F6, 0x00F2, 0x00F3, 0x00F5,
|
||||||
|
// D0-DF
|
||||||
|
0x007D, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050,
|
||||||
|
0x0051, 0x0052, 0x00B9, 0x00FB, 0x00FC, 0x00F9, 0x00FA, 0x00FF,
|
||||||
|
// E0-EF
|
||||||
|
0x005C, 0x00F7, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058,
|
||||||
|
0x0059, 0x005A, 0x00B2, 0x00D4, 0x00D6, 0x00D2, 0x00D3, 0x00D5,
|
||||||
|
// F0-FF
|
||||||
|
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
|
||||||
|
0x0038, 0x0039, 0x00B3, 0x00DB, 0x00DC, 0x00D9, 0x00DA, 0x009F,
|
||||||
|
};
|
||||||
|
|
||||||
|
public Cp500() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "500", MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 803 (Hebrew Old / Standard EBCDIC).
|
||||||
|
* CCSID / CPGID: 803, GCSGID: 1147.
|
||||||
|
*/
|
||||||
|
public class Cp803 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "803";
|
||||||
|
public static final String DESCRIPTION = "Hebrew Old / Standard EBCDIC";
|
||||||
|
public static final int CPGID = 803;
|
||||||
|
public static final int GCSGID = 1147;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = {
|
||||||
|
// 00-0F
|
||||||
|
0x0000, 0x0001, 0x0002, 0x0003, 0x009C, 0x0009, 0x0086, 0x007F,
|
||||||
|
0x0097, 0x008D, 0x008E, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
|
||||||
|
// 10-1F
|
||||||
|
0x0010, 0x0011, 0x0012, 0x0013, 0x009D, 0x0085, 0x0008, 0x0087,
|
||||||
|
0x0018, 0x0019, 0x0092, 0x008F, 0x001C, 0x001D, 0x001E, 0x001F,
|
||||||
|
// 20-2F
|
||||||
|
0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x000A, 0x0017, 0x001B,
|
||||||
|
0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x0005, 0x0006, 0x0007,
|
||||||
|
// 30-3F
|
||||||
|
0x0090, 0x0091, 0x0016, 0x0093, 0x0094, 0x0095, 0x0096, 0x0004,
|
||||||
|
0x0098, 0x0099, 0x009A, 0x009B, 0x0014, 0x0015, 0x009E, 0x001A,
|
||||||
|
// 40-4F
|
||||||
|
0x0020, 0x00A0, 0x00E2, 0x00E4, 0x00E0, 0x00E1, 0x00E3, 0x00E5,
|
||||||
|
0x00E7, 0x00F1, 0x00A2, 0x002E, 0x003C, 0x0028, 0x002B, 0x0021,
|
||||||
|
// 50-5F
|
||||||
|
0x0026, 0x00E9, 0x00EA, 0x00EB, 0x00E8, 0x00ED, 0x00EE, 0x00EF,
|
||||||
|
0x00EC, 0x00DF, 0x005D, 0x0024, 0x002A, 0x0029, 0x003B, 0x005E,
|
||||||
|
// 60-6F
|
||||||
|
0x002D, 0x002F, 0x00C2, 0x00C4, 0x00C0, 0x00C1, 0x00C3, 0x00C5,
|
||||||
|
0x00C7, 0x00D1, 0x00A6, 0x002C, 0x0025, 0x005F, 0x003E, 0x003F,
|
||||||
|
// 70-7F
|
||||||
|
0x00F8, 0x00C9, 0x00CA, 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF,
|
||||||
|
0x00CC, 0x0060, 0x003A, 0x0023, 0x0040, 0x0027, 0x003D, 0x0022,
|
||||||
|
// 80-8F
|
||||||
|
0x00D8, 0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6,
|
||||||
|
0x05D7, 0x05D8, 0x00AB, 0x00BB, 0x00F0, 0x00FD, 0x00FE, 0x00B1,
|
||||||
|
// 90-9F
|
||||||
|
0x00B0, 0x05D9, 0x05DA, 0x05DB, 0x05DC, 0x05DD, 0x05DE, 0x05DF,
|
||||||
|
0x05E0, 0x05E1, 0x00AA, 0x00BA, 0x00E6, 0x00B8, 0x00C6, 0x00A4,
|
||||||
|
// A0-AF
|
||||||
|
0x00B5, 0x007E, 0x05E2, 0x05E3, 0x05E4, 0x05E5, 0x05E6, 0x05E7,
|
||||||
|
0x05E8, 0x05E9, 0x00A1, 0x00BF, 0x00D0, 0x00DD, 0x00DE, 0x00AE,
|
||||||
|
// B0-BF
|
||||||
|
0x05EA, 0x00A3, 0x00A5, 0x00B7, 0x00A9, 0x00A7, 0x00B6, 0x00BC,
|
||||||
|
0x00BD, 0x00BE, 0x00AC, 0x007C, 0x00AF, 0x00A8, 0x00B4, 0x00D7,
|
||||||
|
// C0-CF
|
||||||
|
0x007B, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
|
||||||
|
0x0048, 0x0049, 0x00AD, 0x00F4, 0x00F6, 0x00F2, 0x00F3, 0x00F5,
|
||||||
|
// D0-DF
|
||||||
|
0x007D, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050,
|
||||||
|
0x0051, 0x0052, 0x00B9, 0x00FB, 0x00FC, 0x00F9, 0x00FA, 0x00FF,
|
||||||
|
// E0-EF
|
||||||
|
0x005C, 0x00F7, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058,
|
||||||
|
0x0059, 0x005A, 0x00B2, 0x00D4, 0x00D6, 0x00D2, 0x00D3, 0x00D5,
|
||||||
|
// F0-FF
|
||||||
|
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
|
||||||
|
0x0038, 0x0039, 0x00B3, 0x00DB, 0x00DC, 0x00D9, 0x00DA, 0x009F,
|
||||||
|
};
|
||||||
|
|
||||||
|
public Cp803() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "803", MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 838 (Thai EBCDIC).
|
||||||
|
* CCSID / CPGID: 838, GCSGID: 1176.
|
||||||
|
*/
|
||||||
|
public class Cp838 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "838";
|
||||||
|
public static final String DESCRIPTION = "Thai EBCDIC";
|
||||||
|
public static final int CPGID = 838;
|
||||||
|
public static final int GCSGID = 1176;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = {
|
||||||
|
// 00-0F
|
||||||
|
0x0000, 0x0001, 0x0002, 0x0003, 0x009C, 0x0009, 0x0086, 0x007F,
|
||||||
|
0x0097, 0x008D, 0x008E, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
|
||||||
|
// 10-1F
|
||||||
|
0x0010, 0x0011, 0x0012, 0x0013, 0x009D, 0x0085, 0x0008, 0x0087,
|
||||||
|
0x0018, 0x0019, 0x0092, 0x008F, 0x001C, 0x001D, 0x001E, 0x001F,
|
||||||
|
// 20-2F
|
||||||
|
0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x000A, 0x0017, 0x001B,
|
||||||
|
0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x0005, 0x0006, 0x0007,
|
||||||
|
// 30-3F
|
||||||
|
0x0090, 0x0091, 0x0016, 0x0093, 0x0094, 0x0095, 0x0096, 0x0004,
|
||||||
|
0x0098, 0x0099, 0x009A, 0x009B, 0x0014, 0x0015, 0x009E, 0x001A,
|
||||||
|
// 40-4F
|
||||||
|
0x0020, 0x0E01, 0x0E02, 0x0E03, 0x0E04, 0x0E05, 0x0E06, 0x0E07,
|
||||||
|
0x0E08, 0x0E09, 0x005B, 0x002E, 0x003C, 0x0028, 0x002B, 0x0021,
|
||||||
|
// 50-5F
|
||||||
|
0x0026, 0x0E0A, 0x0E0B, 0x0E0C, 0x0E0D, 0x0E0E, 0x0E0F, 0x0E10,
|
||||||
|
0x0E11, 0x0E12, 0x005D, 0x0024, 0x002A, 0x0029, 0x003B, 0x005E,
|
||||||
|
// 60-6F
|
||||||
|
0x002D, 0x002F, 0x0E13, 0x0E14, 0x0E15, 0x0E16, 0x0E17, 0x0E18,
|
||||||
|
0x0E19, 0x0E1A, 0x00A6, 0x002C, 0x0025, 0x005F, 0x003E, 0x003F,
|
||||||
|
// 70-7F
|
||||||
|
0x0E1B, 0x0E1C, 0x0E1D, 0x0E1E, 0x0E1F, 0x0E20, 0x0E21, 0x0E22,
|
||||||
|
0x0E23, 0x0060, 0x003A, 0x0023, 0x0040, 0x0027, 0x003D, 0x0022,
|
||||||
|
// 80-8F
|
||||||
|
0x0E24, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
|
||||||
|
0x0068, 0x0069, 0x0E25, 0x0E26, 0x0E27, 0x0E28, 0x0E29, 0x0E2A,
|
||||||
|
// 90-9F
|
||||||
|
0x0E2B, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070,
|
||||||
|
0x0071, 0x0072, 0x0E2C, 0x0E2D, 0x0E2E, 0x0E2F, 0x0E30, 0x0E31,
|
||||||
|
// A0-AF
|
||||||
|
0x0E32, 0x007E, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078,
|
||||||
|
0x0079, 0x007A, 0x0E33, 0x0E34, 0x0E35, 0x0E36, 0x0E37, 0x0E38,
|
||||||
|
// B0-BF
|
||||||
|
0x0E39, 0x0E3A, 0x0E40, 0x0E41, 0x0E42, 0x0E43, 0x0E44, 0x0E45,
|
||||||
|
0x0E46, 0x0E47, 0x00AC, 0x007C, 0x0E48, 0x0E49, 0x0E4A, 0x0E4B,
|
||||||
|
// C0-CF
|
||||||
|
0x007B, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
|
||||||
|
0x0048, 0x0049, 0x0E4C, 0x0E4D, 0x0E4E, 0x0E4F, 0x0E50, 0x0E51,
|
||||||
|
// D0-DF
|
||||||
|
0x007D, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050,
|
||||||
|
0x0051, 0x0052, 0x0E52, 0x0E53, 0x0E54, 0x0E55, 0x0E56, 0x0E57,
|
||||||
|
// E0-EF
|
||||||
|
0x005C, 0x00F7, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058,
|
||||||
|
0x0059, 0x005A, 0x0E58, 0x0E59, 0x0E5A, 0x0E5B, 0x00D3, 0x00D5,
|
||||||
|
// F0-FF
|
||||||
|
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
|
||||||
|
0x0038, 0x0039, 0x00B3, 0x00DB, 0x00DC, 0x00D9, 0x00DA, 0x009F,
|
||||||
|
};
|
||||||
|
|
||||||
|
public Cp838() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "838", MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 870 (Latin-2 / Eastern Europe: Polish, Czech, Slovak, Hungarian, etc.).
|
||||||
|
* CCSID / CPGID: 870, GCSGID: 959.
|
||||||
|
*/
|
||||||
|
public class Cp870 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "870";
|
||||||
|
public static final String DESCRIPTION = "Eastern Europe / Latin-2 - EBCDIC";
|
||||||
|
public static final int CPGID = 870;
|
||||||
|
public static final int GCSGID = 959;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = {
|
||||||
|
// 00-0F
|
||||||
|
0x0000, 0x0001, 0x0002, 0x0003, 0x009C, 0x0009, 0x0086, 0x007F,
|
||||||
|
0x0097, 0x008D, 0x008E, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
|
||||||
|
// 10-1F
|
||||||
|
0x0010, 0x0011, 0x0012, 0x0013, 0x009D, 0x0085, 0x0008, 0x0087,
|
||||||
|
0x0018, 0x0019, 0x0092, 0x008F, 0x001C, 0x001D, 0x001E, 0x001F,
|
||||||
|
// 20-2F
|
||||||
|
0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x000A, 0x0017, 0x001B,
|
||||||
|
0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x0005, 0x0006, 0x0007,
|
||||||
|
// 30-3F
|
||||||
|
0x0090, 0x0091, 0x0016, 0x0093, 0x0094, 0x0095, 0x0096, 0x0004,
|
||||||
|
0x0098, 0x0099, 0x009A, 0x009B, 0x0014, 0x0015, 0x009E, 0x001A,
|
||||||
|
// 40-4F
|
||||||
|
0x0020, 0x00A0, 0x0103, 0x00E4, 0x016F, 0x0165, 0x0105, 0x0155,
|
||||||
|
0x0107, 0x00E9, 0x015B, 0x002E, 0x003C, 0x0028, 0x002B, 0x0021,
|
||||||
|
// 50-5F
|
||||||
|
0x0026, 0x00E9, 0x0119, 0x00EB, 0x011B, 0x00ED, 0x00EE, 0x010F,
|
||||||
|
0x010D, 0x00DF, 0x005D, 0x0024, 0x002A, 0x0029, 0x003B, 0x005E,
|
||||||
|
// 60-6F
|
||||||
|
0x002D, 0x002F, 0x00C2, 0x00C4, 0x016E, 0x0164, 0x0104, 0x0154,
|
||||||
|
0x0106, 0x00C9, 0x015A, 0x002C, 0x0025, 0x005F, 0x003E, 0x003F,
|
||||||
|
// 70-7F
|
||||||
|
0x00F8, 0x00C9, 0x0118, 0x00CB, 0x011A, 0x00CD, 0x00CE, 0x010E,
|
||||||
|
0x010C, 0x0060, 0x003A, 0x0161, 0x017C, 0x00FD, 0x003D, 0x0022,
|
||||||
|
// 80-8F
|
||||||
|
0x00D8, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
|
||||||
|
0x0068, 0x0069, 0x00AB, 0x00BB, 0x00F0, 0x00FD, 0x00FE, 0x00B1,
|
||||||
|
// 90-9F
|
||||||
|
0x00B0, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070,
|
||||||
|
0x0071, 0x0072, 0x00AA, 0x00BA, 0x00E6, 0x00B8, 0x00C6, 0x00A4,
|
||||||
|
// A0-AF
|
||||||
|
0x00B5, 0x007E, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078,
|
||||||
|
0x0079, 0x007A, 0x00A1, 0x00BF, 0x00D0, 0x00DD, 0x00DE, 0x00AE,
|
||||||
|
// B0-BF
|
||||||
|
0x005E, 0x00A3, 0x00A5, 0x00B7, 0x00A9, 0x00A7, 0x00B6, 0x00BC,
|
||||||
|
0x00BD, 0x00BE, 0x005B, 0x005D, 0x00AF, 0x00A8, 0x00B4, 0x00D7,
|
||||||
|
// C0-CF
|
||||||
|
0x0160, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
|
||||||
|
0x0048, 0x0049, 0x00AD, 0x00F4, 0x00F6, 0x00F2, 0x00F3, 0x00F5,
|
||||||
|
// D0-DF
|
||||||
|
0x017B, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050,
|
||||||
|
0x0051, 0x0052, 0x00B9, 0x00FB, 0x00FC, 0x00F9, 0x00FA, 0x00FF,
|
||||||
|
// E0-EF
|
||||||
|
0x005C, 0x00F7, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058,
|
||||||
|
0x0059, 0x005A, 0x00B2, 0x00D4, 0x00D6, 0x00D2, 0x00D3, 0x00D5,
|
||||||
|
// F0-FF
|
||||||
|
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
|
||||||
|
0x0038, 0x0039, 0x00B3, 0x00DB, 0x00DC, 0x00D9, 0x00DA, 0x009F,
|
||||||
|
};
|
||||||
|
|
||||||
|
public Cp870() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "870", MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 871 (Iceland).
|
||||||
|
* CCSID / CPGID: 871, GCSGID: 697.
|
||||||
|
*/
|
||||||
|
public class Cp871 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "871";
|
||||||
|
public static final String DESCRIPTION = "Iceland - EBCDIC";
|
||||||
|
public static final int CPGID = 871;
|
||||||
|
public static final int GCSGID = 697;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = {
|
||||||
|
// 00-0F
|
||||||
|
0x0000, 0x0001, 0x0002, 0x0003, 0x009C, 0x0009, 0x0086, 0x007F,
|
||||||
|
0x0097, 0x008D, 0x008E, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
|
||||||
|
// 10-1F
|
||||||
|
0x0010, 0x0011, 0x0012, 0x0013, 0x009D, 0x0085, 0x0008, 0x0087,
|
||||||
|
0x0018, 0x0019, 0x0092, 0x008F, 0x001C, 0x001D, 0x001E, 0x001F,
|
||||||
|
// 20-2F
|
||||||
|
0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x000A, 0x0017, 0x001B,
|
||||||
|
0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x0005, 0x0006, 0x0007,
|
||||||
|
// 30-3F
|
||||||
|
0x0090, 0x0091, 0x0016, 0x0093, 0x0094, 0x0095, 0x0096, 0x0004,
|
||||||
|
0x0098, 0x0099, 0x009A, 0x009B, 0x0014, 0x0015, 0x009E, 0x001A,
|
||||||
|
// 40-4F
|
||||||
|
0x0020, 0x00A0, 0x00E2, 0x00E4, 0x00E0, 0x00E1, 0x00E3, 0x00E5,
|
||||||
|
0x00E7, 0x00F1, 0x00A2, 0x002E, 0x003C, 0x0028, 0x002B, 0x0021,
|
||||||
|
// 50-5F
|
||||||
|
0x0026, 0x00E9, 0x00EA, 0x00EB, 0x00E8, 0x00ED, 0x00EE, 0x00EF,
|
||||||
|
0x00EC, 0x00DF, 0x00A4, 0x0024, 0x002A, 0x0029, 0x003B, 0x005E,
|
||||||
|
// 60-6F
|
||||||
|
0x002D, 0x002F, 0x00C2, 0x00C4, 0x00C0, 0x00C1, 0x00C3, 0x00C5,
|
||||||
|
0x00C7, 0x00D1, 0x00A6, 0x002C, 0x0025, 0x005F, 0x003E, 0x003F,
|
||||||
|
// 70-7F
|
||||||
|
0x00F8, 0x00C9, 0x00CA, 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF,
|
||||||
|
0x00CC, 0x0060, 0x003A, 0x00FE, 0x00F0, 0x00FD, 0x003D, 0x0022,
|
||||||
|
// 80-8F
|
||||||
|
0x00D8, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
|
||||||
|
0x0068, 0x0069, 0x00AB, 0x00BB, 0x00F0, 0x00FD, 0x00FE, 0x00B1,
|
||||||
|
// 90-9F
|
||||||
|
0x00B0, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070,
|
||||||
|
0x0071, 0x0072, 0x00AA, 0x00BA, 0x00E6, 0x00B8, 0x00C6, 0x00A4,
|
||||||
|
// A0-AF
|
||||||
|
0x00B5, 0x007E, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078,
|
||||||
|
0x0079, 0x007A, 0x00A1, 0x00BF, 0x00D0, 0x00DD, 0x00DE, 0x00AE,
|
||||||
|
// B0-BF
|
||||||
|
0x005E, 0x00A3, 0x00A5, 0x00B7, 0x00A9, 0x00A7, 0x00B6, 0x00BC,
|
||||||
|
0x00BD, 0x00BE, 0x005B, 0x005D, 0x00AF, 0x00A8, 0x00B4, 0x00D7,
|
||||||
|
// C0-CF
|
||||||
|
0x00DE, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
|
||||||
|
0x0048, 0x0049, 0x00AD, 0x00F4, 0x00F6, 0x00F2, 0x00F3, 0x00F5,
|
||||||
|
// D0-DF
|
||||||
|
0x00D0, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050,
|
||||||
|
0x0051, 0x0052, 0x00B9, 0x00FB, 0x00FC, 0x00F9, 0x00FA, 0x00FF,
|
||||||
|
// E0-EF
|
||||||
|
0x00DD, 0x00F7, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058,
|
||||||
|
0x0059, 0x005A, 0x00B2, 0x00D4, 0x00D6, 0x00D2, 0x00D3, 0x00D5,
|
||||||
|
// F0-FF
|
||||||
|
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
|
||||||
|
0x0038, 0x0039, 0x00B3, 0x00DB, 0x00DC, 0x00D9, 0x00DA, 0x009F,
|
||||||
|
};
|
||||||
|
|
||||||
|
public Cp871() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "871", MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 875 (Greece - Greek EBCDIC).
|
||||||
|
* CCSID / CPGID: 875, GCSGID: 925.
|
||||||
|
*/
|
||||||
|
public class Cp875 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "875";
|
||||||
|
public static final String DESCRIPTION = "Greece - Greek EBCDIC";
|
||||||
|
public static final int CPGID = 875;
|
||||||
|
public static final int GCSGID = 925;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = {
|
||||||
|
// 00-0F
|
||||||
|
0x0000, 0x0001, 0x0002, 0x0003, 0x009C, 0x0009, 0x0086, 0x007F,
|
||||||
|
0x0097, 0x008D, 0x008E, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
|
||||||
|
// 10-1F
|
||||||
|
0x0010, 0x0011, 0x0012, 0x0013, 0x009D, 0x0085, 0x0008, 0x0087,
|
||||||
|
0x0018, 0x0019, 0x0092, 0x008F, 0x001C, 0x001D, 0x001E, 0x001F,
|
||||||
|
// 20-2F
|
||||||
|
0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x000A, 0x0017, 0x001B,
|
||||||
|
0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x0005, 0x0006, 0x0007,
|
||||||
|
// 30-3F
|
||||||
|
0x0090, 0x0091, 0x0016, 0x0093, 0x0094, 0x0095, 0x0096, 0x0004,
|
||||||
|
0x0098, 0x0099, 0x009A, 0x009B, 0x0014, 0x0015, 0x009E, 0x001A,
|
||||||
|
// 40-4F
|
||||||
|
0x0020, 0x00A0, 0x03AC, 0x03AD, 0x03AE, 0x03AF, 0x03CC, 0x03CD,
|
||||||
|
0x03CE, 0x00DF, 0x005B, 0x002E, 0x003C, 0x0028, 0x002B, 0x0021,
|
||||||
|
// 50-5F
|
||||||
|
0x0026, 0x0390, 0x03B0, 0x03CA, 0x03CB, 0x0390, 0x0385, 0x0386,
|
||||||
|
0x0388, 0x0389, 0x005D, 0x0024, 0x002A, 0x0029, 0x003B, 0x005E,
|
||||||
|
// 60-6F
|
||||||
|
0x002D, 0x002F, 0x038A, 0x038C, 0x038E, 0x038F, 0x03AA, 0x03AB,
|
||||||
|
0x00A9, 0x00AE, 0x00A6, 0x002C, 0x0025, 0x005F, 0x003E, 0x003F,
|
||||||
|
// 70-7F
|
||||||
|
0x00F8, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7,
|
||||||
|
0x03B8, 0x0060, 0x003A, 0x0023, 0x0040, 0x0027, 0x003D, 0x0022,
|
||||||
|
// 80-8F
|
||||||
|
0x03B9, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
|
||||||
|
0x0068, 0x0069, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF,
|
||||||
|
// 90-9F
|
||||||
|
0x03C0, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070,
|
||||||
|
0x0071, 0x0072, 0x03C1, 0x03C3, 0x03C2, 0x03C4, 0x03C5, 0x03C6,
|
||||||
|
// A0-AF
|
||||||
|
0x03C7, 0x007E, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078,
|
||||||
|
0x0079, 0x007A, 0x03C8, 0x03C9, 0x00D0, 0x00DD, 0x00DE, 0x00AE,
|
||||||
|
// B0-BF
|
||||||
|
0x005E, 0x00A3, 0x00A5, 0x00B7, 0x00A9, 0x00A7, 0x00B6, 0x00BC,
|
||||||
|
0x00BD, 0x00BE, 0x00AC, 0x007C, 0x00AF, 0x00A8, 0x00B4, 0x00D7,
|
||||||
|
// C0-CF
|
||||||
|
0x007B, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
|
||||||
|
0x0048, 0x0049, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396,
|
||||||
|
// D0-DF
|
||||||
|
0x007D, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050,
|
||||||
|
0x0051, 0x0052, 0x0397, 0x0398, 0x0399, 0x039A, 0x039B, 0x039C,
|
||||||
|
// E0-EF
|
||||||
|
0x005C, 0x039D, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058,
|
||||||
|
0x0059, 0x005A, 0x039E, 0x039F, 0x03A0, 0x03A1, 0x03A3, 0x03A4,
|
||||||
|
// F0-FF
|
||||||
|
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
|
||||||
|
0x0038, 0x0039, 0x03A5, 0x03A6, 0x03A7, 0x03A8, 0x03A9, 0x009F,
|
||||||
|
};
|
||||||
|
|
||||||
|
public Cp875() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "875", MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 880 (Cyrillic Russian EBCDIC).
|
||||||
|
* CCSID / CPGID: 880, GCSGID: 960.
|
||||||
|
*/
|
||||||
|
public class Cp880 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "880";
|
||||||
|
public static final String DESCRIPTION = "Cyrillic Russian EBCDIC";
|
||||||
|
public static final int CPGID = 880;
|
||||||
|
public static final int GCSGID = 960;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = {
|
||||||
|
// 00-0F
|
||||||
|
0x0000, 0x0001, 0x0002, 0x0003, 0x009C, 0x0009, 0x0086, 0x007F,
|
||||||
|
0x0097, 0x008D, 0x008E, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
|
||||||
|
// 10-1F
|
||||||
|
0x0010, 0x0011, 0x0012, 0x0013, 0x009D, 0x0085, 0x0008, 0x0087,
|
||||||
|
0x0018, 0x0019, 0x0092, 0x008F, 0x001C, 0x001D, 0x001E, 0x001F,
|
||||||
|
// 20-2F
|
||||||
|
0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x000A, 0x0017, 0x001B,
|
||||||
|
0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x0005, 0x0006, 0x0007,
|
||||||
|
// 30-3F
|
||||||
|
0x0090, 0x0091, 0x0016, 0x0093, 0x0094, 0x0095, 0x0096, 0x0004,
|
||||||
|
0x0098, 0x0099, 0x009A, 0x009B, 0x0014, 0x0015, 0x009E, 0x001A,
|
||||||
|
// 40-4F
|
||||||
|
0x0020, 0x00A0, 0x044F, 0x044E, 0x044D, 0x044C, 0x044B, 0x044A,
|
||||||
|
0x0449, 0x0448, 0x005B, 0x002E, 0x003C, 0x0028, 0x002B, 0x0021,
|
||||||
|
// 50-5F
|
||||||
|
0x0026, 0x0447, 0x0446, 0x0445, 0x0444, 0x0443, 0x0442, 0x0441,
|
||||||
|
0x0440, 0x043F, 0x005D, 0x0024, 0x002A, 0x0029, 0x003B, 0x005E,
|
||||||
|
// 60-6F
|
||||||
|
0x002D, 0x002F, 0x043E, 0x043D, 0x043C, 0x043B, 0x043A, 0x0439,
|
||||||
|
0x0438, 0x0437, 0x00A6, 0x002C, 0x0025, 0x005F, 0x003E, 0x003F,
|
||||||
|
// 70-7F
|
||||||
|
0x0436, 0x0435, 0x0434, 0x0433, 0x0432, 0x0431, 0x0430, 0x0451,
|
||||||
|
0x045E, 0x0060, 0x003A, 0x0023, 0x0040, 0x0027, 0x003D, 0x0022,
|
||||||
|
// 80-8F
|
||||||
|
0x045F, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
|
||||||
|
0x0068, 0x0069, 0x0456, 0x0458, 0x0459, 0x045A, 0x045C, 0x045B,
|
||||||
|
// 90-9F
|
||||||
|
0x0402, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070,
|
||||||
|
0x0071, 0x0072, 0x0403, 0x0404, 0x0405, 0x0406, 0x0408, 0x0409,
|
||||||
|
// A0-AF
|
||||||
|
0x040A, 0x007E, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078,
|
||||||
|
0x0079, 0x007A, 0x040C, 0x040B, 0x040F, 0x040E, 0x0401, 0x00AE,
|
||||||
|
// B0-BF
|
||||||
|
0x005E, 0x00A3, 0x00A5, 0x00B7, 0x00A9, 0x00A7, 0x00B6, 0x00BC,
|
||||||
|
0x00BD, 0x00BE, 0x00AC, 0x007C, 0x00AF, 0x00A8, 0x00B4, 0x00D7,
|
||||||
|
// C0-CF
|
||||||
|
0x007B, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
|
||||||
|
0x0048, 0x0049, 0x041F, 0x0420, 0x0421, 0x0422, 0x0423, 0x0424,
|
||||||
|
// D0-DF
|
||||||
|
0x007D, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050,
|
||||||
|
0x0051, 0x0052, 0x0425, 0x0426, 0x0427, 0x0428, 0x0429, 0x042A,
|
||||||
|
// E0-EF
|
||||||
|
0x005C, 0x00F7, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058,
|
||||||
|
0x0059, 0x005A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F, 0x0410,
|
||||||
|
// F0-FF
|
||||||
|
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
|
||||||
|
0x0038, 0x0039, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x009F,
|
||||||
|
};
|
||||||
|
|
||||||
|
public Cp880() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "880", MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 905 (Turkey - Turkish Latin-3 EBCDIC).
|
||||||
|
* CCSID / CPGID: 905, GCSGID: 1151.
|
||||||
|
*/
|
||||||
|
public class Cp905 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "905";
|
||||||
|
public static final String DESCRIPTION = "Turkey - Turkish Latin-3 EBCDIC";
|
||||||
|
public static final int CPGID = 905;
|
||||||
|
public static final int GCSGID = 1151;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = {
|
||||||
|
// 00-0F
|
||||||
|
0x0000, 0x0001, 0x0002, 0x0003, 0x009C, 0x0009, 0x0086, 0x007F,
|
||||||
|
0x0097, 0x008D, 0x008E, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
|
||||||
|
// 10-1F
|
||||||
|
0x0010, 0x0011, 0x0012, 0x0013, 0x009D, 0x0085, 0x0008, 0x0087,
|
||||||
|
0x0018, 0x0019, 0x0092, 0x008F, 0x001C, 0x001D, 0x001E, 0x001F,
|
||||||
|
// 20-2F
|
||||||
|
0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x000A, 0x0017, 0x001B,
|
||||||
|
0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x0005, 0x0006, 0x0007,
|
||||||
|
// 30-3F
|
||||||
|
0x0090, 0x0091, 0x0016, 0x0093, 0x0094, 0x0095, 0x0096, 0x0004,
|
||||||
|
0x0098, 0x0099, 0x009A, 0x009B, 0x0014, 0x0015, 0x009E, 0x001A,
|
||||||
|
// 40-4F
|
||||||
|
0x0020, 0x00A0, 0x00E2, 0x00E4, 0x00E0, 0x00E1, 0x00E3, 0x00E5,
|
||||||
|
0x00E7, 0x00F1, 0x011E, 0x002E, 0x003C, 0x0028, 0x002B, 0x0021,
|
||||||
|
// 50-5F
|
||||||
|
0x0026, 0x00E9, 0x00EA, 0x00EB, 0x00E8, 0x00ED, 0x00EE, 0x00EF,
|
||||||
|
0x00EC, 0x00DF, 0x0130, 0x0024, 0x002A, 0x0029, 0x003B, 0x005E,
|
||||||
|
// 60-6F
|
||||||
|
0x002D, 0x002F, 0x00C2, 0x00C4, 0x00C0, 0x00C1, 0x00C3, 0x00C5,
|
||||||
|
0x00C7, 0x00D1, 0x015E, 0x002C, 0x0025, 0x005F, 0x003E, 0x003F,
|
||||||
|
// 70-7F
|
||||||
|
0x00F8, 0x00C9, 0x00CA, 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF,
|
||||||
|
0x00CC, 0x0060, 0x003A, 0x0023, 0x0040, 0x0027, 0x003D, 0x0022,
|
||||||
|
// 80-8F
|
||||||
|
0x00D8, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
|
||||||
|
0x0068, 0x0069, 0x00AB, 0x00BB, 0x00F0, 0x00FD, 0x00FE, 0x00B1,
|
||||||
|
// 90-9F
|
||||||
|
0x00B0, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070,
|
||||||
|
0x0071, 0x0072, 0x00AA, 0x00BA, 0x00E6, 0x00B8, 0x00C6, 0x00A4,
|
||||||
|
// A0-AF
|
||||||
|
0x00B5, 0x007E, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078,
|
||||||
|
0x0079, 0x007A, 0x00A1, 0x00BF, 0x00D0, 0x00DD, 0x00DE, 0x00AE,
|
||||||
|
// B0-BF
|
||||||
|
0x00A2, 0x00A3, 0x00A5, 0x00B7, 0x00A9, 0x00A7, 0x00B6, 0x00BC,
|
||||||
|
0x00BD, 0x00BE, 0x00AC, 0x007C, 0x00AF, 0x00A8, 0x00B4, 0x00D7,
|
||||||
|
// C0-CF
|
||||||
|
0x011F, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
|
||||||
|
0x0048, 0x0049, 0x00AD, 0x00F4, 0x00F6, 0x00F2, 0x00F3, 0x00F5,
|
||||||
|
// D0-DF
|
||||||
|
0x0131, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050,
|
||||||
|
0x0051, 0x0052, 0x00B9, 0x00FB, 0x00FC, 0x00F9, 0x00FA, 0x00FF,
|
||||||
|
// E0-EF
|
||||||
|
0x015F, 0x00F7, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058,
|
||||||
|
0x0059, 0x005A, 0x00B2, 0x00D4, 0x00D6, 0x00D2, 0x00D3, 0x00D5,
|
||||||
|
// F0-FF
|
||||||
|
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
|
||||||
|
0x0038, 0x0039, 0x00B3, 0x00DB, 0x00DC, 0x00D9, 0x00DA, 0x009F,
|
||||||
|
};
|
||||||
|
|
||||||
|
public Cp905() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "905", MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 930 (Japanese Katakana Mixed DBCS: host SBCS CP290 / DBCS CP300).
|
||||||
|
* CCSID / CPGID: 930, GCSGID: 1172.
|
||||||
|
*/
|
||||||
|
public class Cp930 extends AbstractDBCSCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "930";
|
||||||
|
public static final String DESCRIPTION = "Japanese Katakana Mixed DBCS";
|
||||||
|
public static final int CPGID = 930;
|
||||||
|
public static final int GCSGID = 1172;
|
||||||
|
|
||||||
|
public Cp930() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, Cp037.MAPPING, "x-IBM930");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 933 (Korean Mixed DBCS).
|
||||||
|
* CCSID / CPGID: 933, GCSGID: 1173.
|
||||||
|
*/
|
||||||
|
public class Cp933 extends AbstractDBCSCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "933";
|
||||||
|
public static final String DESCRIPTION = "Korean Mixed DBCS";
|
||||||
|
public static final int CPGID = 933;
|
||||||
|
public static final int GCSGID = 1173;
|
||||||
|
|
||||||
|
public Cp933() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, Cp037.MAPPING, "x-IBM933");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 935 / 1388 (Simplified Chinese Mixed DBCS).
|
||||||
|
* CCSID / CPGID: 935, GCSGID: 1175.
|
||||||
|
*/
|
||||||
|
public class Cp935 extends AbstractDBCSCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "935";
|
||||||
|
public static final String DESCRIPTION = "Simplified Chinese Mixed DBCS";
|
||||||
|
public static final int CPGID = 935;
|
||||||
|
public static final int GCSGID = 1175;
|
||||||
|
|
||||||
|
public Cp935() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, Cp037.MAPPING, "x-IBM935");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Cp1388 extends AbstractDBCSCodePage {
|
||||||
|
public static final String ID = "1388";
|
||||||
|
public static final String DESCRIPTION = "Simplified Chinese Extended Mixed DBCS";
|
||||||
|
public static final int CPGID = 1388;
|
||||||
|
public static final int GCSGID = 1175;
|
||||||
|
|
||||||
|
public Cp1388() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, Cp037.MAPPING, "x-IBM1388");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 937 / 1371 (Traditional Chinese Mixed DBCS).
|
||||||
|
* CCSID / CPGID: 937, GCSGID: 1174.
|
||||||
|
*/
|
||||||
|
public class Cp937 extends AbstractDBCSCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "937";
|
||||||
|
public static final String DESCRIPTION = "Traditional Chinese Mixed DBCS";
|
||||||
|
public static final int CPGID = 937;
|
||||||
|
public static final int GCSGID = 1174;
|
||||||
|
|
||||||
|
public Cp937() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, Cp037.MAPPING, "x-IBM937");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Cp1371 extends AbstractDBCSCodePage {
|
||||||
|
public static final String ID = "1371";
|
||||||
|
public static final String DESCRIPTION = "Traditional Chinese Extended Mixed DBCS";
|
||||||
|
public static final int CPGID = 1371;
|
||||||
|
public static final int GCSGID = 1174;
|
||||||
|
|
||||||
|
public Cp1371() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, Cp037.MAPPING, "x-IBM1371");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 939 (Japanese Latin Mixed DBCS: host SBCS CP1027 / DBCS CP300).
|
||||||
|
* CCSID / CPGID: 939, GCSGID: 1172.
|
||||||
|
*/
|
||||||
|
public class Cp939 extends AbstractDBCSCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "939";
|
||||||
|
public static final String DESCRIPTION = "Japanese Latin Mixed DBCS";
|
||||||
|
public static final int CPGID = 939;
|
||||||
|
public static final int GCSGID = 1172;
|
||||||
|
|
||||||
|
public Cp939() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, Cp037.MAPPING, "x-IBM939");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Euro currency symbol (€) enabled EBCDIC code pages (CP1140 through CP1149).
|
||||||
|
*/
|
||||||
|
public class CpEuroVariants {
|
||||||
|
|
||||||
|
public static class Cp1140 extends AbstractCodePage {
|
||||||
|
public static final String ID = "1140";
|
||||||
|
public static final String DESCRIPTION = "US / Canada Euro - EBCDIC";
|
||||||
|
public static final int CPGID = 1140;
|
||||||
|
public static final int GCSGID = 695;
|
||||||
|
|
||||||
|
public Cp1140() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "1140", modifyEuro(Cp037.MAPPING, 0x9F));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Cp1141 extends AbstractCodePage {
|
||||||
|
public static final String ID = "1141";
|
||||||
|
public static final String DESCRIPTION = "Germany / Austria Euro - EBCDIC";
|
||||||
|
public static final int CPGID = 1141;
|
||||||
|
public static final int GCSGID = 695;
|
||||||
|
|
||||||
|
public Cp1141() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "1141", modifyEuro(Cp273.MAPPING, 0x9F));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Cp1142 extends AbstractCodePage {
|
||||||
|
public static final String ID = "1142";
|
||||||
|
public static final String DESCRIPTION = "Denmark / Norway Euro - EBCDIC";
|
||||||
|
public static final int CPGID = 1142;
|
||||||
|
public static final int GCSGID = 695;
|
||||||
|
|
||||||
|
public Cp1142() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "1142", modifyEuro(Cp277.MAPPING, 0x5A));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Cp1143 extends AbstractCodePage {
|
||||||
|
public static final String ID = "1143";
|
||||||
|
public static final String DESCRIPTION = "Sweden / Finland Euro - EBCDIC";
|
||||||
|
public static final int CPGID = 1143;
|
||||||
|
public static final int GCSGID = 695;
|
||||||
|
|
||||||
|
public Cp1143() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "1143", modifyEuro(Cp278.MAPPING, 0x5A));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Cp1144 extends AbstractCodePage {
|
||||||
|
public static final String ID = "1144";
|
||||||
|
public static final String DESCRIPTION = "Italy Euro - EBCDIC";
|
||||||
|
public static final int CPGID = 1144;
|
||||||
|
public static final int GCSGID = 695;
|
||||||
|
|
||||||
|
public Cp1144() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "1144", modifyEuro(Cp280.MAPPING, 0x9F));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Cp1145 extends AbstractCodePage {
|
||||||
|
public static final String ID = "1145";
|
||||||
|
public static final String DESCRIPTION = "Spain / Latin America Euro - EBCDIC";
|
||||||
|
public static final int CPGID = 1145;
|
||||||
|
public static final int GCSGID = 695;
|
||||||
|
|
||||||
|
public Cp1145() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "1145", modifyEuro(Cp284.MAPPING, 0x9F));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Cp1146 extends AbstractCodePage {
|
||||||
|
public static final String ID = "1146";
|
||||||
|
public static final String DESCRIPTION = "United Kingdom Euro - EBCDIC";
|
||||||
|
public static final int CPGID = 1146;
|
||||||
|
public static final int GCSGID = 695;
|
||||||
|
|
||||||
|
public Cp1146() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "1146", modifyEuro(Cp285.MAPPING, 0x9F));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Cp1147 extends AbstractCodePage {
|
||||||
|
public static final String ID = "1147";
|
||||||
|
public static final String DESCRIPTION = "France Euro - EBCDIC";
|
||||||
|
public static final int CPGID = 1147;
|
||||||
|
public static final int GCSGID = 695;
|
||||||
|
|
||||||
|
public Cp1147() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "1147", modifyEuro(Cp297.MAPPING, 0x9F));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Cp1148 extends AbstractCodePage {
|
||||||
|
public static final String ID = "1148";
|
||||||
|
public static final String DESCRIPTION = "International Latin-1 Euro - EBCDIC";
|
||||||
|
public static final int CPGID = 1148;
|
||||||
|
public static final int GCSGID = 695;
|
||||||
|
|
||||||
|
public Cp1148() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "1148", modifyEuro(Cp500.MAPPING, 0x9F));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Cp1149 extends AbstractCodePage {
|
||||||
|
public static final String ID = "1149";
|
||||||
|
public static final String DESCRIPTION = "Iceland Euro - EBCDIC";
|
||||||
|
public static final int CPGID = 1149;
|
||||||
|
public static final int GCSGID = 695;
|
||||||
|
|
||||||
|
public Cp1149() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, "1149", modifyEuro(Cp871.MAPPING, 0x9F));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int[] modifyEuro(int[] baseMapping, int euroPosition) {
|
||||||
|
int[] copy = Arrays.copyOf(baseMapping, 256);
|
||||||
|
copy[euroPosition & 0xFF] = '\u20AC'; // '€'
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,113 +1,246 @@
|
|||||||
package haus.nightmare.lib3270j.charset;
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EBCDIC ↔ Unicode translator.
|
* EBCDIC ↔ Unicode character translator conforming to IBM Host On-Demand (HoD v14).
|
||||||
|
* Modular architecture delegating to pluggable CodePage implementations (SBCS and DBCS).
|
||||||
|
* Supports custom per-instance character translation override tables and complete
|
||||||
|
* IBM 3270 APL / Graphic Escape (GA23-0059) character mappings.
|
||||||
* Default: Code Page 037 (US/Canada EBCDIC).
|
* Default: Code Page 037 (US/Canada EBCDIC).
|
||||||
*/
|
*/
|
||||||
public class EbcdicTranslator {
|
public class EbcdicTranslator {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EBCDIC Code Page 037 to Unicode mapping.
|
* EBCDIC Code Page 037 to Unicode mapping table preserved for legacy static access.
|
||||||
* Index is the EBCDIC byte value (0x00-0xFF), value is the Unicode codepoint.
|
|
||||||
*/
|
*/
|
||||||
private static final int[] CP037_TO_UNICODE = {
|
public static final int[] CP037_TO_UNICODE = Cp037.MAPPING;
|
||||||
// 00-0F
|
|
||||||
0x0000, 0x0001, 0x0002, 0x0003, 0x009C, 0x0009, 0x0086, 0x007F,
|
private CodePage activeCodePage;
|
||||||
0x0097, 0x008D, 0x008E, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
|
private final Map<Integer, Character> customEbcdicToUnicode = new ConcurrentHashMap<>();
|
||||||
// 10-1F
|
private final Map<Character, Integer> customUnicodeToEbcdic = new ConcurrentHashMap<>();
|
||||||
0x0010, 0x0011, 0x0012, 0x0013, 0x009D, 0x0085, 0x0008, 0x0087,
|
|
||||||
0x0018, 0x0019, 0x0092, 0x008F, 0x001C, 0x001D, 0x001E, 0x001F,
|
public EbcdicTranslator() {
|
||||||
// 20-2F
|
this("037");
|
||||||
0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x000A, 0x0017, 0x001B,
|
}
|
||||||
0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x0005, 0x0006, 0x0007,
|
|
||||||
// 30-3F
|
public EbcdicTranslator(String codePageId) {
|
||||||
0x0090, 0x0091, 0x0016, 0x0093, 0x0094, 0x0095, 0x0096, 0x0004,
|
setCodePage(codePageId);
|
||||||
0x0098, 0x0099, 0x009A, 0x009B, 0x0014, 0x0015, 0x009E, 0x001A,
|
}
|
||||||
// 40-4F (space, accent chars, punctuation)
|
|
||||||
0x0020, 0x00A0, 0x00E2, 0x00E4, 0x00E0, 0x00E1, 0x00E3, 0x00E5,
|
public EbcdicTranslator(CodePage codePage) {
|
||||||
0x00E7, 0x00F1, 0x00A2, 0x002E, 0x003C, 0x0028, 0x002B, 0x007C,
|
setCodePage(codePage);
|
||||||
// 50-5F
|
}
|
||||||
0x0026, 0x00E9, 0x00EA, 0x00EB, 0x00E8, 0x00ED, 0x00EE, 0x00EF,
|
|
||||||
0x00EC, 0x00DF, 0x0021, 0x0024, 0x002A, 0x0029, 0x003B, 0x00AC,
|
|
||||||
// 60-6F
|
|
||||||
0x002D, 0x002F, 0x00C2, 0x00C4, 0x00C0, 0x00C1, 0x00C3, 0x00C5,
|
|
||||||
0x00C7, 0x00D1, 0x00A6, 0x002C, 0x0025, 0x005F, 0x003E, 0x003F,
|
|
||||||
// 70-7F
|
|
||||||
0x00F8, 0x00C9, 0x00CA, 0x00CB, 0x00C8, 0x00CD, 0x00CE, 0x00CF,
|
|
||||||
0x00CC, 0x0060, 0x003A, 0x0023, 0x0040, 0x0027, 0x003D, 0x0022,
|
|
||||||
// 80-8F (lowercase a-i)
|
|
||||||
0x00D8, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
|
|
||||||
0x0068, 0x0069, 0x00AB, 0x00BB, 0x00F0, 0x00FD, 0x00FE, 0x00B1,
|
|
||||||
// 90-9F (lowercase j-r)
|
|
||||||
0x00B0, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, 0x0070,
|
|
||||||
0x0071, 0x0072, 0x00AA, 0x00BA, 0x00E6, 0x00B8, 0x00C6, 0x00A4,
|
|
||||||
// A0-AF (lowercase s-z)
|
|
||||||
0x00B5, 0x007E, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078,
|
|
||||||
0x0079, 0x007A, 0x00A1, 0x00BF, 0x00D0, 0x00DD, 0x00DE, 0x00AE,
|
|
||||||
// B0-BF
|
|
||||||
0x005E, 0x00A3, 0x00A5, 0x00B7, 0x00A9, 0x00A7, 0x00B6, 0x00BC,
|
|
||||||
0x00BD, 0x00BE, 0x005B, 0x005D, 0x00AF, 0x00A8, 0x00B4, 0x00D7,
|
|
||||||
// C0-CF (uppercase A-I)
|
|
||||||
0x007B, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
|
|
||||||
0x0048, 0x0049, 0x00AD, 0x00F4, 0x00F6, 0x00F2, 0x00F3, 0x00F5,
|
|
||||||
// D0-DF (uppercase J-R)
|
|
||||||
0x007D, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, 0x0050,
|
|
||||||
0x0051, 0x0052, 0x00B9, 0x00FB, 0x00FC, 0x00F9, 0x00FA, 0x00FF,
|
|
||||||
// E0-EF (uppercase S-Z)
|
|
||||||
0x005C, 0x00F7, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058,
|
|
||||||
0x0059, 0x005A, 0x00B2, 0x00D4, 0x00D6, 0x00D2, 0x00D3, 0x00D5,
|
|
||||||
// F0-FF (digits 0-9)
|
|
||||||
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
|
|
||||||
0x0038, 0x0039, 0x00B3, 0x00DB, 0x00DC, 0x00D9, 0x00DA, 0x009F,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unicode to EBCDIC Code Page 037 mapping (for basic Latin + Latin-1).
|
* Set active code page by identifier or alias (e.g. "037", "1047", "500", "273", "1140", "930").
|
||||||
* Index is the Unicode codepoint (0x00-0xFF), value is the EBCDIC byte (-1 if unmappable).
|
|
||||||
*/
|
*/
|
||||||
private static final int[] UNICODE_TO_CP037 = new int[256];
|
public synchronized void setCodePage(String codePageId) {
|
||||||
|
this.activeCodePage = CodePageRegistry.getCodePage(codePageId);
|
||||||
|
}
|
||||||
|
|
||||||
static {
|
/**
|
||||||
// Build reverse mapping
|
* Set active code page directly.
|
||||||
java.util.Arrays.fill(UNICODE_TO_CP037, -1);
|
*/
|
||||||
for (int i = 0; i < 256; i++) {
|
public synchronized void setCodePage(CodePage codePage) {
|
||||||
int unicode = CP037_TO_UNICODE[i];
|
if (codePage != null) {
|
||||||
if (unicode < 256) {
|
this.activeCodePage = codePage;
|
||||||
UNICODE_TO_CP037[unicode] = i;
|
} else {
|
||||||
|
this.activeCodePage = CodePageRegistry.getCodePage("037");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the active CodePage descriptor and translator.
|
||||||
|
*/
|
||||||
|
public synchronized CodePage getCodePage() {
|
||||||
|
return activeCodePage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the active code page identifier string.
|
||||||
|
*/
|
||||||
|
public synchronized String getCodePageId() {
|
||||||
|
return activeCodePage.getCodePageId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get active Code Page Global ID (CPGID / CCSID) for Query Replies.
|
||||||
|
*/
|
||||||
|
public synchronized int getCpgid() {
|
||||||
|
return activeCodePage.getCpgid();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get active Graphic Character Set Global ID (GCSGID) for Query Replies.
|
||||||
|
*/
|
||||||
|
public synchronized int getCgcsgid() {
|
||||||
|
return activeCodePage.getCgcsgid();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if active code page is Double-Byte / mixed DBCS.
|
||||||
|
*/
|
||||||
|
public synchronized boolean isDBCSCodePage() {
|
||||||
|
return activeCodePage.isDBCS();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean isDBCS() {
|
||||||
|
return isDBCSCodePage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Custom Character Translation Overrides
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a bidirectional custom character translation override.
|
||||||
|
* Maps an EBCDIC byte value to a Unicode character, and vice versa.
|
||||||
|
*/
|
||||||
|
public synchronized void setCustomOverride(int ebcdicByte, char unicodeChar) {
|
||||||
|
int b = ebcdicByte & 0xFF;
|
||||||
|
customEbcdicToUnicode.put(b, unicodeChar);
|
||||||
|
customUnicodeToEbcdic.put(unicodeChar, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a one-way custom EBCDIC byte to Unicode character override.
|
||||||
|
*/
|
||||||
|
public synchronized void setCustomEbcdicToUnicodeOverride(int ebcdicByte, char unicodeChar) {
|
||||||
|
customEbcdicToUnicode.put(ebcdicByte & 0xFF, unicodeChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a one-way custom Unicode character to EBCDIC byte override.
|
||||||
|
*/
|
||||||
|
public synchronized void setCustomUnicodeToEbcdicOverride(char unicodeChar, int ebcdicByte) {
|
||||||
|
customUnicodeToEbcdic.put(unicodeChar, ebcdicByte & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set custom translation overrides in bulk.
|
||||||
|
*/
|
||||||
|
public synchronized void setCustomOverrides(Map<Integer, Character> ebcToUni, Map<Character, Integer> uniToEbc) {
|
||||||
|
if (ebcToUni != null) {
|
||||||
|
for (Map.Entry<Integer, Character> entry : ebcToUni.entrySet()) {
|
||||||
|
if (entry.getKey() != null && entry.getValue() != null) {
|
||||||
|
setCustomOverride(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (uniToEbc != null) {
|
||||||
|
for (Map.Entry<Character, Integer> entry : uniToEbc.entrySet()) {
|
||||||
|
if (entry.getKey() != null && entry.getValue() != null) {
|
||||||
|
setCustomUnicodeToEbcdicOverride(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translate EBCDIC byte to Unicode character.
|
* Remove custom translation override for a specific EBCDIC byte.
|
||||||
|
*/
|
||||||
|
public synchronized void removeCustomOverride(int ebcdicByte) {
|
||||||
|
Character removed = customEbcdicToUnicode.remove(ebcdicByte & 0xFF);
|
||||||
|
if (removed != null) {
|
||||||
|
customUnicodeToEbcdic.remove(removed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove custom translation override for a specific Unicode character.
|
||||||
|
*/
|
||||||
|
public synchronized void removeCustomUnicodeOverride(char unicodeChar) {
|
||||||
|
Integer removed = customUnicodeToEbcdic.remove(unicodeChar);
|
||||||
|
if (removed != null) {
|
||||||
|
customEbcdicToUnicode.remove(removed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all registered custom translation overrides.
|
||||||
|
*/
|
||||||
|
public synchronized void clearCustomOverrides() {
|
||||||
|
customEbcdicToUnicode.clear();
|
||||||
|
customUnicodeToEbcdic.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if any custom translation overrides are active.
|
||||||
|
*/
|
||||||
|
public boolean hasCustomOverrides() {
|
||||||
|
return !customEbcdicToUnicode.isEmpty() || !customUnicodeToEbcdic.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an unmodifiable view of active EBCDIC-to-Unicode custom overrides.
|
||||||
|
*/
|
||||||
|
public Map<Integer, Character> getCustomEbcdicToUnicodeOverrides() {
|
||||||
|
return Collections.unmodifiableMap(new HashMap<>(customEbcdicToUnicode));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an unmodifiable view of active Unicode-to-EBCDIC custom overrides.
|
||||||
|
*/
|
||||||
|
public Map<Character, Integer> getCustomUnicodeToEbcdicOverrides() {
|
||||||
|
return Collections.unmodifiableMap(new HashMap<>(customUnicodeToEbcdic));
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// Translation Operations
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate double-byte EBCDIC pair (b1, b2) to Unicode.
|
||||||
|
*/
|
||||||
|
public synchronized char dbcsToUnicode(int b1, int b2) {
|
||||||
|
return activeCodePage.dbcsToUnicode(b1, b2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate Unicode character to double-byte EBCDIC ((b1 << 8) | b2).
|
||||||
|
*/
|
||||||
|
public synchronized int unicodeToDbcs(char unicode) {
|
||||||
|
return activeCodePage.unicodeToDbcs(unicode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate EBCDIC byte to Unicode character using active code page and custom overrides.
|
||||||
*/
|
*/
|
||||||
public char ebcdicToUnicode(int ebc) {
|
public char ebcdicToUnicode(int ebc) {
|
||||||
return (char) CP037_TO_UNICODE[ebc & 0xFF];
|
int b = ebc & 0xFF;
|
||||||
|
Character custom = customEbcdicToUnicode.get(b);
|
||||||
|
if (custom != null) {
|
||||||
|
return custom;
|
||||||
|
}
|
||||||
|
return activeCodePage.ebcdicToUnicode(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static helper to translate EBCDIC byte to Unicode character.
|
* Static helper to translate EBCDIC byte to Unicode character (using standard CP037).
|
||||||
*/
|
*/
|
||||||
public static char toUnicode(int ebc) {
|
public static char toUnicode(int ebc) {
|
||||||
return (char) CP037_TO_UNICODE[ebc & 0xFF];
|
return (char) CP037_TO_UNICODE[ebc & 0xFF];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static helper to translate EBCDIC byte to ASCII character.
|
* Static helper to translate EBCDIC byte to ASCII character (using standard CP037).
|
||||||
*/
|
*/
|
||||||
public static char ebcdicToAscii(int ebc) {
|
public static char ebcdicToAscii(int ebc) {
|
||||||
return toUnicode(ebc);
|
return toUnicode(ebc);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translate Unicode character to EBCDIC byte.
|
* Translate Unicode character to EBCDIC byte using active code page and custom overrides.
|
||||||
* Returns -1 if the character cannot be mapped.
|
* Returns -1 if the character cannot be mapped.
|
||||||
*/
|
*/
|
||||||
public int unicodeToEbcdic(char unicode) {
|
public int unicodeToEbcdic(char unicode) {
|
||||||
if (unicode < 256) {
|
Integer custom = customUnicodeToEbcdic.get(unicode);
|
||||||
return UNICODE_TO_CP037[unicode];
|
if (custom != null) {
|
||||||
|
return custom;
|
||||||
}
|
}
|
||||||
return -1;
|
return activeCodePage.unicodeToEbcdic(unicode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -119,24 +252,140 @@ public class EbcdicTranslator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translate a byte array from EBCDIC to a Unicode string.
|
* Translate a byte array from EBCDIC to a Unicode string using active code page and overrides.
|
||||||
*/
|
*/
|
||||||
public String ebcdicToString(byte[] ebcdic, int offset, int length) {
|
public String ebcdicToString(byte[] ebcdic, int offset, int length) {
|
||||||
StringBuilder sb = new StringBuilder(length);
|
if (ebcdic == null || length <= 0) return "";
|
||||||
|
if (hasCustomOverrides() || !(activeCodePage instanceof AbstractDBCSCodePage)) {
|
||||||
|
char[] chars = new char[length];
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
sb.append(ebcdicToUnicode(ebcdic[offset + i] & 0xFF));
|
chars[i] = ebcdicToUnicode(ebcdic[offset + i]);
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return new String(chars);
|
||||||
|
}
|
||||||
|
return activeCodePage.ebcdicToString(ebcdic, offset, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translate a Unicode string to EBCDIC byte array.
|
* Translate a Unicode string to EBCDIC byte array using active code page and overrides.
|
||||||
*/
|
*/
|
||||||
public byte[] stringToEbcdic(String s) {
|
public byte[] stringToEbcdic(String s) {
|
||||||
byte[] result = new byte[s.length()];
|
if (s == null || s.isEmpty()) return new byte[0];
|
||||||
|
if (hasCustomOverrides() || !(activeCodePage instanceof AbstractDBCSCodePage)) {
|
||||||
|
byte[] bytes = new byte[s.length()];
|
||||||
for (int i = 0; i < s.length(); i++) {
|
for (int i = 0; i < s.length(); i++) {
|
||||||
result[i] = unicodeToEbcdicSafe(s.charAt(i));
|
bytes[i] = unicodeToEbcdicSafe(s.charAt(i));
|
||||||
}
|
}
|
||||||
return result;
|
return bytes;
|
||||||
|
}
|
||||||
|
return activeCodePage.stringToEbcdic(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an array of all registered code page ID strings.
|
||||||
|
*/
|
||||||
|
public static String[] getAvailableCodePages() {
|
||||||
|
return CodePageRegistry.getAvailableCodePageIds();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a list of all registered CodePage instances.
|
||||||
|
*/
|
||||||
|
public static List<CodePage> getAllCodePages() {
|
||||||
|
return CodePageRegistry.getAvailableCodePages();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map an IBM 3270 APL / Graphic Escape (GE) EBCDIC code point to its Unicode glyph.
|
||||||
|
* Conforms to IBM 3270 APL / Text character set and GA23-0059 specification.
|
||||||
|
*/
|
||||||
|
public char mapAPL(int ebcdicCodePoint) {
|
||||||
|
switch (ebcdicCodePoint & 0xFF) {
|
||||||
|
// Standard Box-Drawing Lines, Corners, T-Junctions, and Crosses
|
||||||
|
case 0xA2: return '\u2500'; // Horizontal Line 's' -> '─'
|
||||||
|
case 0x85: return '\u2502'; // Vertical Line 'e' -> '│'
|
||||||
|
case 0xC5: return '\u250C'; // Top Left 'E' -> '┌'
|
||||||
|
case 0xD5: return '\u2510'; // Top Right 'N' -> '┐'
|
||||||
|
case 0xC4: return '\u2514'; // Bottom Left 'D' -> '└'
|
||||||
|
case 0xD4: return '\u2518'; // Bottom Right 'M' -> '┘'
|
||||||
|
case 0xC6: return '\u251C'; // T-Junction Left 'F' -> '├'
|
||||||
|
case 0xD6: return '\u2524'; // T-Junction Right 'O' -> '┤'
|
||||||
|
case 0xC7: return '\u252C'; // T-Junction Top 'G' -> '┬'
|
||||||
|
case 0xD7: return '\u2534'; // T-Junction Bottom 'P' -> '┴'
|
||||||
|
case 0xCB: return '\u253C'; // Cross -> '┼'
|
||||||
|
|
||||||
|
// Mathematical Relations and Punctuation
|
||||||
|
case 0x8C: return '\u2264'; // Less-than or equal '≤'
|
||||||
|
case 0xAE: return '\u2265'; // Greater-than or equal '≥'
|
||||||
|
case 0xBE: return '\u2260'; // Not equal '≠'
|
||||||
|
case 0xAD: return '['; // Left bracket
|
||||||
|
case 0xBD: return ']'; // Right bracket
|
||||||
|
case 0x8D: return '{'; // Left brace
|
||||||
|
case 0x9D: return '}'; // Right brace
|
||||||
|
case 0xB0: return '\u00B0'; // Degree '°'
|
||||||
|
case 0xB1: return '\u00B1'; // Plus-minus '±'
|
||||||
|
case 0xB2: return '\u00B2'; // Superscript 2 '²'
|
||||||
|
case 0xB3: return '\u00B3'; // Superscript 3 '³'
|
||||||
|
case 0xAF: return '\u00AF'; // Overbar / High Minus '¯'
|
||||||
|
case 0xBA: return '\u03A9'; // Omega 'Ω'
|
||||||
|
case 0xBF: return '\u00B5'; // Micro 'µ'
|
||||||
|
case 0x5F: return '\u00AC'; // Not sign '¬'
|
||||||
|
|
||||||
|
// IBM 3270 APL Operational & Structural Glyphs
|
||||||
|
case 0x80: return '\u22C4'; // Diamond '⋄'
|
||||||
|
case 0x81: return '\u237A'; // APL Alpha '⍺'
|
||||||
|
case 0x82: return '\u22A5'; // Up Tack / Decode '⊥'
|
||||||
|
case 0x83: return '\u2229'; // Intersection '∩'
|
||||||
|
case 0x84: return '\u230A'; // Floor '⌊'
|
||||||
|
case 0x86: return '\u2286'; // Subset or equal '⊆'
|
||||||
|
case 0x87: return '\u2207'; // Del / Grad '∇'
|
||||||
|
case 0x88: return '\u2206'; // Delta '∆'
|
||||||
|
case 0x89: return '\u2373'; // Iota '⍳'
|
||||||
|
case 0x8A: return '\u2192'; // Right Arrow '→'
|
||||||
|
case 0x8B: return '\u235E'; // Quote Quad '⍞'
|
||||||
|
case 0x8E: return '\u00D7'; // Multiply '×'
|
||||||
|
case 0x8F: return '\u00F7'; // Divide '÷'
|
||||||
|
case 0x90: return '\u235F'; // Circle Star / Log '⍟'
|
||||||
|
case 0x91: return '\u2339'; // Quad Divide / Domino '⌹'
|
||||||
|
case 0x92: return '\u22A4'; // Down Tack / Encode '⊤'
|
||||||
|
case 0x93: return '\u222A'; // Union '∪'
|
||||||
|
case 0x94: return '\u2308'; // Ceiling '⌈'
|
||||||
|
case 0x95: return '\u2374'; // Rho / Shape '⍴'
|
||||||
|
case 0x96: return '\u2375'; // APL Omega '⍵'
|
||||||
|
case 0x97: return '\u2260'; // Not equal '≠'
|
||||||
|
case 0x98: return '\u2377'; // Epsilon Underbar '⍷'
|
||||||
|
case 0x99: return '\u25CB'; // Circle '○'
|
||||||
|
case 0x9A: return '\u2190'; // Left Arrow '←'
|
||||||
|
case 0x9B: return '\u2359'; // Delta Underbar '⍙'
|
||||||
|
case 0x9C: return '\u234B'; // Grade Up '⍋'
|
||||||
|
case 0x9E: return '\u2352'; // Grade Down '⍒'
|
||||||
|
case 0x9F: return '\u235D'; // Lamp / Comment '⍝'
|
||||||
|
case 0xA1: return '\u00A8'; // Diaeresis '¨'
|
||||||
|
case 0xA4: return '\u2336'; // I-Beam '⌶'
|
||||||
|
case 0xA5: return '\u2355'; // Thorn / Format '⍕'
|
||||||
|
case 0xA6: return '\u2282'; // Left Shoe / Enclose '⊂'
|
||||||
|
case 0xA7: return '\u2283'; // Right Shoe / Disclose '⊃'
|
||||||
|
case 0xA8: return '\u2191'; // Up Arrow / Take '↑'
|
||||||
|
case 0xA9: return '\u2193'; // Down Arrow / Drop '↓'
|
||||||
|
case 0xAA: return '\u2395'; // Quad '⎕'
|
||||||
|
case 0xAB: return '\u234E'; // Execute / Hydra '⍎'
|
||||||
|
case 0xAC: return '\u2349'; // Transpose / Circle Slope '⍉'
|
||||||
|
case 0xB4: return '\u2296'; // Circle Bar / Reverse '⊖'
|
||||||
|
case 0xB5: return '\u236A'; // Comma Bar / Table '⍪'
|
||||||
|
case 0xB6: return '\u236B'; // Del Tilde '⍫'
|
||||||
|
case 0xB7: return '\u236C'; // Zilde '⍬'
|
||||||
|
case 0xB9: return '\u233F'; // Slash Bar '⌿'
|
||||||
|
case 0xBB: return '\u2340'; // Backslash Bar '⍀'
|
||||||
|
case 0xBC: return '\u2338'; // Quad Equal '⌸'
|
||||||
|
|
||||||
|
default: return ebcdicToUnicode(ebcdicCodePoint & 0xFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translate an IBM 3270 Graphic Escape (GE) / APL character code to Unicode.
|
||||||
|
*/
|
||||||
|
public char getAplGraphic(int ec) {
|
||||||
|
return mapAPL(ec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+597
-116
@@ -4,6 +4,7 @@ import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
|||||||
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
|
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
|
||||||
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||||
import haus.nightmare.lib3270j.listener.ScreenUpdateListener;
|
import haus.nightmare.lib3270j.listener.ScreenUpdateListener;
|
||||||
|
import haus.nightmare.lib3270j.protocol.DS3270Constants;
|
||||||
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@@ -41,7 +42,7 @@ public class DataStreamProcessor {
|
|||||||
|
|
||||||
// Graphics & Programmed Symbols
|
// Graphics & Programmed Symbols
|
||||||
private final haus.nightmare.lib3270j.graphics.ProgramSymbolManager programSymbolManager = new haus.nightmare.lib3270j.graphics.ProgramSymbolManager();
|
private final haus.nightmare.lib3270j.graphics.ProgramSymbolManager programSymbolManager = new haus.nightmare.lib3270j.graphics.ProgramSymbolManager();
|
||||||
private final haus.nightmare.lib3270j.graphics.GraphicsPlane graphicsPlane = new haus.nightmare.lib3270j.graphics.GraphicsPlane(800, 600);
|
private final haus.nightmare.lib3270j.graphics.GraphicsPlane graphicsPlane = new haus.nightmare.lib3270j.graphics.GraphicsPlane(720, 384);
|
||||||
private final haus.nightmare.lib3270j.graphics.GocaDecoder gocaDecoder = new haus.nightmare.lib3270j.graphics.GocaDecoder(graphicsPlane);
|
private final haus.nightmare.lib3270j.graphics.GocaDecoder gocaDecoder = new haus.nightmare.lib3270j.graphics.GocaDecoder(graphicsPlane);
|
||||||
private final java.io.ByteArrayOutputStream gocaAccumulator = new java.io.ByteArrayOutputStream();
|
private final java.io.ByteArrayOutputStream gocaAccumulator = new java.io.ByteArrayOutputStream();
|
||||||
private int currentGocaSubtype = 0;
|
private int currentGocaSubtype = 0;
|
||||||
@@ -86,6 +87,16 @@ public class DataStreamProcessor {
|
|||||||
this.ftDft = ftDft;
|
this.ftDft = ftDft;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private haus.nightmare.lib3270j.printer.PrintSCS3270 embeddedScsProcessor;
|
||||||
|
|
||||||
|
public void setEmbeddedScsProcessor(haus.nightmare.lib3270j.printer.PrintSCS3270 scsProcessor) {
|
||||||
|
this.embeddedScsProcessor = scsProcessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public haus.nightmare.lib3270j.printer.PrintSCS3270 getEmbeddedScsProcessor() {
|
||||||
|
return embeddedScsProcessor;
|
||||||
|
}
|
||||||
|
|
||||||
public void setInputProcessor(haus.nightmare.lib3270j.input.InputProcessor inputProcessor) {
|
public void setInputProcessor(haus.nightmare.lib3270j.input.InputProcessor inputProcessor) {
|
||||||
this.inputProcessor = inputProcessor;
|
this.inputProcessor = inputProcessor;
|
||||||
}
|
}
|
||||||
@@ -98,6 +109,10 @@ public class DataStreamProcessor {
|
|||||||
screenListeners.add(l);
|
screenListeners.add(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void removeScreenUpdateListener(ScreenUpdateListener l) {
|
||||||
|
screenListeners.remove(l);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process a 3270 data stream record.
|
* Process a 3270 data stream record.
|
||||||
*
|
*
|
||||||
@@ -133,6 +148,9 @@ public class DataStreamProcessor {
|
|||||||
int oldCols = screen.getCols();
|
int oldCols = screen.getCols();
|
||||||
screen.erase(false);
|
screen.erase(false);
|
||||||
graphicsPlane.clear();
|
graphicsPlane.clear();
|
||||||
|
if (gocaDecoder != null) {
|
||||||
|
gocaDecoder.setGraphicsCursorActive(false);
|
||||||
|
}
|
||||||
processWrite(data, offset, length, true);
|
processWrite(data, offset, length, true);
|
||||||
if (oldRows != screen.getRows() || oldCols != screen.getCols()) {
|
if (oldRows != screen.getRows() || oldCols != screen.getCols()) {
|
||||||
notifyScreenSizeChanged();
|
notifyScreenSizeChanged();
|
||||||
@@ -150,6 +168,9 @@ public class DataStreamProcessor {
|
|||||||
int oldCols = screen.getCols();
|
int oldCols = screen.getCols();
|
||||||
screen.erase(true);
|
screen.erase(true);
|
||||||
graphicsPlane.clear();
|
graphicsPlane.clear();
|
||||||
|
if (gocaDecoder != null) {
|
||||||
|
gocaDecoder.setGraphicsCursorActive(false);
|
||||||
|
}
|
||||||
processWrite(data, offset, length, true);
|
processWrite(data, offset, length, true);
|
||||||
if (oldRows != screen.getRows() || oldCols != screen.getCols()) {
|
if (oldRows != screen.getRows() || oldCols != screen.getCols()) {
|
||||||
notifyScreenSizeChanged();
|
notifyScreenSizeChanged();
|
||||||
@@ -499,11 +520,14 @@ public class DataStreamProcessor {
|
|||||||
|
|
||||||
// Handle GE (graphic escape) prefix
|
// Handle GE (graphic escape) prefix
|
||||||
byte fillCs = currentCs;
|
byte fillCs = currentCs;
|
||||||
|
char fillUcs4 = 0;
|
||||||
if (pos + 4 < end && fillChar == ORDER_GE) {
|
if (pos + 4 < end && fillChar == ORDER_GE) {
|
||||||
fillChar = data[pos + 4] & 0xFF;
|
fillChar = data[pos + 4] & 0xFF;
|
||||||
fillCs = CS_GE;
|
fillCs = CS_GE;
|
||||||
|
fillUcs4 = (translator != null) ? translator.mapAPL(fillChar) : (char) fillChar;
|
||||||
pos += 5;
|
pos += 5;
|
||||||
} else {
|
} else {
|
||||||
|
fillUcs4 = (translator != null) ? translator.ebcdicToUnicode(fillChar) : (char) fillChar;
|
||||||
pos += 4;
|
pos += 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,6 +536,7 @@ public class DataStreamProcessor {
|
|||||||
ExtendedAttribute ea = screen.getCell(baddr);
|
ExtendedAttribute ea = screen.getCell(baddr);
|
||||||
ea.fa = 0; // Destroy previous field attribute if any
|
ea.fa = 0; // Destroy previous field attribute if any
|
||||||
ea.ec = (byte) fillChar;
|
ea.ec = (byte) fillChar;
|
||||||
|
ea.ucs4 = fillUcs4;
|
||||||
ea.fg = currentFg;
|
ea.fg = currentFg;
|
||||||
ea.bg = currentBg;
|
ea.bg = currentBg;
|
||||||
ea.gr = (byte) currentGr;
|
ea.gr = (byte) currentGr;
|
||||||
@@ -564,6 +589,7 @@ public class DataStreamProcessor {
|
|||||||
ea.fa = 0; // Destroy previous field attribute if any
|
ea.fa = 0; // Destroy previous field attribute if any
|
||||||
ea.ec = (byte) geChar;
|
ea.ec = (byte) geChar;
|
||||||
ea.cs = CS_GE;
|
ea.cs = CS_GE;
|
||||||
|
ea.ucs4 = (translator != null) ? translator.mapAPL(geChar) : (char) geChar;
|
||||||
ea.fg = currentFg;
|
ea.fg = currentFg;
|
||||||
ea.bg = currentBg;
|
ea.bg = currentBg;
|
||||||
ea.gr = (byte) currentGr;
|
ea.gr = (byte) currentGr;
|
||||||
@@ -638,10 +664,21 @@ public class DataStreamProcessor {
|
|||||||
case XA_CHARSET:
|
case XA_CHARSET:
|
||||||
ea.cs = (byte) value;
|
ea.cs = (byte) value;
|
||||||
break;
|
break;
|
||||||
case XA_VALIDATION:
|
|
||||||
case XA_OUTLINING:
|
case XA_OUTLINING:
|
||||||
|
case 0x84: // HoD / 3270 Outlining alternative ID
|
||||||
|
ea.ol = (byte) value;
|
||||||
|
break;
|
||||||
|
case XA_VALIDATION:
|
||||||
|
ea.vl = (byte) value;
|
||||||
|
break;
|
||||||
|
case XA_TRANSPARENCY:
|
||||||
|
ea.tr = (byte) value;
|
||||||
|
break;
|
||||||
case XA_INPUT_CONTROL:
|
case XA_INPUT_CONTROL:
|
||||||
// Acknowledged but not visually rendered yet
|
ea.ic = (byte) value;
|
||||||
|
break;
|
||||||
|
case 0x91: // DBCS Asian attributes
|
||||||
|
ea.db = (byte) value;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -658,7 +695,7 @@ public class DataStreamProcessor {
|
|||||||
|
|
||||||
// ========== Read Buffer ==========
|
// ========== Read Buffer ==========
|
||||||
|
|
||||||
private void processReadBuffer() {
|
public void processReadBuffer() {
|
||||||
outputPos = 0;
|
outputPos = 0;
|
||||||
|
|
||||||
int size = screen.getRows() * screen.getCols();
|
int size = screen.getRows() * screen.getCols();
|
||||||
@@ -677,7 +714,71 @@ public class DataStreamProcessor {
|
|||||||
outputWrite(caddr[0] & 0xFF);
|
outputWrite(caddr[0] & 0xFF);
|
||||||
outputWrite(caddr[1] & 0xFF);
|
outputWrite(caddr[1] & 0xFF);
|
||||||
|
|
||||||
// Buffer contents
|
byte mode = screen.getReplyMode();
|
||||||
|
|
||||||
|
// Buffer contents depending on Reply Mode
|
||||||
|
if (mode == SF_SRM_XFIELD || mode == SF_SRM_CHAR) {
|
||||||
|
// Extended Field or Character Mode
|
||||||
|
byte curFg = 0, curBg = 0, curGr = 0, curCs = 0;
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
ExtendedAttribute ea = screen.getCell(i);
|
||||||
|
if (ea.isFieldAttribute()) {
|
||||||
|
// Count number of attribute pairs
|
||||||
|
int count = 1; // 3270 FA is always present
|
||||||
|
if (ea.fg != 0) count++;
|
||||||
|
if (ea.bg != 0) count++;
|
||||||
|
if (ea.gr != 0) count++;
|
||||||
|
if (ea.cs != 0) count++;
|
||||||
|
if (ea.ol != 0) count++;
|
||||||
|
|
||||||
|
outputWrite(ORDER_SFE);
|
||||||
|
outputWrite(count);
|
||||||
|
outputWrite(XA_3270);
|
||||||
|
outputWrite(ea.fa & 0xFF);
|
||||||
|
if (ea.fg != 0) { outputWrite(XA_FOREGROUND); outputWrite(ea.fg & 0xFF); }
|
||||||
|
if (ea.bg != 0) { outputWrite(XA_BACKGROUND); outputWrite(ea.bg & 0xFF); }
|
||||||
|
if (ea.gr != 0) {
|
||||||
|
outputWrite(XA_HIGHLIGHTING);
|
||||||
|
int xah = XAH_NORMAL;
|
||||||
|
if ((ea.gr & GR_BLINK) != 0) xah = XAH_BLINK;
|
||||||
|
else if ((ea.gr & GR_REVERSE) != 0) xah = XAH_REVERSE;
|
||||||
|
else if ((ea.gr & GR_UNDERLINE) != 0) xah = XAH_UNDERSCORE;
|
||||||
|
else if ((ea.gr & GR_INTENSIFY) != 0) xah = XAH_INTENSIFY;
|
||||||
|
outputWrite(xah);
|
||||||
|
}
|
||||||
|
if (ea.cs != 0) { outputWrite(XA_CHARSET); outputWrite(ea.cs & 0xFF); }
|
||||||
|
if (ea.ol != 0) { outputWrite(XA_OUTLINING); outputWrite(ea.ol & 0xFF); }
|
||||||
|
} else {
|
||||||
|
if (mode == SF_SRM_CHAR) {
|
||||||
|
// In character mode, output SA if character attributes differ
|
||||||
|
if (ea.fg != curFg) {
|
||||||
|
outputWrite(ORDER_SA); outputWrite(XA_FOREGROUND); outputWrite(ea.fg & 0xFF);
|
||||||
|
curFg = ea.fg;
|
||||||
|
}
|
||||||
|
if (ea.bg != curBg) {
|
||||||
|
outputWrite(ORDER_SA); outputWrite(XA_BACKGROUND); outputWrite(ea.bg & 0xFF);
|
||||||
|
curBg = ea.bg;
|
||||||
|
}
|
||||||
|
if (ea.gr != curGr) {
|
||||||
|
outputWrite(ORDER_SA); outputWrite(XA_HIGHLIGHTING);
|
||||||
|
int xah = XAH_NORMAL;
|
||||||
|
if ((ea.gr & GR_BLINK) != 0) xah = XAH_BLINK;
|
||||||
|
else if ((ea.gr & GR_REVERSE) != 0) xah = XAH_REVERSE;
|
||||||
|
else if ((ea.gr & GR_UNDERLINE) != 0) xah = XAH_UNDERSCORE;
|
||||||
|
else if ((ea.gr & GR_INTENSIFY) != 0) xah = XAH_INTENSIFY;
|
||||||
|
outputWrite(xah);
|
||||||
|
curGr = ea.gr;
|
||||||
|
}
|
||||||
|
if (ea.cs != curCs) {
|
||||||
|
outputWrite(ORDER_SA); outputWrite(XA_CHARSET); outputWrite(ea.cs & 0xFF);
|
||||||
|
curCs = ea.cs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outputWrite(ea.ec & 0xFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Standard Field Mode (SF_SRM_FIELD)
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
ExtendedAttribute ea = screen.getCell(i);
|
ExtendedAttribute ea = screen.getCell(i);
|
||||||
if (ea.isFieldAttribute()) {
|
if (ea.isFieldAttribute()) {
|
||||||
@@ -687,15 +788,26 @@ public class DataStreamProcessor {
|
|||||||
outputWrite(ea.ec & 0xFF);
|
outputWrite(ea.ec & 0xFF);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sendOutput();
|
sendOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Read Modified ==========
|
// ========== Read Modified ==========
|
||||||
|
|
||||||
private void processReadModified(boolean all) {
|
public void processReadModified(boolean all) {
|
||||||
if (ftDft != null) {
|
if (ftDft != null && ftDft.readModified()) {
|
||||||
ftDft.readModified();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inputProcessor != null) {
|
||||||
|
int aid = (inputProcessor.getLastAid() != 0) ? inputProcessor.getLastAid() : AID_NO;
|
||||||
|
inputProcessor.setLastAid(AID_NO);
|
||||||
|
byte[] data = inputProcessor.buildReadModifiedInboundData(aid, all);
|
||||||
|
if (outputSender != null) {
|
||||||
|
outputSender.send3270Data(data);
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
outputPos = 0;
|
outputPos = 0;
|
||||||
@@ -726,7 +838,7 @@ public class DataStreamProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Formatted screen: send modified fields with null suppression per 3270 spec
|
// Formatted screen: send fields with null suppression per 3270 spec
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
ExtendedAttribute ea = screen.getCell(i);
|
ExtendedAttribute ea = screen.getCell(i);
|
||||||
if (ea.isFieldAttribute() && (all || faIsModified(ea.fa & 0xFF))) {
|
if (ea.isFieldAttribute() && (all || faIsModified(ea.fa & 0xFF))) {
|
||||||
@@ -756,10 +868,11 @@ public class DataStreamProcessor {
|
|||||||
sendOutput();
|
sendOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Write Structured Field ==========
|
public void processWriteStructuredField(byte[] data, int offset, int length) {
|
||||||
|
if (data == null || length <= 0)
|
||||||
private void processWriteStructuredField(byte[] data, int offset, int length) {
|
return;
|
||||||
int pos = offset + 1; // Skip WSF command byte
|
int firstByte = data[offset] & 0xFF;
|
||||||
|
int pos = (firstByte == CMD_WSF || firstByte == SNA_CMD_WSF) ? offset + 1 : offset;
|
||||||
int end = offset + length;
|
int end = offset + length;
|
||||||
|
|
||||||
while (pos < end) {
|
while (pos < end) {
|
||||||
@@ -779,35 +892,34 @@ public class DataStreamProcessor {
|
|||||||
case SF_READ_PART:
|
case SF_READ_PART:
|
||||||
processSFReadPartition(data, pos, fieldLen);
|
processSFReadPartition(data, pos, fieldLen);
|
||||||
break;
|
break;
|
||||||
case SF_ERASE_RESET: {
|
case SF_ERASE_RESET:
|
||||||
boolean alt = (fieldLen >= 4) && ((data[pos + 3] & 0xFF) == SF_ER_ALT);
|
processEraseReset(data, pos, fieldLen);
|
||||||
screen.erase(alt);
|
|
||||||
graphicsPlane.clear();
|
|
||||||
gocaDecoder.resetDefaults();
|
|
||||||
notifyScreenSizeChanged();
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
case SF_SET_REPLY_MODE:
|
case SF_SET_REPLY_MODE:
|
||||||
if (fieldLen >= 5) {
|
processSetReplyMode(data, pos, fieldLen);
|
||||||
screen.setReplyMode((byte) (data[pos + 4] & 0xFF));
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case SF_CREATE_PART:
|
case SF_CREATE_PART:
|
||||||
if (fieldLen >= 4) {
|
processCreatePartition(data, pos, fieldLen);
|
||||||
int pid = data[pos + 3] & 0xFF;
|
|
||||||
screen.setActivePartition(pid);
|
|
||||||
log.fine("Created active partition ID=" + pid);
|
|
||||||
}
|
|
||||||
graphicsPlane.clear();
|
|
||||||
gocaDecoder.resetDefaults();
|
|
||||||
break;
|
break;
|
||||||
case SF_OUTBOUND_DS:
|
case SF_DESTROY_PART:
|
||||||
if (fieldLen > 5) {
|
processDestroyPartition(data, pos, fieldLen);
|
||||||
// Outbound DS contains another 3270 command
|
|
||||||
processRecord(data, pos + 4, fieldLen - 4, false);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case SF_TRANSFER_DATA:
|
case SF_ACTIVATE_PART:
|
||||||
|
processActivatePartition(data, pos, fieldLen);
|
||||||
|
break;
|
||||||
|
case SF_SET_WINDOW: // 0x0F: Set Window / Modify Partition / 3270 Graphics Viewport
|
||||||
|
processSFSetWindow(data, pos, fieldLen);
|
||||||
|
break;
|
||||||
|
case SF_3270_GRAPHICS: // 0x20: 3270 Graphics / Object Control
|
||||||
|
processSFObjectControl(data, pos, fieldLen);
|
||||||
|
break;
|
||||||
|
case SF_DOCUMENT_DATA: // 0x24: Document Data / embedded SCS / Object Control
|
||||||
|
processSFDocumentData(data, pos, fieldLen);
|
||||||
|
break;
|
||||||
|
case SF_OUTBOUND_DS: // 0x40
|
||||||
|
processOutbound3270DS(data, pos, fieldLen);
|
||||||
|
break;
|
||||||
|
case SF_TRANSFER_DATA: // 0xD0
|
||||||
if (ftDft != null) {
|
if (ftDft != null) {
|
||||||
ftDft.processStructuredField(data, pos, fieldLen);
|
ftDft.processStructuredField(data, pos, fieldLen);
|
||||||
} else {
|
} else {
|
||||||
@@ -821,84 +933,7 @@ public class DataStreamProcessor {
|
|||||||
programSymbolManager.loadps(psData);
|
programSymbolManager.loadps(psData);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case haus.nightmare.lib3270j.graphics.GocaConstants.SF_LOADPS: { // 0x0F: 2-byte Structured Field prefix
|
|
||||||
if (fieldLen >= 4) {
|
|
||||||
int sfSubId = data[pos + 3] & 0xFF;
|
|
||||||
switch (sfSubId) {
|
|
||||||
case haus.nightmare.lib3270j.graphics.GocaConstants.SF_LOADPS_SUB: // 0x06: Load Programmed Symbols
|
|
||||||
if (fieldLen > 4) {
|
|
||||||
byte[] psData = new byte[fieldLen - 4];
|
|
||||||
System.arraycopy(data, pos + 4, psData, 0, psData.length);
|
|
||||||
programSymbolManager.loadps(psData);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJCNTL_SUB: // 0x11: Object Control (Procedure orders)
|
|
||||||
case haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJPICT_SUB: // 0x10: Object Picture (Picture segments)
|
|
||||||
case haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJDATA_SUB: { // 0x0F: Object Data (GOCA draw orders)
|
|
||||||
int flags = (fieldLen >= 6) ? (data[pos + 5] & 0xC0) : 0xC0;
|
|
||||||
int orderOffset = (fieldLen >= 7) ? (pos + 7) : (pos + 4);
|
|
||||||
int orderLen = Math.max(0, fieldLen - (orderOffset - pos));
|
|
||||||
|
|
||||||
StringBuilder hexDump = new StringBuilder();
|
|
||||||
for (int i = 0; i < Math.min(32, fieldLen); i++) {
|
|
||||||
hexDump.append(String.format("%02x ", data[pos + i] & 0xFF));
|
|
||||||
}
|
|
||||||
log.info(String.format("SF 0x0F sub=0x%02x len=%d flags=0x%02x orderOffset=+%d orderLen=%d bytes=[%s]",
|
|
||||||
sfSubId, fieldLen, flags, (orderOffset - pos), orderLen, hexDump.toString().trim()));
|
|
||||||
|
|
||||||
graphicsPlane.setScreenDimensions(screen.getCols(), screen.getRows());
|
|
||||||
|
|
||||||
if (flags == 0x80) { // SPAN_FIRST
|
|
||||||
gocaAccumulator.reset();
|
|
||||||
currentGocaSubtype = sfSubId;
|
|
||||||
if (orderLen > 0) {
|
|
||||||
gocaAccumulator.write(data, orderOffset, orderLen);
|
|
||||||
}
|
|
||||||
} else if (flags == 0x00) { // SPAN_MIDDLE
|
|
||||||
if (orderLen > 0) {
|
|
||||||
gocaAccumulator.write(data, orderOffset, orderLen);
|
|
||||||
}
|
|
||||||
} else if (flags == 0x40) { // SPAN_LAST
|
|
||||||
if (orderLen > 0) {
|
|
||||||
gocaAccumulator.write(data, orderOffset, orderLen);
|
|
||||||
}
|
|
||||||
byte[] fullStream = gocaAccumulator.toByteArray();
|
|
||||||
gocaAccumulator.reset();
|
|
||||||
log.info(String.format("GOCA stream SPAN_LAST assembled: %d bytes", fullStream.length));
|
|
||||||
if (currentGocaSubtype == haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJCNTL_SUB) {
|
|
||||||
gocaDecoder.processProcedureOrders(fullStream, 0, fullStream.length);
|
|
||||||
} else {
|
|
||||||
gocaDecoder.decodeStream(fullStream, 0, fullStream.length);
|
|
||||||
}
|
|
||||||
notifyScreenUpdated();
|
|
||||||
} else { // SPAN_ONLY (0xC0)
|
|
||||||
gocaAccumulator.reset();
|
|
||||||
log.info(String.format("GOCA stream SPAN_ONLY: %d bytes", orderLen));
|
|
||||||
if (sfSubId == haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJCNTL_SUB) {
|
|
||||||
gocaDecoder.processProcedureOrders(data, orderOffset, orderLen);
|
|
||||||
} else {
|
|
||||||
gocaDecoder.decodeStream(data, orderOffset, orderLen);
|
|
||||||
}
|
|
||||||
notifyScreenUpdated();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
log.fine("Unknown SF 0x0F subtype: " + String.format("0x%02x", sfSubId));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJCNTL: // 0x24: Object Control (Procedure orders)
|
|
||||||
if (fieldLen > 3) {
|
|
||||||
graphicsPlane.setScreenDimensions(screen.getCols(), screen.getRows());
|
|
||||||
gocaDecoder.processProcedureOrders(data, pos + 3, fieldLen - 3);
|
|
||||||
notifyScreenUpdated();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJDATA: // 0x85: Graphics Object Data / GOCA
|
case haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJDATA: // 0x85: Graphics Object Data / GOCA
|
||||||
case haus.nightmare.lib3270j.graphics.GocaConstants.SF_3270_G: // 0x20: 3270 Graphics
|
|
||||||
if (fieldLen > 3) {
|
if (fieldLen > 3) {
|
||||||
graphicsPlane.setScreenDimensions(screen.getCols(), screen.getRows());
|
graphicsPlane.setScreenDimensions(screen.getCols(), screen.getRows());
|
||||||
gocaDecoder.decodeStream(data, pos + 3, fieldLen - 3);
|
gocaDecoder.decodeStream(data, pos + 3, fieldLen - 3);
|
||||||
@@ -932,7 +967,7 @@ public class DataStreamProcessor {
|
|||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case SF_RP_QUERY:
|
case SF_RP_QUERY:
|
||||||
log.info("ReadPartition Query — sending all query replies");
|
log.info("ReadPartition Query — sending base query replies");
|
||||||
graphicsPlane.clear();
|
graphicsPlane.clear();
|
||||||
gocaDecoder.resetDefaults();
|
gocaDecoder.resetDefaults();
|
||||||
sendAllQueryReplies();
|
sendAllQueryReplies();
|
||||||
@@ -941,7 +976,9 @@ public class DataStreamProcessor {
|
|||||||
if (fieldLen >= 6) {
|
if (fieldLen >= 6) {
|
||||||
int listType = data[offset + 5] & 0xFF;
|
int listType = data[offset + 5] & 0xFF;
|
||||||
log.info("ReadPartition QueryList type=" + String.format("0x%02x", listType));
|
log.info("ReadPartition QueryList type=" + String.format("0x%02x", listType));
|
||||||
if (listType == SF_RPQ_ALL || listType == SF_RPQ_EQUIV) {
|
if (listType == SF_RPQ_ALL) {
|
||||||
|
sendCompleteQueryReplies();
|
||||||
|
} else if (listType == SF_RPQ_EQUIV) {
|
||||||
sendAllQueryReplies();
|
sendAllQueryReplies();
|
||||||
} else if (listType == SF_RPQ_LIST) {
|
} else if (listType == SF_RPQ_LIST) {
|
||||||
// Send only requested query replies
|
// Send only requested query replies
|
||||||
@@ -983,7 +1020,22 @@ public class DataStreamProcessor {
|
|||||||
if ((i + 1) % 32 == 0)
|
if ((i + 1) % 32 == 0)
|
||||||
sb.append("\n ");
|
sb.append("\n ");
|
||||||
}
|
}
|
||||||
log.warning(">>> SENDING Query Reply (" + qr.length + " bytes):\n " + sb.toString().trim());
|
log.warning(">>> SENDING Base Query Reply (" + qr.length + " bytes):\n " + sb.toString().trim());
|
||||||
|
if (outputSender != null) {
|
||||||
|
outputSender.send3270Data(qr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendCompleteQueryReplies() {
|
||||||
|
byte[] qr = qrBuilder.buildCompleteQueryReplies(screen.getMaxCols(), screen.getMaxRows(),
|
||||||
|
screen.getMaxCols() * screen.getMaxRows());
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < qr.length; i++) {
|
||||||
|
sb.append(String.format("%02x ", qr[i] & 0xFF));
|
||||||
|
if ((i + 1) % 32 == 0)
|
||||||
|
sb.append("\n ");
|
||||||
|
}
|
||||||
|
log.warning(">>> SENDING Complete Query Reply (" + qr.length + " bytes):\n " + sb.toString().trim());
|
||||||
if (outputSender != null) {
|
if (outputSender != null) {
|
||||||
outputSender.send3270Data(qr);
|
outputSender.send3270Data(qr);
|
||||||
}
|
}
|
||||||
@@ -1035,4 +1087,433 @@ public class DataStreamProcessor {
|
|||||||
l.onScreenUpdated();
|
l.onScreenUpdated();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== Structured Field Handlers & Utilities (Phase 2) ==========
|
||||||
|
|
||||||
|
public void processSetReplyMode(byte[] data, int offset, int length) {
|
||||||
|
if (length >= 5) {
|
||||||
|
byte mode = (byte) (data[offset + 4] & 0xFF);
|
||||||
|
screen.setReplyMode(mode);
|
||||||
|
log.fine("Set reply mode: " + mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processCreatePartition(byte[] data, int offset, int length) {
|
||||||
|
if (length >= 4) {
|
||||||
|
int pid = data[offset + 3] & 0xFF;
|
||||||
|
int pRows = screen.getRows();
|
||||||
|
int pCols = screen.getCols();
|
||||||
|
if (length >= 8) {
|
||||||
|
pCols = ((data[offset + 4] & 0xFF) << 8) | (data[offset + 5] & 0xFF);
|
||||||
|
pRows = ((data[offset + 6] & 0xFF) << 8) | (data[offset + 7] & 0xFF);
|
||||||
|
if (pRows <= 0) pRows = screen.getRows();
|
||||||
|
if (pCols <= 0) pCols = screen.getCols();
|
||||||
|
}
|
||||||
|
screen.createPartition(pid, pRows, pCols);
|
||||||
|
log.fine("Created partition pid=" + pid + " (" + pRows + "x" + pCols + ")");
|
||||||
|
}
|
||||||
|
graphicsPlane.clear();
|
||||||
|
gocaDecoder.resetDefaults();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processDestroyPartition(byte[] data, int offset, int length) {
|
||||||
|
if (length >= 4) {
|
||||||
|
int pid = data[offset + 3] & 0xFF;
|
||||||
|
screen.destroyPartition(pid);
|
||||||
|
log.fine("Destroyed partition pid=" + pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processActivatePartition(byte[] data, int offset, int length) {
|
||||||
|
if (length >= 4) {
|
||||||
|
int pid = data[offset + 3] & 0xFF;
|
||||||
|
screen.activatePartition(pid);
|
||||||
|
log.fine("Activated partition pid=" + pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processEraseReset(byte[] data, int offset, int length) {
|
||||||
|
boolean alt = (length >= 4) && ((data[offset + 3] & 0xFF) == SF_ER_ALT);
|
||||||
|
screen.eraseReset(alt);
|
||||||
|
graphicsPlane.clear();
|
||||||
|
gocaDecoder.resetDefaults();
|
||||||
|
notifyScreenSizeChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processSCSData(byte[] data, int offset, int length) {
|
||||||
|
log.info("Received embedded SCS printer data (" + length + " bytes)");
|
||||||
|
if (embeddedScsProcessor != null && data != null && length > 0) {
|
||||||
|
embeddedScsProcessor.processHostData(data, offset, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScreenToBindSize(int primaryRows, int primaryCols, int altRows, int altCols, int bindFlags) {
|
||||||
|
screen.setScreenToBindSize(primaryRows, primaryCols, altRows, altCols, bindFlags);
|
||||||
|
notifyScreenSizeChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScrSizetoDefault(boolean isDefault) {
|
||||||
|
screen.setScrSizetoDefault(isDefault);
|
||||||
|
notifyScreenSizeChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] doubleFF(byte[] data, int length) {
|
||||||
|
int count = countFF(data, length);
|
||||||
|
if (count == 0 && data.length == length) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
byte[] result = new byte[length + count];
|
||||||
|
int j = 0;
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
byte b = data[i];
|
||||||
|
result[j++] = b;
|
||||||
|
if ((b & 0xFF) == 0xFF) {
|
||||||
|
result[j++] = (byte) 0xFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int countFF(byte[] data, int length) {
|
||||||
|
int count = 0;
|
||||||
|
int len = Math.min(length, data.length);
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
if ((data[i] & 0xFF) == 0xFF) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] sizeuparray(byte[] data, int count) {
|
||||||
|
byte[] newArr = new byte[data.length + count];
|
||||||
|
System.arraycopy(data, 0, newArr, 0, Math.min(data.length, newArr.length));
|
||||||
|
return newArr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int cmd2ebc(int commandCode) {
|
||||||
|
return DS3270Constants.cmd2ebc(commandCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int cmd2ebc(short commandCode) {
|
||||||
|
return DS3270Constants.cmd2ebc(commandCode & 0xFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int x2bin(int ebcByte) {
|
||||||
|
return DS3270Constants.x2bin(ebcByte);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int x2bin(short ebcByte) {
|
||||||
|
return DS3270Constants.x2bin(ebcByte & 0xFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean validateStructuredFieldHeader(byte[] data, int offset, int length) {
|
||||||
|
if (length < 3) return false;
|
||||||
|
int fieldLen = ((data[offset] & 0xFF) << 8) | (data[offset + 1] & 0xFF);
|
||||||
|
return fieldLen >= 3 && fieldLen <= length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int decodeAddressingMode(int flags) {
|
||||||
|
return (flags & 0x01) != 0 ? 14 : 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean validatePartitionId(int pid) {
|
||||||
|
return pid >= 0 && pid <= 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Missing Structured Field Handlers (Phase 2) ==========
|
||||||
|
|
||||||
|
public void processSFSetWindow(byte[] data, int offset, int fieldLen) {
|
||||||
|
if (fieldLen >= 4) {
|
||||||
|
int sfSubId = data[offset + 3] & 0xFF;
|
||||||
|
switch (sfSubId) {
|
||||||
|
case haus.nightmare.lib3270j.graphics.GocaConstants.SF_LOADPS_SUB: // 0x06: Load Programmed Symbols
|
||||||
|
if (fieldLen > 4) {
|
||||||
|
byte[] psData = new byte[fieldLen - 4];
|
||||||
|
System.arraycopy(data, offset + 4, psData, 0, psData.length);
|
||||||
|
programSymbolManager.loadps(psData);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJDATA_SUB: { // 0x0F: Object Control / Data Unit
|
||||||
|
log.info(String.format("SF 0x0F sub=0x0F (Data Unit Object Control len=%d): activating graphics cursor", fieldLen));
|
||||||
|
gocaDecoder.setGraphicsCursorActive(true);
|
||||||
|
notifyScreenUpdated();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJCNTL_SUB: // 0x11: Object Control (Procedure orders)
|
||||||
|
case haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJPICT_SUB: { // 0x10: Object Picture (Picture segments)
|
||||||
|
int flags = (fieldLen >= 6) ? (data[offset + 5] & 0xC0) : 0xC0;
|
||||||
|
int orderOffset = (fieldLen >= 7) ? (offset + 7) : (offset + 4);
|
||||||
|
int orderLen = Math.max(0, fieldLen - (orderOffset - offset));
|
||||||
|
|
||||||
|
graphicsPlane.setScreenDimensions(screen.getCols(), screen.getRows());
|
||||||
|
int targetW = screen.getCols() * 9;
|
||||||
|
int targetH = screen.getRows() * 16;
|
||||||
|
if (graphicsPlane.getCanvasWidth() != targetW || graphicsPlane.getCanvasHeight() != targetH) {
|
||||||
|
graphicsPlane.resize(targetW, targetH);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags == 0x80) { // SPAN_FIRST
|
||||||
|
gocaAccumulator.reset();
|
||||||
|
currentGocaSubtype = sfSubId;
|
||||||
|
if (orderLen > 0) {
|
||||||
|
gocaAccumulator.write(data, orderOffset, orderLen);
|
||||||
|
}
|
||||||
|
} else if (flags == 0x00) { // SPAN_MIDDLE
|
||||||
|
if (orderLen > 0) {
|
||||||
|
gocaAccumulator.write(data, orderOffset, orderLen);
|
||||||
|
}
|
||||||
|
} else if (flags == 0x40) { // SPAN_LAST
|
||||||
|
if (orderLen > 0) {
|
||||||
|
gocaAccumulator.write(data, orderOffset, orderLen);
|
||||||
|
}
|
||||||
|
byte[] fullStream = gocaAccumulator.toByteArray();
|
||||||
|
gocaAccumulator.reset();
|
||||||
|
log.info(String.format("GOCA stream SPAN_LAST assembled: %d bytes", fullStream.length));
|
||||||
|
if (currentGocaSubtype == haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJCNTL_SUB) {
|
||||||
|
gocaDecoder.processProcedureOrders(fullStream, 0, fullStream.length);
|
||||||
|
} else {
|
||||||
|
gocaDecoder.decodeStream(fullStream, 0, fullStream.length);
|
||||||
|
}
|
||||||
|
notifyScreenUpdated();
|
||||||
|
} else { // SPAN_ONLY (0xC0)
|
||||||
|
gocaAccumulator.reset();
|
||||||
|
log.info(String.format("GOCA stream SPAN_ONLY: %d bytes", orderLen));
|
||||||
|
if (sfSubId == haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJCNTL_SUB) {
|
||||||
|
gocaDecoder.processProcedureOrders(data, orderOffset, orderLen);
|
||||||
|
} else {
|
||||||
|
gocaDecoder.decodeStream(data, orderOffset, orderLen);
|
||||||
|
}
|
||||||
|
notifyScreenUpdated();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if (fieldLen >= 11) {
|
||||||
|
int xMin = ((data[offset + 3] & 0xFF) << 8) | (data[offset + 4] & 0xFF);
|
||||||
|
int yMin = ((data[offset + 5] & 0xFF) << 8) | (data[offset + 6] & 0xFF);
|
||||||
|
int xMax = ((data[offset + 7] & 0xFF) << 8) | (data[offset + 8] & 0xFF);
|
||||||
|
int yMax = ((data[offset + 9] & 0xFF) << 8) | (data[offset + 10] & 0xFF);
|
||||||
|
graphicsPlane.setViewingWindow(xMin, yMin, xMax, yMax);
|
||||||
|
}
|
||||||
|
log.fine("SF 0x0F Set Window processed (len=" + fieldLen + ")");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processSFSetWindow(byte[] sf) {
|
||||||
|
if (sf != null && sf.length >= 3) {
|
||||||
|
int off = ((sf[0] & 0xFF) == CMD_WSF || (sf[0] & 0xFF) == SNA_CMD_WSF) ? 1 : 0;
|
||||||
|
processSFSetWindow(sf, off, sf.length - off);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processSFObjectControl(byte[] data, int offset, int fieldLen) {
|
||||||
|
if (fieldLen > 3) {
|
||||||
|
graphicsPlane.setScreenDimensions(screen.getCols(), screen.getRows());
|
||||||
|
gocaDecoder.decodeStream(data, offset + 3, fieldLen - 3);
|
||||||
|
notifyScreenUpdated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processSFObjectControl(byte[] sf) {
|
||||||
|
if (sf != null && sf.length >= 3) {
|
||||||
|
int off = ((sf[0] & 0xFF) == CMD_WSF || (sf[0] & 0xFF) == SNA_CMD_WSF) ? 1 : 0;
|
||||||
|
processSFObjectControl(sf, off, sf.length - off);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processSFDocumentData(byte[] data, int offset, int fieldLen) {
|
||||||
|
if (fieldLen > 3) {
|
||||||
|
if (embeddedScsProcessor != null) {
|
||||||
|
embeddedScsProcessor.processHostData(data, offset + 3, fieldLen - 3);
|
||||||
|
}
|
||||||
|
graphicsPlane.setScreenDimensions(screen.getCols(), screen.getRows());
|
||||||
|
gocaDecoder.processProcedureOrders(data, offset + 3, fieldLen - 3);
|
||||||
|
notifyScreenUpdated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processSFDocumentData(byte[] sf) {
|
||||||
|
if (sf != null && sf.length >= 3) {
|
||||||
|
int off = ((sf[0] & 0xFF) == CMD_WSF || (sf[0] & 0xFF) == SNA_CMD_WSF) ? 1 : 0;
|
||||||
|
processSFDocumentData(sf, off, sf.length - off);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processOutbound3270DS(byte[] data, int offset, int fieldLen) {
|
||||||
|
if (fieldLen >= 4) {
|
||||||
|
int pid = data[offset + 3] & 0xFF;
|
||||||
|
screen.setActivePartition(pid);
|
||||||
|
if (fieldLen > 4) {
|
||||||
|
processRecord(data, offset + 4, fieldLen - 4, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processOutbound3270DS(byte[] sf) {
|
||||||
|
if (sf != null && sf.length >= 4) {
|
||||||
|
int off = ((sf[0] & 0xFF) == CMD_WSF || (sf[0] & 0xFF) == SNA_CMD_WSF) ? 1 : 0;
|
||||||
|
processOutbound3270DS(sf, off, sf.length - off);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Convenience Overloads ==========
|
||||||
|
|
||||||
|
public void processRecord(byte[] data) {
|
||||||
|
if (data != null && data.length > 0) {
|
||||||
|
processRecord(data, 0, data.length, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processWrite(byte[] data) {
|
||||||
|
if (data != null && data.length > 0) {
|
||||||
|
processRecord(data, 0, data.length, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processEraseWrite(byte[] data) {
|
||||||
|
if (data != null && data.length > 0) {
|
||||||
|
int oldRows = screen.getRows();
|
||||||
|
int oldCols = screen.getCols();
|
||||||
|
screen.erase(false);
|
||||||
|
graphicsPlane.clear();
|
||||||
|
if (gocaDecoder != null) {
|
||||||
|
gocaDecoder.setGraphicsCursorActive(false);
|
||||||
|
}
|
||||||
|
processWrite(data, 0, data.length, true);
|
||||||
|
if (oldRows != screen.getRows() || oldCols != screen.getCols()) {
|
||||||
|
notifyScreenSizeChanged();
|
||||||
|
}
|
||||||
|
screen.translateToUnicode();
|
||||||
|
screen.markAllChanged();
|
||||||
|
screen.updateDisplaySnapshot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processEraseWriteAlternate(byte[] data) {
|
||||||
|
if (data != null && data.length > 0) {
|
||||||
|
int oldRows = screen.getRows();
|
||||||
|
int oldCols = screen.getCols();
|
||||||
|
screen.erase(true);
|
||||||
|
graphicsPlane.clear();
|
||||||
|
if (gocaDecoder != null) {
|
||||||
|
gocaDecoder.setGraphicsCursorActive(false);
|
||||||
|
}
|
||||||
|
processWrite(data, 0, data.length, true);
|
||||||
|
if (oldRows != screen.getRows() || oldCols != screen.getCols()) {
|
||||||
|
notifyScreenSizeChanged();
|
||||||
|
}
|
||||||
|
screen.translateToUnicode();
|
||||||
|
screen.markAllChanged();
|
||||||
|
screen.updateDisplaySnapshot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processEraseAllUnprotected() {
|
||||||
|
synchronized (screen.getRenderLock()) {
|
||||||
|
screen.eraseAllUnprotected();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processReadModified() {
|
||||||
|
processReadModified(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processReadModifiedAll() {
|
||||||
|
processReadModified(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processWriteStructuredField(byte[] data) {
|
||||||
|
if (data != null && data.length > 0) {
|
||||||
|
processWriteStructuredField(data, 0, data.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processSFReadPartition(byte[] data) {
|
||||||
|
if (data != null && data.length >= 3) {
|
||||||
|
int off = ((data[0] & 0xFF) == CMD_WSF || (data[0] & 0xFF) == SNA_CMD_WSF) ? 1 : 0;
|
||||||
|
processSFReadPartition(data, off, data.length - off);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processSFReadPartitionQuery(byte[] data) {
|
||||||
|
sendAllQueryReplies();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processSFReadPartitionQueryList(byte[] data) {
|
||||||
|
if (data != null && data.length >= 6) {
|
||||||
|
int off = ((data[0] & 0xFF) == CMD_WSF || (data[0] & 0xFF) == SNA_CMD_WSF) ? 1 : 0;
|
||||||
|
int qlStart = off + 6;
|
||||||
|
if (data.length > qlStart) {
|
||||||
|
byte[] codes = new byte[data.length - qlStart];
|
||||||
|
System.arraycopy(data, qlStart, codes, 0, codes.length);
|
||||||
|
sendRequestedQueryReplies(codes);
|
||||||
|
} else {
|
||||||
|
sendAllQueryReplies();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendAllQueryReplies();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processSetReplyMode(byte[] data) {
|
||||||
|
if (data != null && data.length >= 3) {
|
||||||
|
int off = ((data[0] & 0xFF) == CMD_WSF || (data[0] & 0xFF) == SNA_CMD_WSF) ? 1 : 0;
|
||||||
|
processSetReplyMode(data, off, data.length - off);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processCreatePartition(byte[] data) {
|
||||||
|
if (data != null && data.length >= 3) {
|
||||||
|
int off = ((data[0] & 0xFF) == CMD_WSF || (data[0] & 0xFF) == SNA_CMD_WSF) ? 1 : 0;
|
||||||
|
processCreatePartition(data, off, data.length - off);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processDestroyPartition(byte[] data) {
|
||||||
|
if (data != null && data.length >= 3) {
|
||||||
|
int off = ((data[0] & 0xFF) == CMD_WSF || (data[0] & 0xFF) == SNA_CMD_WSF) ? 1 : 0;
|
||||||
|
processDestroyPartition(data, off, data.length - off);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processActivatePartition(byte[] data) {
|
||||||
|
if (data != null && data.length >= 3) {
|
||||||
|
int off = ((data[0] & 0xFF) == CMD_WSF || (data[0] & 0xFF) == SNA_CMD_WSF) ? 1 : 0;
|
||||||
|
processActivatePartition(data, off, data.length - off);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processEraseReset(byte[] data) {
|
||||||
|
if (data != null && data.length >= 3) {
|
||||||
|
int off = ((data[0] & 0xFF) == CMD_WSF || (data[0] & 0xFF) == SNA_CMD_WSF) ? 1 : 0;
|
||||||
|
processEraseReset(data, off, data.length - off);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processQueryListOrder(byte[] data, int offset, int length) {
|
||||||
|
processSFReadPartition(data, offset, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processImplicitPartition(byte[] data, int offset, int length) {
|
||||||
|
screen.setActivePartition(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processModifyPartition(byte[] data, int offset, int length) {
|
||||||
|
if (length >= 4) {
|
||||||
|
int pid = data[offset + 3] & 0xFF;
|
||||||
|
log.fine("Modify partition pid=" + pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processResetPartition(byte[] data, int offset, int length) {
|
||||||
|
screen.setActivePartition(0);
|
||||||
|
screen.erase(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processNullStructuredField() {
|
||||||
|
log.fine("Processed null structured field");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+312
-135
@@ -15,16 +15,17 @@ public class QueryReplyBuilder {
|
|||||||
|
|
||||||
private static final Logger log = Logger.getLogger(QueryReplyBuilder.class.getName());
|
private static final Logger log = Logger.getLogger(QueryReplyBuilder.class.getName());
|
||||||
|
|
||||||
// Canned values from 3279-2 (matching sf.c)
|
|
||||||
private static final int SW_3279_2 = 0x09;
|
private static final int SW_3279_2 = 0x09;
|
||||||
private static final int SH_3279_2 = 0x0c;
|
private static final int SH_3279_2 = 0x0c;
|
||||||
private static final int Xr_3279_2 = 0x000a02e5;
|
|
||||||
private static final int Yr_3279_2 = 0x0002006f;
|
// Usable Area physical dimensions matching IBM Host On-Demand DS3270.java (Inches, 96 dpi: 0x00010060)
|
||||||
|
private static final int Xr_HOD = 0x00010060;
|
||||||
|
private static final int Yr_HOD = 0x00010060;
|
||||||
|
|
||||||
private final ScreenBuffer screen;
|
private final ScreenBuffer screen;
|
||||||
private GraphicsMode graphicsMode = GraphicsMode.BOTH;
|
private GraphicsMode graphicsMode = GraphicsMode.BOTH;
|
||||||
|
|
||||||
// Base query reply codes (text mode)
|
// Base query reply codes (text mode, matches HOD DS3270.java queryEquiv)
|
||||||
private static final int[] SUPPORTED_QR_BASE = {
|
private static final int[] SUPPORTED_QR_BASE = {
|
||||||
QR_SUMMARY, // 0x80 — summary must list itself
|
QR_SUMMARY, // 0x80 — summary must list itself
|
||||||
QR_USABLE_AREA, // 0x81
|
QR_USABLE_AREA, // 0x81
|
||||||
@@ -34,10 +35,11 @@ public class QueryReplyBuilder {
|
|||||||
QR_HIGHLIGHTING, // 0x87
|
QR_HIGHLIGHTING, // 0x87
|
||||||
QR_REPLY_MODES, // 0x88
|
QR_REPLY_MODES, // 0x88
|
||||||
QR_DDM, // 0x95 - Distributed Data Management (file transfer)
|
QR_DDM, // 0x95 - Distributed Data Management (file transfer)
|
||||||
QR_IMP_PART, // 0xa6
|
QR_AUXDA, // 0x99 - Auxiliary Devices
|
||||||
|
QR_IMP_PART, // 0xa6 - Implicit Partition Sizes
|
||||||
};
|
};
|
||||||
|
|
||||||
// Vector graphics query reply codes matching HOD DS3270.java line 1723
|
// Vector graphics query reply codes matching HOD QueryReply3270Constants.java QR_3270_WITHOUT_DCBS_SUMMARY_STRING
|
||||||
private static final int[] SUPPORTED_QR_VECTOR = {
|
private static final int[] SUPPORTED_QR_VECTOR = {
|
||||||
QR_SUMMARY, // 0x80
|
QR_SUMMARY, // 0x80
|
||||||
QR_USABLE_AREA, // 0x81
|
QR_USABLE_AREA, // 0x81
|
||||||
@@ -46,17 +48,17 @@ public class QueryReplyBuilder {
|
|||||||
QR_COLOR, // 0x86
|
QR_COLOR, // 0x86
|
||||||
QR_HIGHLIGHTING, // 0x87
|
QR_HIGHLIGHTING, // 0x87
|
||||||
QR_REPLY_MODES, // 0x88
|
QR_REPLY_MODES, // 0x88
|
||||||
QR_SAVE_RESTORE, // 0x8c
|
QR_OUTLINING, // 0x8c
|
||||||
QR_DDM, // 0x95
|
QR_DDM, // 0x95
|
||||||
QR_TRANSPARENCY, // 0x99
|
QR_AUXDA, // 0x99
|
||||||
QR_IMP_PART, // 0xa6
|
QR_IMP_PART, // 0xa6
|
||||||
QR_RPQ_NAMES, // 0xa8
|
QR_TRANSPARENCY, // 0xa8
|
||||||
QR_GRAPHICS, // 0xb0
|
QR_SEGMENT, // 0xb0
|
||||||
QR_GIMAGE, // 0xb1
|
QR_PROCEDURE, // 0xb1
|
||||||
QR_AUX_DEV, // 0xb2
|
QR_LINETYPE, // 0xb2
|
||||||
QR_OEM_FMT, // 0xb3
|
QR_PORT, // 0xb3
|
||||||
QR_GCOLOR, // 0xb4
|
QR_GRCOLOR, // 0xb4
|
||||||
QR_GSYMBOLS, // 0xb6
|
QR_GRSYMBOLSET, // 0xb6
|
||||||
};
|
};
|
||||||
|
|
||||||
public QueryReplyBuilder(ScreenBuffer screen) {
|
public QueryReplyBuilder(ScreenBuffer screen) {
|
||||||
@@ -77,7 +79,8 @@ public class QueryReplyBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build all query replies as a single AID_SF + structured field response.
|
* Build base query replies in response to a generic Read Partition Query (0x02).
|
||||||
|
* Returns base text/presentation summary structured fields (matches HOD DS3270.java line 1723).
|
||||||
*/
|
*/
|
||||||
public byte[] buildAllQueryReplies(int maxCols, int maxRows, int bufferSize) {
|
public byte[] buildAllQueryReplies(int maxCols, int maxRows, int bufferSize) {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream(512);
|
ByteArrayOutputStream out = new ByteArrayOutputStream(512);
|
||||||
@@ -85,62 +88,90 @@ public class QueryReplyBuilder {
|
|||||||
// AID byte for structured field
|
// AID byte for structured field
|
||||||
out.write(AID_SF);
|
out.write(AID_SF);
|
||||||
|
|
||||||
// Summary
|
// Summary (0x80) - lists all supported capabilities
|
||||||
appendQueryReply(out, QR_SUMMARY, buildSummary());
|
appendQueryReply(out, QR_SUMMARY, buildSummary());
|
||||||
|
|
||||||
// Usable Area
|
// Usable Area (0x81)
|
||||||
appendQueryReply(out, QR_USABLE_AREA, buildUsableArea(maxCols, maxRows, bufferSize));
|
appendQueryReply(out, QR_USABLE_AREA, buildUsableArea(maxCols, maxRows, bufferSize));
|
||||||
|
|
||||||
// Alpha Partitions
|
// Alpha Partitions (0x84)
|
||||||
appendQueryReply(out, QR_ALPHA_PART, buildAlphaPartitions(maxRows));
|
appendQueryReply(out, QR_ALPHA_PART, buildAlphaPartitions(maxRows));
|
||||||
|
|
||||||
// Character Sets
|
// Character Sets (0x85)
|
||||||
appendQueryReply(out, QR_CHARSETS, buildCharsets());
|
appendQueryReply(out, QR_CHARSETS, buildCharsets());
|
||||||
|
|
||||||
// Color
|
// Color (0x86)
|
||||||
appendQueryReply(out, QR_COLOR, buildColor());
|
appendQueryReply(out, QR_COLOR, buildColor());
|
||||||
|
|
||||||
// Highlighting
|
// Highlighting (0x87)
|
||||||
appendQueryReply(out, QR_HIGHLIGHTING, buildHighlighting());
|
appendQueryReply(out, QR_HIGHLIGHTING, buildHighlighting());
|
||||||
|
|
||||||
// Reply Modes (0x88)
|
// Reply Modes (0x88)
|
||||||
appendQueryReply(out, QR_REPLY_MODES, buildReplyModes());
|
appendQueryReply(out, QR_REPLY_MODES, buildReplyModes());
|
||||||
|
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
boolean isDbcs = (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS());
|
||||||
// Save/Restore (0x8C)
|
if (isDbcs) {
|
||||||
appendQueryReply(out, QR_SAVE_RESTORE, buildSaveRestore());
|
// Outlining (0x8C)
|
||||||
|
appendQueryReply(out, QR_OUTLINING, buildOutlining());
|
||||||
|
// DBCS Asia (0x91)
|
||||||
|
appendQueryReply(out, QR_DBCS_ASIA, buildDbcsAsia());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Distributed Data Management (0x95)
|
// Distributed Data Management (0x95)
|
||||||
appendQueryReply(out, QR_DDM, buildDdm(4096));
|
appendQueryReply(out, QR_DDM, buildDdm(4096));
|
||||||
|
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
// Auxiliary Devices (0x99)
|
||||||
// Transparency (0x99)
|
appendQueryReply(out, QR_AUXDA, buildAuxDa());
|
||||||
appendQueryReply(out, QR_TRANSPARENCY, buildTransparency());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implicit Partition (0xA6)
|
// Implicit Partition (0xA6)
|
||||||
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
||||||
|
|
||||||
// Vector Graphics QRs if enabled
|
log.info("Built " + out.size() + " bytes of base query replies (graphicsMode=" + graphicsMode + ")");
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
return out.toByteArray();
|
||||||
appendQueryReply(out, QR_RPQ_NAMES, buildRpqNames()); // 0xA8
|
|
||||||
appendQueryReply(out, QR_GRAPHICS, buildGraphics(maxCols, maxRows)); // 0xB0
|
|
||||||
appendQueryReply(out, QR_GIMAGE, buildGImage(maxCols, maxRows)); // 0xB1
|
|
||||||
appendQueryReply(out, QR_AUX_DEV, buildAuxDev()); // 0xB2
|
|
||||||
appendOemFmt(out); // 0xB3
|
|
||||||
appendQueryReply(out, QR_GCOLOR, buildGColor()); // 0xB4
|
|
||||||
appendQueryReply(out, QR_GSYMBOLS, buildGSymbols()); // 0xB6
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("Built " + out.size() + " bytes of all query replies (graphicsMode=" + graphicsMode + ")");
|
/**
|
||||||
|
* Build complete query replies including vector graphics (when SF_RPQ_ALL 0x80 is requested).
|
||||||
|
*/
|
||||||
|
public byte[] buildCompleteQueryReplies(int maxCols, int maxRows, int bufferSize) {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream(512);
|
||||||
|
out.write(AID_SF);
|
||||||
|
|
||||||
|
appendQueryReply(out, QR_SUMMARY, buildSummary());
|
||||||
|
appendQueryReply(out, QR_USABLE_AREA, buildUsableArea(maxCols, maxRows, bufferSize));
|
||||||
|
appendQueryReply(out, QR_ALPHA_PART, buildAlphaPartitions(maxRows));
|
||||||
|
appendQueryReply(out, QR_CHARSETS, buildCharsets());
|
||||||
|
appendQueryReply(out, QR_COLOR, buildColor());
|
||||||
|
appendQueryReply(out, QR_HIGHLIGHTING, buildHighlighting());
|
||||||
|
appendQueryReply(out, QR_REPLY_MODES, buildReplyModes());
|
||||||
|
|
||||||
|
appendQueryReply(out, QR_OUTLINING, buildOutlining());
|
||||||
|
boolean isDbcs = (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS());
|
||||||
|
if (isDbcs) {
|
||||||
|
appendQueryReply(out, QR_DBCS_ASIA, buildDbcsAsia());
|
||||||
|
}
|
||||||
|
appendQueryReply(out, QR_DDM, buildDdm(4096));
|
||||||
|
appendQueryReply(out, QR_AUXDA, buildAuxDa());
|
||||||
|
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
||||||
|
|
||||||
|
if (graphicsMode.isVectorGraphicsEnabled()) {
|
||||||
|
appendQueryReply(out, QR_TRANSPARENCY, buildTransparency()); // 0xA8
|
||||||
|
appendQueryReply(out, QR_SEGMENT, buildSegment(maxCols, maxRows)); // 0xB0
|
||||||
|
appendQueryReply(out, QR_PROCEDURE, buildProcedure(maxCols, maxRows)); // 0xB1
|
||||||
|
appendQueryReply(out, QR_LINETYPE, buildLineType()); // 0xB2
|
||||||
|
appendPort(out); // 0xB3
|
||||||
|
appendQueryReply(out, QR_GRCOLOR, buildGrColor()); // 0xB4
|
||||||
|
appendQueryReply(out, QR_GRSYMBOLSET, buildGrSymbolSet()); // 0xB6
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Built " + out.size() + " bytes of complete query replies (graphicsMode=" + graphicsMode + ")");
|
||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build specific query replies in response to a Read Partition Query List (SF_RPQ_LIST).
|
* Build specific query replies in response to a Read Partition Query List (SF_RPQ_LIST).
|
||||||
* For any unsupported requested query code, emits a QR_NULL (0xFF) structured field
|
* For any unsupported requested query code, emits a QR_NULL (0xFF) structured field
|
||||||
* matching x3270 sf.c behavior.
|
* matching HOD DS3270.java line 1835.
|
||||||
*/
|
*/
|
||||||
public byte[] buildQueryReplies(byte[] requestedCodes, int maxCols, int maxRows, int bufferSize) {
|
public byte[] buildQueryReplies(byte[] requestedCodes, int maxCols, int maxRows, int bufferSize) {
|
||||||
if (requestedCodes == null || requestedCodes.length == 0) {
|
if (requestedCodes == null || requestedCodes.length == 0) {
|
||||||
@@ -174,72 +205,70 @@ public class QueryReplyBuilder {
|
|||||||
case QR_REPLY_MODES:
|
case QR_REPLY_MODES:
|
||||||
appendQueryReply(out, QR_REPLY_MODES, buildReplyModes());
|
appendQueryReply(out, QR_REPLY_MODES, buildReplyModes());
|
||||||
break;
|
break;
|
||||||
case QR_SAVE_RESTORE:
|
case QR_OUTLINING: // 0x8C
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
appendQueryReply(out, QR_OUTLINING, buildOutlining());
|
||||||
appendQueryReply(out, QR_SAVE_RESTORE, buildSaveRestore());
|
break;
|
||||||
|
case QR_DBCS_ASIA: // 0x91
|
||||||
|
if (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS()) {
|
||||||
|
appendQueryReply(out, QR_DBCS_ASIA, buildDbcsAsia());
|
||||||
} else {
|
} else {
|
||||||
appendQueryReply(out, QR_NULL, new byte[0]);
|
appendQueryReply(out, QR_NULL, new byte[0]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case QR_DDM:
|
case QR_DDM: // 0x95
|
||||||
appendQueryReply(out, QR_DDM, buildDdm(4096));
|
appendQueryReply(out, QR_DDM, buildDdm(4096));
|
||||||
break;
|
break;
|
||||||
case QR_TRANSPARENCY:
|
case QR_AUXDA: // 0x99
|
||||||
|
appendQueryReply(out, QR_AUXDA, buildAuxDa());
|
||||||
|
break;
|
||||||
|
case QR_IMP_PART: // 0xA6
|
||||||
|
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
||||||
|
break;
|
||||||
|
case QR_TRANSPARENCY: // 0xA8
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
if (graphicsMode.isVectorGraphicsEnabled()) {
|
||||||
appendQueryReply(out, QR_TRANSPARENCY, buildTransparency());
|
appendQueryReply(out, QR_TRANSPARENCY, buildTransparency());
|
||||||
} else {
|
} else {
|
||||||
appendQueryReply(out, QR_NULL, new byte[0]);
|
appendQueryReply(out, QR_NULL, new byte[0]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case QR_IMP_PART:
|
case QR_SEGMENT: // 0xB0
|
||||||
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
|
||||||
break;
|
|
||||||
case QR_RPQ_NAMES:
|
|
||||||
case QR_RPQNAMES:
|
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
if (graphicsMode.isVectorGraphicsEnabled()) {
|
||||||
appendQueryReply(out, code, buildRpqNames());
|
appendQueryReply(out, QR_SEGMENT, buildSegment(maxCols, maxRows));
|
||||||
} else {
|
} else {
|
||||||
appendQueryReply(out, QR_NULL, new byte[0]);
|
appendQueryReply(out, QR_NULL, new byte[0]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case QR_GRAPHICS:
|
case QR_PROCEDURE: // 0xB1
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
if (graphicsMode.isVectorGraphicsEnabled()) {
|
||||||
appendQueryReply(out, QR_GRAPHICS, buildGraphics(maxCols, maxRows));
|
appendQueryReply(out, QR_PROCEDURE, buildProcedure(maxCols, maxRows));
|
||||||
} else {
|
} else {
|
||||||
appendQueryReply(out, QR_NULL, new byte[0]);
|
appendQueryReply(out, QR_NULL, new byte[0]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case QR_GIMAGE:
|
case QR_LINETYPE: // 0xB2
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
if (graphicsMode.isVectorGraphicsEnabled()) {
|
||||||
appendQueryReply(out, QR_GIMAGE, buildGImage(maxCols, maxRows));
|
appendQueryReply(out, QR_LINETYPE, buildLineType());
|
||||||
} else {
|
} else {
|
||||||
appendQueryReply(out, QR_NULL, new byte[0]);
|
appendQueryReply(out, QR_NULL, new byte[0]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case QR_AUX_DEV:
|
case QR_PORT: // 0xB3
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
if (graphicsMode.isVectorGraphicsEnabled()) {
|
||||||
appendQueryReply(out, QR_AUX_DEV, buildAuxDev());
|
appendPort(out);
|
||||||
} else {
|
} else {
|
||||||
appendQueryReply(out, QR_NULL, new byte[0]);
|
appendQueryReply(out, QR_NULL, new byte[0]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case QR_OEM_FMT:
|
case QR_GRCOLOR: // 0xB4
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
if (graphicsMode.isVectorGraphicsEnabled()) {
|
||||||
appendOemFmt(out);
|
appendQueryReply(out, QR_GRCOLOR, buildGrColor());
|
||||||
} else {
|
} else {
|
||||||
appendQueryReply(out, QR_NULL, new byte[0]);
|
appendQueryReply(out, QR_NULL, new byte[0]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case QR_GCOLOR:
|
case QR_GRSYMBOLSET: // 0xB6
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
if (graphicsMode.isVectorGraphicsEnabled()) {
|
||||||
appendQueryReply(out, QR_GCOLOR, buildGColor());
|
appendQueryReply(out, QR_GRSYMBOLSET, buildGrSymbolSet());
|
||||||
} else {
|
|
||||||
appendQueryReply(out, QR_NULL, new byte[0]);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case QR_GSYMBOLS:
|
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
|
||||||
appendQueryReply(out, QR_GSYMBOLS, buildGSymbols());
|
|
||||||
} else {
|
} else {
|
||||||
appendQueryReply(out, QR_NULL, new byte[0]);
|
appendQueryReply(out, QR_NULL, new byte[0]);
|
||||||
}
|
}
|
||||||
@@ -265,44 +294,85 @@ public class QueryReplyBuilder {
|
|||||||
out.write(data, 0, data.length);
|
out.write(data, 0, data.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] buildSummary() {
|
public byte[] buildSummary() {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
int[] codes = graphicsMode.isVectorGraphicsEnabled() ? SUPPORTED_QR_VECTOR : SUPPORTED_QR_BASE;
|
int[] codes = graphicsMode.isVectorGraphicsEnabled() ? SUPPORTED_QR_VECTOR : SUPPORTED_QR_BASE;
|
||||||
|
boolean isDbcs = (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS());
|
||||||
for (int code : codes) {
|
for (int code : codes) {
|
||||||
out.write(code);
|
out.write(code);
|
||||||
|
if (isDbcs && code == QR_OUTLINING) {
|
||||||
|
out.write(QR_DBCS_ASIA); // 0x91
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] buildUsableArea(int maxCols, int maxRows, int bufferSize) {
|
public byte[] buildUsableArea() {
|
||||||
|
int maxCols = (screen != null) ? screen.getMaxCols() : MODEL_2_COLS;
|
||||||
|
int maxRows = (screen != null) ? screen.getMaxRows() : MODEL_2_ROWS;
|
||||||
|
return buildUsableArea(maxCols, maxRows, maxCols * maxRows);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildUsableArea(int maxCols, int maxRows, int bufferSize) {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream(19);
|
ByteArrayOutputStream out = new ByteArrayOutputStream(19);
|
||||||
out.write(0x01); // 12/14-bit addressing
|
out.write(graphicsMode.isVectorGraphicsEnabled() ? 0x03 : 0x01); // 12/14-bit addressing + graphics flag (matching HOD DS3270.java)
|
||||||
out.write(0x00); // no special character features
|
out.write(0x00); // no special character features
|
||||||
out.write((maxCols >> 8) & 0xFF); // usable width high
|
out.write((maxCols >> 8) & 0xFF); // usable width high
|
||||||
out.write(maxCols & 0xFF); // usable width low
|
out.write(maxCols & 0xFF); // usable width low
|
||||||
out.write((maxRows >> 8) & 0xFF); // usable height high
|
out.write((maxRows >> 8) & 0xFF); // usable height high
|
||||||
out.write(maxRows & 0xFF); // usable height low
|
out.write(maxRows & 0xFF); // usable height low
|
||||||
out.write(0x01); // units (mm)
|
out.write(0x00); // units (0x00 = inches, matching IBM Host On-Demand QR_USEAREA_STRING)
|
||||||
// Xr (4 bytes) - canned from 3279-2
|
// Xr (4 bytes) - matching IBM Host On-Demand QR_USEAREA_STRING (96 DPI)
|
||||||
out.write((Xr_3279_2 >> 24) & 0xFF);
|
out.write((Xr_HOD >> 24) & 0xFF);
|
||||||
out.write((Xr_3279_2 >> 16) & 0xFF);
|
out.write((Xr_HOD >> 16) & 0xFF);
|
||||||
out.write((Xr_3279_2 >> 8) & 0xFF);
|
out.write((Xr_HOD >> 8) & 0xFF);
|
||||||
out.write(Xr_3279_2 & 0xFF);
|
out.write(Xr_HOD & 0xFF);
|
||||||
// Yr (4 bytes) - canned from 3279-2
|
// Yr (4 bytes) - matching IBM Host On-Demand QR_USEAREA_STRING (96 DPI)
|
||||||
out.write((Yr_3279_2 >> 24) & 0xFF);
|
out.write((Yr_HOD >> 24) & 0xFF);
|
||||||
out.write((Yr_3279_2 >> 16) & 0xFF);
|
out.write((Yr_HOD >> 16) & 0xFF);
|
||||||
out.write((Yr_3279_2 >> 8) & 0xFF);
|
out.write((Yr_HOD >> 8) & 0xFF);
|
||||||
out.write(Yr_3279_2 & 0xFF);
|
out.write(Yr_HOD & 0xFF);
|
||||||
out.write(SW_3279_2); // AW
|
int charW = getCharWidth();
|
||||||
out.write(SH_3279_2); // AH
|
int charH = getCharHeight();
|
||||||
|
out.write(charW); // AW
|
||||||
|
out.write(charH); // AH
|
||||||
int buf = maxCols * maxRows;
|
int buf = maxCols * maxRows;
|
||||||
out.write((buf >> 8) & 0xFF); // buffer size high
|
out.write((buf >> 8) & 0xFF); // buffer size high
|
||||||
out.write(buf & 0xFF); // buffer size low
|
out.write(buf & 0xFF); // buffer size low
|
||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] buildAlphaPartitions(int maxRows) {
|
public int getCharWidth() {
|
||||||
int bufSize = screen.getMaxCols() * screen.getMaxRows();
|
if (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS()) {
|
||||||
|
return 12;
|
||||||
|
}
|
||||||
|
return SW_3279_2; // 9
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCharHeight() {
|
||||||
|
// ARCHITECTURAL NOTE ON 3179G GOCA VERTICAL ALIGNMENT & QUERY REPLIES:
|
||||||
|
// Why hardcoding SH = 12 (0x0C) in Character Sets & Usable Area failed in past iterations:
|
||||||
|
// When SDH/AH is declared as 12 (0x0C) in Query Reply, the mainframe host GDDM engine computes
|
||||||
|
// total presentation space as rows * 12 (e.g. 43 * 12 = 516 units, yMax = 257).
|
||||||
|
// GDDM then places the top menu bar at Row 1 (gy = 187..200).
|
||||||
|
// Meanwhile, the client emulator rendered into a 16-pitch grid (43 * 16 = 688 units, yMax = 343).
|
||||||
|
// On a 688-unit canvas, gy = 200 mapped to Row 9.2 (middle of the screen), leaving a massive void above.
|
||||||
|
// When the user clicked on the visual menu drawn at Row 9, the client emitted gy = 189 with cursor at Row 9,
|
||||||
|
// which GDDM rejected as outside its menu hit box (causing terminal alarm beeps).
|
||||||
|
//
|
||||||
|
// Solution: Declare SDH = 16 (0x10) when Vector Graphics is enabled (3179G standard), ensuring host GDDM
|
||||||
|
// and client GraphicsPlane share the exact same 16-pitch presentation space (720x688, yMax = 343).
|
||||||
|
return graphicsMode.isVectorGraphicsEnabled() ? 0x10 : SH_3279_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildAlphaPartitions() {
|
||||||
|
int maxRows = (screen != null) ? screen.getMaxRows() : MODEL_2_ROWS;
|
||||||
|
return buildAlphaPartitions(maxRows);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildAlphaPartitions(int maxRows) {
|
||||||
|
int maxCols = (screen != null) ? screen.getMaxCols() : MODEL_2_COLS;
|
||||||
|
int bufSize = maxCols * maxRows;
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream(4);
|
ByteArrayOutputStream out = new ByteArrayOutputStream(4);
|
||||||
out.write(0x00); // max partitions (1 byte: 0x00 = 1 partition)
|
out.write(0x00); // max partitions (1 byte: 0x00 = 1 partition)
|
||||||
out.write((bufSize >> 8) & 0xFF); // total partition storage high
|
out.write((bufSize >> 8) & 0xFF); // total partition storage high
|
||||||
@@ -311,21 +381,33 @@ public class QueryReplyBuilder {
|
|||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] buildCharsets() {
|
public byte[] buildCharsets() {
|
||||||
if (graphicsMode.isProgrammedSymbolsEnabled()) {
|
int charW = getCharWidth();
|
||||||
// Programmed Symbols mode (3279 PS with LoadPS 0x0A, flags1 = 0xA2 for GE + PS + CGCSGID)
|
int charH = getCharHeight();
|
||||||
|
|
||||||
|
int cgcsgid = 0x02B9;
|
||||||
|
int cpgid = 0x0025;
|
||||||
|
if (screen != null && screen.getTranslator() != null) {
|
||||||
|
cgcsgid = screen.getTranslator().getCgcsgid();
|
||||||
|
cpgid = screen.getTranslator().getCpgid();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (graphicsMode == GraphicsMode.PROGRAMMED_SYMBOLS) {
|
||||||
|
// Programmed Symbols only mode (3279 PS with LoadPS 0x0A, flags1 = 0xA2 for GE + PS + CGCSGID)
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream(65);
|
ByteArrayOutputStream out = new ByteArrayOutputStream(65);
|
||||||
out.write(0xa2); // flags: GE (0x80), PS/Loadable Charsets (0x20), CGCSGID present (0x02)
|
out.write(0xa2); // flags: GE (0x80), PS/Loadable Charsets (0x20), CGCSGID present (0x02)
|
||||||
out.write(0x00); // more flags
|
out.write(0x00); // more flags
|
||||||
out.write(SW_3279_2); // SDW (9)
|
out.write(charW); // SDW (9)
|
||||||
out.write(SH_3279_2); // SDH (12)
|
out.write(charH); // SDH (16 for 3179G, 12 for 3279-2)
|
||||||
out.write(0x0a); // Load PS format types supported: Format 1 and Format 3
|
out.write(0x0a); // Load PS format types supported: Format 1 and Format 3
|
||||||
out.write(0x00); // Load PS device type (high)
|
out.write(0x00); // Load PS device type (high)
|
||||||
out.write(0x00); // Load PS device type (low)
|
out.write(0x00); // Load PS device type (low)
|
||||||
out.write(0x00); // reserved
|
out.write(0x00); // reserved
|
||||||
out.write(0x07); // DL = 7 bytes per descriptor
|
out.write(0x07); // DL = 7 bytes per descriptor
|
||||||
// Descriptor 1 (SET 0): default character set (non-loadable, single plane, CP037)
|
// Descriptor 1 (SET 0): default character set (non-loadable, single plane, active codepage)
|
||||||
out.write(0x00); out.write(0x10); out.write(0x00); out.write(0x02); out.write(0xb9); out.write(0x00); out.write(0x25);
|
out.write(0x00); out.write(0x10); out.write(0x00);
|
||||||
|
out.write((cgcsgid >> 8) & 0xFF); out.write(cgcsgid & 0xFF);
|
||||||
|
out.write((cpgid >> 8) & 0xFF); out.write(cpgid & 0xFF);
|
||||||
// Descriptor 2 (SET 1): APL/GE character set
|
// Descriptor 2 (SET 1): APL/GE character set
|
||||||
out.write(0x01); out.write(0x00); out.write(0xf1); out.write(0x03); out.write(0xc3); out.write(0x01); out.write(0x36);
|
out.write(0x01); out.write(0x00); out.write(0xf1); out.write(0x03); out.write(0xc3); out.write(0x01); out.write(0x36);
|
||||||
// Loadable Single-Plane PS Sets (PSA, PSB: Slots 2, 3) - Flags = 0x80 (Loadable, single plane)
|
// Loadable Single-Plane PS Sets (PSA, PSB: Slots 2, 3) - Flags = 0x80 (Loadable, single plane)
|
||||||
@@ -343,21 +425,23 @@ public class QueryReplyBuilder {
|
|||||||
ByteArrayOutputStream out = new ByteArrayOutputStream(23);
|
ByteArrayOutputStream out = new ByteArrayOutputStream(23);
|
||||||
out.write(0x82); // flags: GE, CGCSGID present
|
out.write(0x82); // flags: GE, CGCSGID present
|
||||||
out.write(0x00); // more flags
|
out.write(0x00); // more flags
|
||||||
out.write(SW_3279_2); // SDW - default char width (9)
|
out.write(charW); // SDW - default char width (9)
|
||||||
out.write(SH_3279_2); // SDH - default char height (12)
|
out.write(charH); // SDH - default char height (16 for 3179G, 12 for 3279-2)
|
||||||
out.write(0x00); // LoadPS format (0x00)
|
out.write(0x00); // LoadPS format (0x00)
|
||||||
out.write(0x00);
|
out.write(0x00);
|
||||||
out.write(0x00);
|
out.write(0x00);
|
||||||
out.write(0x00);
|
out.write(0x00);
|
||||||
out.write(0x07); // DL = 7
|
out.write(0x07); // DL = 7
|
||||||
// Set 0 (Base EBCDIC - Non-loadable, single plane, CP037)
|
// Set 0 (Base EBCDIC - Non-loadable, single plane, active codepage)
|
||||||
out.write(0x00); out.write(0x10); out.write(0x00); out.write(0x02); out.write(0xb9); out.write(0x00); out.write(0x25);
|
out.write(0x00); out.write(0x10); out.write(0x00);
|
||||||
|
out.write((cgcsgid >> 8) & 0xFF); out.write(cgcsgid & 0xFF);
|
||||||
|
out.write((cpgid >> 8) & 0xFF); out.write(cpgid & 0xFF);
|
||||||
// Set 1 (APL/Text)
|
// Set 1 (APL/Text)
|
||||||
out.write(0x01); out.write(0x00); out.write(0xf1); out.write(0x03); out.write(0xc3); out.write(0x01); out.write(0x36);
|
out.write(0x01); out.write(0x00); out.write(0xf1); out.write(0x03); out.write(0xc3); out.write(0x01); out.write(0x36);
|
||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] buildColor() {
|
public byte[] buildColor() {
|
||||||
// Alphanumeric Color (matches HOD: 8 pairs, F1..F7 + default F4 green, 18 bytes payload / 22 bytes total)
|
// Alphanumeric Color (matches HOD: 8 pairs, F1..F7 + default F4 green, 18 bytes payload / 22 bytes total)
|
||||||
return new byte[] {
|
return new byte[] {
|
||||||
0x00, 0x08, 0x00, (byte) 0xF4,
|
0x00, 0x08, 0x00, (byte) 0xF4,
|
||||||
@@ -371,7 +455,7 @@ public class QueryReplyBuilder {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] buildHighlighting() {
|
public byte[] buildHighlighting() {
|
||||||
// Highlighting (matches HOD: 4 pairs: default F0, Blink F1, Reverse F2, Underscore F4, 9 bytes payload / 13 bytes total)
|
// Highlighting (matches HOD: 4 pairs: default F0, Blink F1, Reverse F2, Underscore F4, 9 bytes payload / 13 bytes total)
|
||||||
return new byte[] {
|
return new byte[] {
|
||||||
0x04, 0x00, (byte) 0xF0,
|
0x04, 0x00, (byte) 0xF0,
|
||||||
@@ -381,11 +465,15 @@ public class QueryReplyBuilder {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] buildReplyModes() {
|
public byte[] buildReplyModes() {
|
||||||
return new byte[] { SF_SRM_FIELD, SF_SRM_XFIELD, SF_SRM_CHAR };
|
return new byte[] { SF_SRM_FIELD, SF_SRM_XFIELD, SF_SRM_CHAR };
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] buildDdm(int bufferSize) {
|
public byte[] buildDdm() {
|
||||||
|
return buildDdm(4096);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildDdm(int bufferSize) {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream(8);
|
ByteArrayOutputStream out = new ByteArrayOutputStream(8);
|
||||||
out.write(0x00); // reserved
|
out.write(0x00); // reserved
|
||||||
out.write(0x00); // reserved
|
out.write(0x00); // reserved
|
||||||
@@ -398,7 +486,13 @@ public class QueryReplyBuilder {
|
|||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] buildImplicitPartition(int maxCols, int maxRows) {
|
public byte[] buildImplicitPartition() {
|
||||||
|
int maxCols = (screen != null) ? screen.getMaxCols() : MODEL_2_COLS;
|
||||||
|
int maxRows = (screen != null) ? screen.getMaxRows() : MODEL_2_ROWS;
|
||||||
|
return buildImplicitPartition(maxCols, maxRows);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildImplicitPartition(int maxCols, int maxRows) {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream(22);
|
ByteArrayOutputStream out = new ByteArrayOutputStream(22);
|
||||||
// Implicit partition sizes, 2 self-defining parameters
|
// Implicit partition sizes, 2 self-defining parameters
|
||||||
|
|
||||||
@@ -422,31 +516,78 @@ public class QueryReplyBuilder {
|
|||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] buildGraphics(int maxCols, int maxRows) {
|
public byte[] buildOutlining() {
|
||||||
int width = maxCols * 9;
|
// HOD QueryReply3270Constants.java QR_OUTLINING_STRING ("\u0000\n\u0081\u008c\u0000\u0000\u0000\u0000\u0000\u0000")
|
||||||
int height = maxRows * 12;
|
return new byte[]{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildDbcsAsia() {
|
||||||
|
// HOD QueryReply3270Constants.java QR_DBCS_ASIA_STRING ("\u0000\u000b\u0081\u0091\u0000\u0003\u0001\u0080\u0003\u0002\u0001")
|
||||||
|
return new byte[]{ 0x00, 0x03, 0x01, (byte) 0x80, 0x03, 0x02, 0x01 };
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildAuxDa() {
|
||||||
|
// HOD QueryReply3270Constants.java QR_AUXDA_STRING ("\u0000\u0006\u0081\u0099\u0000\u0000")
|
||||||
|
return new byte[]{ 0x00, 0x00 };
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildTransparency() {
|
||||||
|
// HOD QueryReply3270Constants.java QR_TRANSPARENCY_STRING ("\u0000\t\u0081\u00a8\u0002\u0000\u00f0\u00ff\u00ff")
|
||||||
|
return new byte[]{ 0x02, 0x00, (byte) 0xF0, (byte) 0xFF, (byte) 0xFF };
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildSegment() {
|
||||||
|
int maxCols = (screen != null) ? screen.getMaxCols() : MODEL_2_COLS;
|
||||||
|
int maxRows = (screen != null) ? screen.getMaxRows() : MODEL_2_ROWS;
|
||||||
|
return buildSegment(maxCols, maxRows);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildSegment(int maxCols, int maxRows) {
|
||||||
|
// HOD QueryReply3270Constants.java QR_SEGMENT_STRING ("\u0000\u000b\u0081\u00b0\u0080\u0002\u0000\u0000\u0000\u00fc\u0000")
|
||||||
return new byte[]{
|
return new byte[]{
|
||||||
(byte) 0x80, 0x02,
|
(byte) 0x80, 0x02,
|
||||||
(byte) ((width >> 8) & 0xFF), (byte) (width & 0xFF),
|
0x00, 0x00,
|
||||||
(byte) ((height >> 8) & 0xFF), (byte) (height & 0xFF),
|
0x00, (byte) 0xFC,
|
||||||
0x00
|
0x00
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] buildGImage(int maxCols, int maxRows) {
|
public byte[] buildGraphics() {
|
||||||
int width = maxCols * 9;
|
return buildSegment();
|
||||||
int height = maxRows * 12;
|
}
|
||||||
|
|
||||||
|
public byte[] buildGraphics(int maxCols, int maxRows) {
|
||||||
|
return buildSegment(maxCols, maxRows);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildProcedure() {
|
||||||
|
int maxCols = (screen != null) ? screen.getMaxCols() : MODEL_2_COLS;
|
||||||
|
int maxRows = (screen != null) ? screen.getMaxRows() : MODEL_2_ROWS;
|
||||||
|
return buildProcedure(maxCols, maxRows);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildProcedure(int maxCols, int maxRows) {
|
||||||
|
// HOD QueryReply3270Constants.java QR_PROCEDURE_STRING ("\u0000\u0015\u0081\u00b1\u0000\u0001\u0000\u0000\u0000\u00fc\u0000\u0006@\u0006@\u0006\u0001\u00ff\u00ff\u00ff\u00f0")
|
||||||
return new byte[]{
|
return new byte[]{
|
||||||
0x00, 0x01,
|
0x00, 0x01,
|
||||||
(byte) ((width >> 8) & 0xFF), (byte) (width & 0xFF),
|
0x00, 0x00,
|
||||||
(byte) ((height >> 8) & 0xFF), (byte) (height & 0xFF),
|
0x00, (byte) 0xFC,
|
||||||
0x00,
|
0x00,
|
||||||
0x06, 0x40, 0x06, 0x40, 0x06, 0x01,
|
0x06, 0x40, 0x06, 0x40, 0x06, 0x01,
|
||||||
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xF0
|
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xF0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] buildAuxDev() {
|
public byte[] buildGImage() {
|
||||||
|
return buildProcedure();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildGImage(int maxCols, int maxRows) {
|
||||||
|
return buildProcedure(maxCols, maxRows);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildLineType() {
|
||||||
|
// HOD QueryReply3270Constants.java QR_LINETYPE_STRING ("\u0000\u0018\u0081\u00b2\u0000\t\u0000\u0007\u0001\u0001\u0002\u0002\u0003\u0003\u0004\u0004\u0005\u0005\u0006\u0006\u0007\u0007\b\b")
|
||||||
return new byte[]{
|
return new byte[]{
|
||||||
0x00, 0x09, 0x00, 0x07,
|
0x00, 0x09, 0x00, 0x07,
|
||||||
0x01, 0x01, 0x02, 0x02, 0x03, 0x03, 0x04, 0x04,
|
0x01, 0x01, 0x02, 0x02, 0x03, 0x03, 0x04, 0x04,
|
||||||
@@ -454,38 +595,46 @@ public class QueryReplyBuilder {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] buildSaveRestore() {
|
public byte[] buildAuxDev() {
|
||||||
// HOD DS3270.java line 1768: 6 bytes payload
|
return buildLineType();
|
||||||
return new byte[]{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] buildTransparency() {
|
public byte[] buildAuxDevice() {
|
||||||
// HOD DS3270.java line 1782: 2 bytes payload
|
return buildLineType();
|
||||||
return new byte[]{ 0x00, 0x00 };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] buildRpqNames() {
|
public void appendPort(ByteArrayOutputStream out) {
|
||||||
// HOD DS3270.java line 1798: 5 bytes payload
|
// HOD QueryReply3270Constants.java QR_PORT_STRING (4 OEM format sub-fields, 64 bytes total)
|
||||||
return new byte[]{ 0x02, 0x00, (byte) 0xF0, (byte) 0xFF, (byte) 0xFF };
|
appendQueryReply(out, QR_PORT, new byte[]{
|
||||||
}
|
|
||||||
|
|
||||||
private void appendOemFmt(ByteArrayOutputStream out) {
|
|
||||||
// HOD DS3270.java line 1814: 4 distinct OEM format sub-fields
|
|
||||||
appendQueryReply(out, QR_OEM_FMT, new byte[]{
|
|
||||||
0x00, 0x03, 0x02, (byte) 0x90, 0x09, 0x01, 0x00, 0x03, 0x02, (byte) 0x80, 0x00, 0x7F, (byte) 0xFF
|
0x00, 0x03, 0x02, (byte) 0x90, 0x09, 0x01, 0x00, 0x03, 0x02, (byte) 0x80, 0x00, 0x7F, (byte) 0xFF
|
||||||
});
|
});
|
||||||
appendQueryReply(out, QR_OEM_FMT, new byte[]{
|
appendQueryReply(out, QR_PORT, new byte[]{
|
||||||
0x00, 0x04, 0x08, 0x40, 0x07, 0x03, 0x00, 0x03, (byte) 0x80, 0x00, 0x02
|
0x00, 0x04, 0x08, 0x40, 0x07, 0x03, 0x00, 0x03, (byte) 0x80, 0x00, 0x02
|
||||||
});
|
});
|
||||||
appendQueryReply(out, QR_OEM_FMT, new byte[]{
|
appendQueryReply(out, QR_PORT, new byte[]{
|
||||||
0x00, 0x05, 0x02, (byte) 0x80, 0x09, 0x01, 0x00, 0x01, 0x02, (byte) 0x80, 0x00, 0x7F, (byte) 0xFF
|
0x00, 0x05, 0x02, (byte) 0x80, 0x09, 0x01, 0x00, 0x01, 0x02, (byte) 0x80, 0x00, 0x7F, (byte) 0xFF
|
||||||
});
|
});
|
||||||
appendQueryReply(out, QR_OEM_FMT, new byte[]{
|
appendQueryReply(out, QR_PORT, new byte[]{
|
||||||
0x00, 0x07, 0x08, 0x40, 0x07, 0x03, 0x00, 0x01, 0x00, 0x00, 0x1C
|
0x00, 0x07, 0x08, 0x40, 0x07, 0x03, 0x00, 0x01, 0x00, 0x00, 0x1C
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] buildGColor() {
|
public void appendOemFmt(ByteArrayOutputStream out) {
|
||||||
|
appendPort(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildPort() {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream(70);
|
||||||
|
appendPort(out);
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildOemFormat() {
|
||||||
|
return buildPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildGrColor() {
|
||||||
|
// HOD QueryReply3270Constants.java QR_GRCOLOR_STRING (109 bytes total)
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream(110);
|
ByteArrayOutputStream out = new ByteArrayOutputStream(110);
|
||||||
out.write(0x00); out.write(0x04); out.write(0x00); out.write(0xFF); out.write(0xFF);
|
out.write(0x00); out.write(0x04); out.write(0x00); out.write(0xFF); out.write(0xFF);
|
||||||
out.write(0x00); out.write(0x10); out.write(0x00); out.write(0x10);
|
out.write(0x00); out.write(0x10); out.write(0x00); out.write(0x10);
|
||||||
@@ -505,11 +654,39 @@ public class QueryReplyBuilder {
|
|||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] buildGSymbols() {
|
public byte[] buildGraphicColor() {
|
||||||
|
return buildGrColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildGColor() {
|
||||||
|
return buildGrColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildGrSymbolSet() {
|
||||||
|
int charW = getCharWidth();
|
||||||
|
int charH = getCharHeight();
|
||||||
|
int cgcsgid = 0x02B9;
|
||||||
|
int cpgid = 0x0025;
|
||||||
|
if (screen != null && screen.getTranslator() != null) {
|
||||||
|
cgcsgid = screen.getTranslator().getCgcsgid();
|
||||||
|
cpgid = screen.getTranslator().getCpgid();
|
||||||
|
}
|
||||||
return new byte[]{
|
return new byte[]{
|
||||||
0x00, 0x00, 0x0C, 0x18, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x12,
|
0x00, 0x00, (byte) charW, (byte) charH, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x12,
|
||||||
0x01, 0x00, 0x00, (byte) 0xF0, (byte) 0xC1, (byte) 0xC1, 0x00, 0x00,
|
0x01, 0x00, 0x00, (byte) 0xF0, (byte) ((cgcsgid >> 8) & 0xFF), (byte) (cgcsgid & 0xFF), 0x00, 0x00,
|
||||||
0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
(byte) charW, (byte) charH, 0x00, 0x00, (byte) 0xF0, (byte) ((cgcsgid >> 8) & 0xFF), (byte) (cgcsgid & 0xFF), 0x00, 0x00
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public byte[] buildGSymbols() {
|
||||||
|
return buildGrSymbolSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildSaveRestore() {
|
||||||
|
return buildOutlining();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] buildRpqNames() {
|
||||||
|
return buildTransparency();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package haus.nightmare.lib3270j.eNetwork.ECL;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLConnection drop-in compatibility class.
|
||||||
|
*/
|
||||||
|
public class ECLConnection extends haus.nightmare.lib3270j.ecl.ECLConnection {
|
||||||
|
|
||||||
|
public ECLConnection(haus.nightmare.lib3270j.ecl.ECLSession session, Telnet3270Client client) {
|
||||||
|
super(session, client);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package haus.nightmare.lib3270j.eNetwork.ECL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLConstants compatibility interface.
|
||||||
|
*/
|
||||||
|
public interface ECLConstants extends haus.nightmare.lib3270j.ecl.ECLConstants {
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package haus.nightmare.lib3270j.eNetwork.ECL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLErrors compatibility interface.
|
||||||
|
*/
|
||||||
|
public interface ECLErrors extends haus.nightmare.lib3270j.ecl.ECLErrors {
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package haus.nightmare.lib3270j.eNetwork.ECL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLException compatibility class.
|
||||||
|
*/
|
||||||
|
public class ECLException extends haus.nightmare.lib3270j.ecl.ECLException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public ECLException(int errorCode, String message) {
|
||||||
|
super(errorCode, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLException(int errorCode, String message, Throwable cause) {
|
||||||
|
super(errorCode, message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package haus.nightmare.lib3270j.eNetwork.ECL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLField drop-in compatibility class.
|
||||||
|
*/
|
||||||
|
public class ECLField extends haus.nightmare.lib3270j.ecl.ECLField {
|
||||||
|
|
||||||
|
public ECLField(haus.nightmare.lib3270j.ecl.ECLPS ps, int startPos, int dataStart, int endPos, int length, byte attribute) {
|
||||||
|
super(ps, startPos, dataStart, endPos, length, attribute);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package haus.nightmare.lib3270j.eNetwork.ECL;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLFieldList drop-in compatibility class.
|
||||||
|
*/
|
||||||
|
public class ECLFieldList extends haus.nightmare.lib3270j.ecl.ECLFieldList {
|
||||||
|
|
||||||
|
public ECLFieldList(haus.nightmare.lib3270j.ecl.ECLPS ps, ScreenBuffer screen) {
|
||||||
|
super(ps, screen);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package haus.nightmare.lib3270j.eNetwork.ECL;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.input.InputProcessor;
|
||||||
|
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||||
|
import haus.nightmare.lib3270j.telnet.TelnetFSM;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLOIA drop-in compatibility class.
|
||||||
|
*/
|
||||||
|
public class ECLOIA extends haus.nightmare.lib3270j.ecl.ECLOIA {
|
||||||
|
|
||||||
|
public ECLOIA(ScreenBuffer screen, InputProcessor inputProcessor, TelnetFSM fsm) {
|
||||||
|
super(screen, inputProcessor, fsm);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package haus.nightmare.lib3270j.eNetwork.ECL;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||||
|
import haus.nightmare.lib3270j.input.InputProcessor;
|
||||||
|
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLPS drop-in compatibility class.
|
||||||
|
*/
|
||||||
|
public class ECLPS extends haus.nightmare.lib3270j.ecl.ECLPS {
|
||||||
|
|
||||||
|
public ECLPS(ScreenBuffer screen, InputProcessor inputProcessor, EbcdicTranslator translator) {
|
||||||
|
super(screen, inputProcessor, translator);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package haus.nightmare.lib3270j.eNetwork.ECL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLScreenDesc drop-in compatibility class.
|
||||||
|
*/
|
||||||
|
public class ECLScreenDesc extends haus.nightmare.lib3270j.ecl.ECLScreenDesc {
|
||||||
|
|
||||||
|
public ECLScreenDesc() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package haus.nightmare.lib3270j.eNetwork.ECL;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.ConnectionConfig;
|
||||||
|
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||||
|
import haus.nightmare.lib3270j.TerminalModel;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLSession drop-in compatibility class.
|
||||||
|
*/
|
||||||
|
public class ECLSession extends haus.nightmare.lib3270j.ecl.ECLSession {
|
||||||
|
|
||||||
|
public ECLSession() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSession(ConnectionConfig config) {
|
||||||
|
super(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSession(String host, int port, TerminalModel model) {
|
||||||
|
super(host, port, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSession(String host, int port, TerminalModel model, boolean useTls) {
|
||||||
|
super(host, port, model, useTls);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSession(Properties props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSession(Telnet3270Client client) {
|
||||||
|
super(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package haus.nightmare.lib3270j.eNetwork.ECL;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||||
|
import haus.nightmare.lib3270j.datastream.DataStreamProcessor;
|
||||||
|
import haus.nightmare.lib3270j.input.InputProcessor;
|
||||||
|
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLXfer drop-in compatibility class.
|
||||||
|
*/
|
||||||
|
public class ECLXfer extends haus.nightmare.lib3270j.ecl.ECLXfer {
|
||||||
|
|
||||||
|
public ECLXfer(ScreenBuffer screen, InputProcessor input, DataStreamProcessor dsProcessor, EbcdicTranslator translator) {
|
||||||
|
super(screen, input, dsProcessor, translator);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package haus.nightmare.lib3270j.eNetwork.ECL.event;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.ConnectionState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLCommEvent compatibility class.
|
||||||
|
*/
|
||||||
|
public class ECLCommEvent extends haus.nightmare.lib3270j.ecl.ECLCommEvent {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public ECLCommEvent(Object source, int eventType, ConnectionState oldState, ConnectionState newState,
|
||||||
|
String message, String deviceType, String deviceName) {
|
||||||
|
super(source, eventType, oldState, newState, message, deviceType, deviceName);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package haus.nightmare.lib3270j.eNetwork.ECL.event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLCommListener compatibility interface.
|
||||||
|
*/
|
||||||
|
public interface ECLCommListener extends haus.nightmare.lib3270j.ecl.ECLCommListener {
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package haus.nightmare.lib3270j.eNetwork.ECL.event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLCommNotify compatibility interface.
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ECLCommNotify extends haus.nightmare.lib3270j.ecl.ECLCommNotify {
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package haus.nightmare.lib3270j.eNetwork.ECL.event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLOIAEvent compatibility class.
|
||||||
|
*/
|
||||||
|
public class ECLOIAEvent extends haus.nightmare.lib3270j.ecl.ECLOIAEvent {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public ECLOIAEvent(Object source, int eventType, int inputInhibited, int alphanumericType,
|
||||||
|
boolean insertMode, String statusString) {
|
||||||
|
super(source, eventType, inputInhibited, alphanumericType, insertMode, statusString);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package haus.nightmare.lib3270j.eNetwork.ECL.event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLOIAListener compatibility interface.
|
||||||
|
*/
|
||||||
|
public interface ECLOIAListener extends haus.nightmare.lib3270j.ecl.ECLOIAListener {
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package haus.nightmare.lib3270j.eNetwork.ECL.event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLPSEvent compatibility class.
|
||||||
|
*/
|
||||||
|
public class ECLPSEvent extends haus.nightmare.lib3270j.ecl.ECLPSEvent {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public ECLPSEvent(Object source, int eventType, int startRow, int startCol, int endRow, int endCol,
|
||||||
|
int oldCursorAddress, int newCursorAddress, int rows, int cols, boolean fullUpdate) {
|
||||||
|
super(source, eventType, startRow, startCol, endRow, endCol, oldCursorAddress, newCursorAddress, rows, cols, fullUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLPSEvent(Object source, int eventType) {
|
||||||
|
super(source, eventType);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package haus.nightmare.lib3270j.eNetwork.ECL.event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLPSListener compatibility interface.
|
||||||
|
*/
|
||||||
|
public interface ECLPSListener extends haus.nightmare.lib3270j.ecl.ECLPSListener {
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package haus.nightmare.lib3270j.ecl;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.ConnectionState;
|
||||||
|
import java.util.EventObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event object dispatched on communication lifecycle and state transitions.
|
||||||
|
*/
|
||||||
|
public class ECLCommEvent extends EventObject {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public static final int COMM_CONNECTING = 1;
|
||||||
|
public static final int COMM_CONNECTED = 2;
|
||||||
|
public static final int COMM_DISCONNECTED = 3;
|
||||||
|
public static final int COMM_STATE_CHANGED = 4;
|
||||||
|
public static final int COMM_ERROR = 5;
|
||||||
|
public static final int COMM_UNBOUND = 6;
|
||||||
|
public static final int COMM_BIND = 7;
|
||||||
|
|
||||||
|
public static final int EVENT_CONNECTING = COMM_CONNECTING;
|
||||||
|
public static final int EVENT_CONNECTED = COMM_CONNECTED;
|
||||||
|
public static final int EVENT_DISCONNECTED = COMM_DISCONNECTED;
|
||||||
|
public static final int EVENT_STATE_CHANGED = COMM_STATE_CHANGED;
|
||||||
|
public static final int EVENT_ERROR = COMM_ERROR;
|
||||||
|
public static final int EVENT_UNBOUND = COMM_UNBOUND;
|
||||||
|
public static final int EVENT_BIND = COMM_BIND;
|
||||||
|
|
||||||
|
private final int eventType;
|
||||||
|
private final ConnectionState oldState;
|
||||||
|
private final ConnectionState newState;
|
||||||
|
private final String message;
|
||||||
|
private final String deviceType;
|
||||||
|
private final String deviceName;
|
||||||
|
|
||||||
|
public ECLCommEvent(Object source, int eventType, ConnectionState oldState, ConnectionState newState,
|
||||||
|
String message, String deviceType, String deviceName) {
|
||||||
|
super(source);
|
||||||
|
this.eventType = eventType;
|
||||||
|
this.oldState = oldState;
|
||||||
|
this.newState = newState;
|
||||||
|
this.message = message;
|
||||||
|
this.deviceType = deviceType;
|
||||||
|
this.deviceName = deviceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEventType() { return eventType; }
|
||||||
|
public ConnectionState getOldState() { return oldState; }
|
||||||
|
public ConnectionState getNewState() { return newState; }
|
||||||
|
public String getMessage() { return message; }
|
||||||
|
public String getErrorMessage() { return message; }
|
||||||
|
public String getDeviceType() { return deviceType; }
|
||||||
|
public String getDeviceName() { return deviceName; }
|
||||||
|
public String getLUName() { return deviceName; }
|
||||||
|
|
||||||
|
public boolean isConnected() {
|
||||||
|
return newState != null && newState.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFullSession() {
|
||||||
|
return newState != null && newState.isFullSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLConnection getConnection() {
|
||||||
|
return (getSource() instanceof ECLConnection) ? (ECLConnection) getSource() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("ECLCommEvent[type=%d, oldState=%s, newState=%s, msg='%s', dev='%s', lu='%s']",
|
||||||
|
eventType, oldState, newState, message, deviceType, deviceName);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package haus.nightmare.lib3270j.ecl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener interface for communication lifecycle events.
|
||||||
|
*/
|
||||||
|
public interface ECLCommListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the communication connection state or status changes.
|
||||||
|
* @param event ECLCommEvent containing transition details
|
||||||
|
*/
|
||||||
|
void commEvent(ECLCommEvent event);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called specifically when the connection is established.
|
||||||
|
* @param event ECLCommEvent
|
||||||
|
*/
|
||||||
|
default void commConnected(ECLCommEvent event) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called specifically when the connection is disconnected.
|
||||||
|
* @param event ECLCommEvent
|
||||||
|
*/
|
||||||
|
default void commDisconnected(ECLCommEvent event) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called specifically when a communication error occurs.
|
||||||
|
* @param event ECLCommEvent
|
||||||
|
*/
|
||||||
|
default void commError(ECLCommEvent event) {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package haus.nightmare.lib3270j.ecl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLCommNotify callback interface.
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ECLCommNotify {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notification callback invoked on connection state change.
|
||||||
|
* @param connected true if session is connected, false otherwise
|
||||||
|
*/
|
||||||
|
void CommNotify(boolean connected);
|
||||||
|
}
|
||||||
@@ -0,0 +1,230 @@
|
|||||||
|
package haus.nightmare.lib3270j.ecl;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.ConnectionState;
|
||||||
|
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||||
|
import haus.nightmare.lib3270j.TerminalModel;
|
||||||
|
import haus.nightmare.lib3270j.listener.ConnectionListener;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Communication and session connection state management conforming to IBM Host On-Demand ECL.
|
||||||
|
*/
|
||||||
|
public class ECLConnection {
|
||||||
|
|
||||||
|
private final ECLSession session;
|
||||||
|
private final Telnet3270Client client;
|
||||||
|
private final List<ECLCommListener> commListeners = new CopyOnWriteArrayList<>();
|
||||||
|
private final List<ECLCommNotify> commNotifies = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
|
public ECLConnection(ECLSession session, Telnet3270Client client) {
|
||||||
|
this.session = session;
|
||||||
|
this.client = client;
|
||||||
|
|
||||||
|
if (client != null) {
|
||||||
|
client.addConnectionListener(new ConnectionListener() {
|
||||||
|
@Override
|
||||||
|
public void onConnectionStateChanged(ConnectionState oldState, ConnectionState newState) {
|
||||||
|
int eventType = ECLCommEvent.COMM_STATE_CHANGED;
|
||||||
|
if (newState.isFullSession()) {
|
||||||
|
eventType = ECLCommEvent.COMM_CONNECTED;
|
||||||
|
} else if (newState == ConnectionState.NOT_CONNECTED) {
|
||||||
|
eventType = ECLCommEvent.COMM_DISCONNECTED;
|
||||||
|
} else if (newState.isHalfConnected()) {
|
||||||
|
eventType = ECLCommEvent.COMM_CONNECTING;
|
||||||
|
}
|
||||||
|
|
||||||
|
String devType = (client.getTelnetFSM() != null) ? client.getTelnetFSM().getConnectedType() : null;
|
||||||
|
String lu = (client.getTelnetFSM() != null) ? client.getTelnetFSM().getConnectedLu() : null;
|
||||||
|
ECLCommEvent event = new ECLCommEvent(ECLConnection.this, eventType, oldState, newState,
|
||||||
|
"State: " + newState, devType, lu);
|
||||||
|
|
||||||
|
notifyCommEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnectionError(String message) {
|
||||||
|
ConnectionState state = client.getConnectionState();
|
||||||
|
ECLCommEvent event = new ECLCommEvent(ECLConnection.this, ECLCommEvent.COMM_ERROR,
|
||||||
|
state, state, message, null, null);
|
||||||
|
notifyCommEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTN3270ENegotiated(String deviceType, String deviceName) {
|
||||||
|
ConnectionState state = client.getConnectionState();
|
||||||
|
ECLCommEvent event = new ECLCommEvent(ECLConnection.this, ECLCommEvent.COMM_BIND,
|
||||||
|
state, state, "TN3270E Negotiated", deviceType, deviceName);
|
||||||
|
notifyCommEvent(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSession GetSession() { return session; }
|
||||||
|
public ECLSession getSession() { return session; }
|
||||||
|
|
||||||
|
public Telnet3270Client GetClient() { return client; }
|
||||||
|
public Telnet3270Client getClient() { return client; }
|
||||||
|
|
||||||
|
public String GetHost() {
|
||||||
|
return (client != null && client.getConfig() != null) ? client.getConfig().getHost() : "";
|
||||||
|
}
|
||||||
|
public String getHost() { return GetHost(); }
|
||||||
|
|
||||||
|
public int GetPort() {
|
||||||
|
return (client != null && client.getConfig() != null) ? client.getConfig().getPort() : 23;
|
||||||
|
}
|
||||||
|
public int getPort() { return GetPort(); }
|
||||||
|
|
||||||
|
public String GetCodePage() {
|
||||||
|
return (client != null) ? client.getCodePage() : "037";
|
||||||
|
}
|
||||||
|
public String getCodePage() { return GetCodePage(); }
|
||||||
|
|
||||||
|
public TerminalModel GetModel() {
|
||||||
|
return (client != null && client.getConfig() != null) ? client.getConfig().getModel() : TerminalModel.IBM_3279_4;
|
||||||
|
}
|
||||||
|
public TerminalModel getModel() { return GetModel(); }
|
||||||
|
|
||||||
|
public String GetLUName() {
|
||||||
|
if (client != null && client.getTelnetFSM() != null && client.getTelnetFSM().getConnectedLu() != null) {
|
||||||
|
return client.getTelnetFSM().getConnectedLu();
|
||||||
|
}
|
||||||
|
return (client != null && client.getConfig() != null) ? client.getConfig().getLuName() : null;
|
||||||
|
}
|
||||||
|
public String getLUName() { return GetLUName(); }
|
||||||
|
|
||||||
|
public String GetDevName() { return GetLUName(); }
|
||||||
|
public String getDevName() { return GetLUName(); }
|
||||||
|
|
||||||
|
public String GetConnType() {
|
||||||
|
if (client == null) return "UNKNOWN";
|
||||||
|
ConnectionState cs = client.getConnectionState();
|
||||||
|
if (cs.isTn3270e()) return "TN3270E";
|
||||||
|
if (cs.is3270()) return "TN3270";
|
||||||
|
if (cs.isNvt()) return "NVT";
|
||||||
|
if (client.getConfig() != null) {
|
||||||
|
return client.getConfig().isTn3270eEnabled() ? "TN3270E" : "TN3270";
|
||||||
|
}
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
public String getConnType() { return GetConnType(); }
|
||||||
|
|
||||||
|
public ConnectionState GetState() {
|
||||||
|
return (client != null) ? client.getConnectionState() : ConnectionState.NOT_CONNECTED;
|
||||||
|
}
|
||||||
|
public ConnectionState getState() { return GetState(); }
|
||||||
|
|
||||||
|
public int GetStateCode() {
|
||||||
|
return GetState().toHoDStateCode();
|
||||||
|
}
|
||||||
|
public int getStateCode() { return GetStateCode(); }
|
||||||
|
|
||||||
|
public boolean IsConnected() {
|
||||||
|
return client != null && client.isConnected();
|
||||||
|
}
|
||||||
|
public boolean isConnected() { return IsConnected(); }
|
||||||
|
|
||||||
|
public boolean IsStarted() {
|
||||||
|
return IsConnected();
|
||||||
|
}
|
||||||
|
public boolean isStarted() { return IsStarted(); }
|
||||||
|
|
||||||
|
public boolean IsReady() {
|
||||||
|
return client != null && client.getConnectionState().isFullSession();
|
||||||
|
}
|
||||||
|
public boolean isReady() { return IsReady(); }
|
||||||
|
|
||||||
|
public boolean IsConnecting() {
|
||||||
|
return client != null && client.getConnectionState().isHalfConnected();
|
||||||
|
}
|
||||||
|
public boolean isConnecting() { return IsConnecting(); }
|
||||||
|
|
||||||
|
public boolean IsDisconnecting() {
|
||||||
|
return client == null || client.getConnectionState() == ConnectionState.NOT_CONNECTED;
|
||||||
|
}
|
||||||
|
public boolean isDisconnecting() { return IsDisconnecting(); }
|
||||||
|
|
||||||
|
public boolean IsSSL() {
|
||||||
|
return client != null && client.getConfig() != null && client.getConfig().isUseTls();
|
||||||
|
}
|
||||||
|
public boolean isSSL() { return IsSSL(); }
|
||||||
|
|
||||||
|
public boolean IsTLS() { return IsSSL(); }
|
||||||
|
public boolean isTLS() { return IsSSL(); }
|
||||||
|
|
||||||
|
public void Connect() throws IOException {
|
||||||
|
if (client != null) {
|
||||||
|
client.connect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void connect() throws IOException { Connect(); }
|
||||||
|
|
||||||
|
public void Disconnect() {
|
||||||
|
if (client != null) {
|
||||||
|
client.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void disconnect() { Disconnect(); }
|
||||||
|
|
||||||
|
public void StartCommunication() throws IOException { Connect(); }
|
||||||
|
public void startCommunication() throws IOException { Connect(); }
|
||||||
|
|
||||||
|
public void StopCommunication() { Disconnect(); }
|
||||||
|
public void stopCommunication() { Disconnect(); }
|
||||||
|
|
||||||
|
// ========== Event Listener Management ==========
|
||||||
|
|
||||||
|
public void RegisterCommEvent(ECLCommListener listener) {
|
||||||
|
if (listener != null && !commListeners.contains(listener)) {
|
||||||
|
commListeners.add(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void registerCommEvent(ECLCommListener listener) { RegisterCommEvent(listener); }
|
||||||
|
|
||||||
|
public void UnregisterCommEvent(ECLCommListener listener) {
|
||||||
|
commListeners.remove(listener);
|
||||||
|
}
|
||||||
|
public void unregisterCommEvent(ECLCommListener listener) { UnregisterCommEvent(listener); }
|
||||||
|
|
||||||
|
public void RegisterCommEvent(ECLCommNotify notify, boolean sync) {
|
||||||
|
if (notify != null && !commNotifies.contains(notify)) {
|
||||||
|
commNotifies.add(notify);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void registerCommEvent(ECLCommNotify notify, boolean sync) { RegisterCommEvent(notify, sync); }
|
||||||
|
|
||||||
|
public void UnregisterCommEvent(ECLCommNotify notify) {
|
||||||
|
commNotifies.remove(notify);
|
||||||
|
}
|
||||||
|
public void unregisterCommEvent(ECLCommNotify notify) { UnregisterCommEvent(notify); }
|
||||||
|
|
||||||
|
private void notifyCommEvent(ECLCommEvent event) {
|
||||||
|
for (ECLCommListener l : commListeners) {
|
||||||
|
try {
|
||||||
|
l.commEvent(event);
|
||||||
|
if (event.getEventType() == ECLCommEvent.COMM_CONNECTED) {
|
||||||
|
l.commConnected(event);
|
||||||
|
} else if (event.getEventType() == ECLCommEvent.COMM_DISCONNECTED) {
|
||||||
|
l.commDisconnected(event);
|
||||||
|
} else if (event.getEventType() == ECLCommEvent.COMM_ERROR) {
|
||||||
|
l.commError(event);
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
for (ECLCommNotify n : commNotifies) {
|
||||||
|
try {
|
||||||
|
n.CommNotify(event.isConnected());
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("ECLConnection[host=%s, port=%d, state=%s, lu=%s, ssl=%b]",
|
||||||
|
GetHost(), GetPort(), GetState(), GetLUName(), IsSSL());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package haus.nightmare.lib3270j.ecl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common constants for IBM Host On-Demand Emulator Class Library (ECL) emulation.
|
||||||
|
*/
|
||||||
|
public interface ECLConstants {
|
||||||
|
|
||||||
|
// Presentation space logical planes
|
||||||
|
int PLANE_TEXT = 1; // Unicode/EBCDIC characters
|
||||||
|
int PLANE_COLOR = 2; // Color attributes
|
||||||
|
int PLANE_HILITE = 3; // Extended highlighting
|
||||||
|
int PLANE_EXTENDED = 4; // Extended character sets / outlining
|
||||||
|
int PLANE_FIELD = 5; // Field attribute definition bytes
|
||||||
|
int PLANE_DBCS = 6; // Double-byte character plane
|
||||||
|
|
||||||
|
// Color definitions (IBM 3279 standard 16-color palette)
|
||||||
|
char COLOR_NEUTRAL_BLACK = 0;
|
||||||
|
char COLOR_BLUE = 1;
|
||||||
|
char COLOR_RED = 2;
|
||||||
|
char COLOR_PINK = 3;
|
||||||
|
char COLOR_GREEN = 4;
|
||||||
|
char COLOR_TURQUOISE = 5;
|
||||||
|
char COLOR_YELLOW = 6;
|
||||||
|
char COLOR_NEUTRAL_WHITE = 7;
|
||||||
|
char COLOR_BLACK = 8;
|
||||||
|
char COLOR_DEEP_BLUE = 9;
|
||||||
|
char COLOR_ORANGE = 10;
|
||||||
|
char COLOR_PURPLE = 11;
|
||||||
|
char COLOR_PALE_GREEN = 12;
|
||||||
|
char COLOR_PALE_TURQUOISE= 13;
|
||||||
|
char COLOR_GREY = 14;
|
||||||
|
char COLOR_WHITE = 15;
|
||||||
|
|
||||||
|
// Highlighting attributes
|
||||||
|
char HILITE_DEFAULT = 0x00;
|
||||||
|
char HILITE_BLINK = 0xF1;
|
||||||
|
char HILITE_REVERSE = 0xF2;
|
||||||
|
char HILITE_UNDERSCORE = 0xF4;
|
||||||
|
|
||||||
|
// Search directions
|
||||||
|
int SEARCH_FORWARD = 1;
|
||||||
|
int SEARCH_BACKWARD = 2;
|
||||||
|
int DIR_FORWARD = 1;
|
||||||
|
int DIR_BACKWARD = 2;
|
||||||
|
|
||||||
|
// OIA Input Inhibited Reason Codes
|
||||||
|
int INHIBIT_NOT_INHIBITED = 0;
|
||||||
|
int INHIBIT_SYSTEM_LOCK = 1; // X SYSTEM (Waiting for host response)
|
||||||
|
int INHIBIT_NUMERIC_ONLY = 2; // X NUMERIC (Non-numeric in numeric field)
|
||||||
|
int INHIBIT_PROTECTED_FIELD = 3; // X PROT (Attempted write into protected field)
|
||||||
|
int INHIBIT_OVERFLOW = 4; // X > (Insert mode buffer overflow)
|
||||||
|
int INHIBIT_COMM_CHECK = 5; // X COMM (Communication or socket check)
|
||||||
|
int INHIBIT_OPERATOR_DUE = 6; // X OP (Operator Intervention Due)
|
||||||
|
|
||||||
|
// Alphanumeric entry types (ECLOIA.getAlphanumericType())
|
||||||
|
int TYPE_ALPHANUMERIC = 0;
|
||||||
|
int TYPE_NUMERIC = 1;
|
||||||
|
int TYPE_DBCS = 2;
|
||||||
|
int ALPHANUMERIC_NORMAL = 0;
|
||||||
|
int ALPHANUMERIC_NUMERIC = 1;
|
||||||
|
int ALPHANUMERIC_DBCS = 2;
|
||||||
|
|
||||||
|
// Status condition flags
|
||||||
|
int STATUS_READY = 0;
|
||||||
|
int STATUS_X_SYSTEM = 1;
|
||||||
|
int STATUS_X_NUM = 2;
|
||||||
|
int STATUS_X_PROT = 3;
|
||||||
|
int STATUS_X_WAIT = 4;
|
||||||
|
int STATUS_X_INSERT = 5;
|
||||||
|
int STATUS_X_COMM = 6;
|
||||||
|
int STATUS_X_OVERFLOW = 7;
|
||||||
|
int STATUS_X_OP = 8;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package haus.nightmare.lib3270j.ecl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common error codes for IBM Host On-Demand Emulator Class Library (ECL) emulation.
|
||||||
|
*/
|
||||||
|
public interface ECLErrors {
|
||||||
|
int ECL_ERR_NONE = 0;
|
||||||
|
int ECL_ERR_COMM_NOT_CONNECTED = 1;
|
||||||
|
int ECL_ERR_COMM_TIMEOUT = 2;
|
||||||
|
int ECL_ERR_COMM_FAILED = 3;
|
||||||
|
int ECL_ERR_SCREEN_MATCH_TIMEOUT = 4;
|
||||||
|
int ECL_ERR_PS_NOT_AVAILABLE = 5;
|
||||||
|
int ECL_ERR_OIA_NOT_AVAILABLE = 6;
|
||||||
|
int ECL_ERR_INVALID_PARAM = 7;
|
||||||
|
int ECL_ERR_INVALID_POSITION = 8;
|
||||||
|
int ECL_ERR_FIELD_NOT_FOUND = 9;
|
||||||
|
int ECL_ERR_KEYBOARD_LOCKED = 10;
|
||||||
|
int ECL_ERR_XFER_FAILED = 11;
|
||||||
|
int ECL_ERR_SESSION_ALREADY_OPEN = 12;
|
||||||
|
int ECL_ERR_SESSION_CLOSED = 13;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package haus.nightmare.lib3270j.ecl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception class for IBM Host On-Demand ECL operations.
|
||||||
|
*/
|
||||||
|
public class ECLException extends RuntimeException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
private final int errorCode;
|
||||||
|
|
||||||
|
public ECLException(int errorCode, String message) {
|
||||||
|
super(message);
|
||||||
|
this.errorCode = errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLException(int errorCode, String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
this.errorCode = errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLException(String message) {
|
||||||
|
this(ECLErrors.ECL_ERR_COMM_FAILED, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getErrorCode() {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ECLException[errorCode=" + errorCode + ", message=" + getMessage() + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,274 @@
|
|||||||
|
package haus.nightmare.lib3270j.ecl;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||||
|
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
|
||||||
|
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a discrete 3270 field within an IBM Host On-Demand Presentation Space.
|
||||||
|
* Provides programmatic inspection and modification of field text and attributes.
|
||||||
|
*/
|
||||||
|
public class ECLField {
|
||||||
|
|
||||||
|
private final ECLPS ps;
|
||||||
|
private final int startPos; // Position of field attribute byte
|
||||||
|
private final int dataStart; // First data position (startPos + 1 wrapped)
|
||||||
|
private final int endPos; // Last data position inclusive
|
||||||
|
private final int length; // Usable data character length
|
||||||
|
private final byte attribute; // Field attribute byte (FA)
|
||||||
|
|
||||||
|
public ECLField(ECLPS ps, int startPos, int dataStart, int endPos, int length, byte attribute) {
|
||||||
|
this.ps = ps;
|
||||||
|
this.startPos = startPos;
|
||||||
|
this.dataStart = dataStart;
|
||||||
|
this.endPos = endPos;
|
||||||
|
this.length = length;
|
||||||
|
this.attribute = attribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Buffer address of the field attribute character. */
|
||||||
|
public int getStart() { return startPos; }
|
||||||
|
public int GetStart() { return getStart(); }
|
||||||
|
|
||||||
|
/** First buffer address of the field data (start + 1). */
|
||||||
|
public int getDataStart() { return dataStart; }
|
||||||
|
public int GetDataStart() { return getDataStart(); }
|
||||||
|
|
||||||
|
/** Last buffer address of the field data inclusive. */
|
||||||
|
public int getEnd() { return endPos; }
|
||||||
|
public int GetEnd() { return getEnd(); }
|
||||||
|
|
||||||
|
/** Number of data characters in the field. */
|
||||||
|
public int getLength() { return length; }
|
||||||
|
public int GetLength() { return getLength(); }
|
||||||
|
|
||||||
|
public int getStartRow() {
|
||||||
|
int cols = ps.getCols();
|
||||||
|
return cols > 0 ? startPos / cols : 0;
|
||||||
|
}
|
||||||
|
public int GetStartRow() { return getStartRow(); }
|
||||||
|
|
||||||
|
public int getStartCol() {
|
||||||
|
int cols = ps.getCols();
|
||||||
|
return cols > 0 ? startPos % cols : 0;
|
||||||
|
}
|
||||||
|
public int GetStartCol() { return getStartCol(); }
|
||||||
|
|
||||||
|
public int getEndRow() {
|
||||||
|
int cols = ps.getCols();
|
||||||
|
return cols > 0 ? endPos / cols : 0;
|
||||||
|
}
|
||||||
|
public int GetEndRow() { return getEndRow(); }
|
||||||
|
|
||||||
|
public int getEndCol() {
|
||||||
|
int cols = ps.getCols();
|
||||||
|
return cols > 0 ? endPos % cols : 0;
|
||||||
|
}
|
||||||
|
public int GetEndCol() { return getEndCol(); }
|
||||||
|
|
||||||
|
private byte getLiveAttribute() {
|
||||||
|
if (ps != null && ps.getScreenBuffer() != null) {
|
||||||
|
ExtendedAttribute cell = ps.getScreenBuffer().getCell(startPos);
|
||||||
|
if (cell.isFieldAttribute()) {
|
||||||
|
return cell.fa;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return attribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isModified() {
|
||||||
|
return faIsModified(getLiveAttribute() & 0xFF);
|
||||||
|
}
|
||||||
|
public boolean IsModified() { return isModified(); }
|
||||||
|
|
||||||
|
public boolean isProtected() {
|
||||||
|
return faIsProtected(getLiveAttribute() & 0xFF);
|
||||||
|
}
|
||||||
|
public boolean IsProtected() { return isProtected(); }
|
||||||
|
|
||||||
|
public boolean isNumeric() {
|
||||||
|
return faIsNumeric(getLiveAttribute() & 0xFF);
|
||||||
|
}
|
||||||
|
public boolean IsNumeric() { return isNumeric(); }
|
||||||
|
|
||||||
|
public boolean isHighIntensity() {
|
||||||
|
return faIsHigh(getLiveAttribute() & 0xFF);
|
||||||
|
}
|
||||||
|
public boolean IsHighIntensity() { return isHighIntensity(); }
|
||||||
|
|
||||||
|
public boolean isHidden() {
|
||||||
|
return faIsZero(getLiveAttribute() & 0xFF);
|
||||||
|
}
|
||||||
|
public boolean IsHidden() { return isHidden(); }
|
||||||
|
|
||||||
|
public boolean isDisplay() {
|
||||||
|
return !isHidden();
|
||||||
|
}
|
||||||
|
public boolean IsDisplay() { return isDisplay(); }
|
||||||
|
|
||||||
|
public boolean isPenSelectable() {
|
||||||
|
return faIsSelectable(getLiveAttribute() & 0xFF);
|
||||||
|
}
|
||||||
|
public boolean IsPenSelectable() { return isPenSelectable(); }
|
||||||
|
|
||||||
|
public short getAttribute() {
|
||||||
|
return (short) (getLiveAttribute() & 0xFF);
|
||||||
|
}
|
||||||
|
public short GetAttribute() { return getAttribute(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the text contents of this field as a String.
|
||||||
|
*/
|
||||||
|
public String getText() {
|
||||||
|
if (length <= 0) return "";
|
||||||
|
return ps.getString(dataStart, length);
|
||||||
|
}
|
||||||
|
public String GetText() { return getText(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the text contents of this field.
|
||||||
|
*/
|
||||||
|
public void setText(String text) {
|
||||||
|
if (isProtected() || length <= 0) return;
|
||||||
|
ps.setText(text, dataStart);
|
||||||
|
}
|
||||||
|
public void SetText(String text) { setText(text); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get selector light pen type.
|
||||||
|
* Returns '?' for selectable, '>' for selected, ' ' for space/unselectable.
|
||||||
|
*/
|
||||||
|
public char getSelectorPenType() {
|
||||||
|
if (length <= 0) return ' ';
|
||||||
|
String t = getText();
|
||||||
|
if (!t.isEmpty()) {
|
||||||
|
char first = t.charAt(0);
|
||||||
|
if (first == '?' || first == '>') return first;
|
||||||
|
}
|
||||||
|
return ' ';
|
||||||
|
}
|
||||||
|
public char GetSelectorPenType() { return getSelectorPenType(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actuate lightpen selection on this field ('?' -> '>').
|
||||||
|
*/
|
||||||
|
public void selectField() {
|
||||||
|
if (isProtected() || length <= 0) return;
|
||||||
|
String t = getText();
|
||||||
|
if (!t.isEmpty() && t.charAt(0) == '?') {
|
||||||
|
setText(">" + (t.length() > 1 ? t.substring(1) : ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void SelectField() { selectField(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deselect lightpen selection on this field ('>' -> '?').
|
||||||
|
*/
|
||||||
|
public void deSelectField() {
|
||||||
|
if (isProtected() || length <= 0) return;
|
||||||
|
String t = getText();
|
||||||
|
if (!t.isEmpty() && t.charAt(0) == '>') {
|
||||||
|
setText("?" + (t.length() > 1 ? t.substring(1) : ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void DeSelectField() { deSelectField(); }
|
||||||
|
|
||||||
|
/** Return true if this field wraps from bottom of screen to top. */
|
||||||
|
public boolean isWrapped() {
|
||||||
|
return startPos > endPos;
|
||||||
|
}
|
||||||
|
public boolean IsWrapped() { return isWrapped(); }
|
||||||
|
|
||||||
|
/** Last data buffer address (same as getEnd). */
|
||||||
|
public int getDataEnd() {
|
||||||
|
return endPos;
|
||||||
|
}
|
||||||
|
public int GetDataEnd() { return getDataEnd(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the specified buffer address is contained within this field (including its FA).
|
||||||
|
*/
|
||||||
|
public boolean contains(int pos) {
|
||||||
|
if (ps == null) return false;
|
||||||
|
int size = ps.getSize();
|
||||||
|
if (size <= 0) return false;
|
||||||
|
pos = ((pos % size) + size) % size;
|
||||||
|
|
||||||
|
if (startPos <= endPos) {
|
||||||
|
return pos >= startPos && pos <= endPos;
|
||||||
|
} else {
|
||||||
|
// Wrapped field across screen boundary
|
||||||
|
return pos >= startPos || pos <= endPos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public boolean Contains(int pos) { return contains(pos); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the specified row and column is contained within this field.
|
||||||
|
*/
|
||||||
|
public boolean contains(int row, int col) {
|
||||||
|
if (ps == null) return false;
|
||||||
|
int cols = ps.getCols();
|
||||||
|
return contains(row * cols + col);
|
||||||
|
}
|
||||||
|
public boolean Contains(int row, int col) { return contains(row, col); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the Modified Data Tag (MDT) for this field.
|
||||||
|
*/
|
||||||
|
public void setModified(boolean modified) {
|
||||||
|
if (ps != null && ps.getScreenBuffer() != null) {
|
||||||
|
ExtendedAttribute cell = ps.getScreenBuffer().getCell(startPos);
|
||||||
|
if (cell.isFieldAttribute()) {
|
||||||
|
if (modified) {
|
||||||
|
cell.fa = (byte) (cell.fa | FA_MODIFY);
|
||||||
|
} else {
|
||||||
|
cell.fa = (byte) (cell.fa & ~FA_MODIFY);
|
||||||
|
}
|
||||||
|
ps.getScreenBuffer().markAllChanged();
|
||||||
|
ps.getScreenBuffer().updateDisplaySnapshot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void SetModified(boolean modified) { setModified(modified); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erase all character data within this field to nulls.
|
||||||
|
*/
|
||||||
|
public void erase() {
|
||||||
|
if (isProtected() || length <= 0 || ps == null || ps.getScreenBuffer() == null) return;
|
||||||
|
ScreenBuffer sb = ps.getScreenBuffer();
|
||||||
|
int size = sb.getRows() * sb.getCols();
|
||||||
|
if (size <= 0) return;
|
||||||
|
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
int addr = (dataStart + i) % size;
|
||||||
|
ExtendedAttribute ea = sb.getCell(addr);
|
||||||
|
ea.ec = 0;
|
||||||
|
ea.ucs4 = 0;
|
||||||
|
}
|
||||||
|
setModified(false);
|
||||||
|
sb.markAllChanged();
|
||||||
|
sb.updateDisplaySnapshot();
|
||||||
|
}
|
||||||
|
public void Erase() { erase(); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (!(o instanceof ECLField)) return false;
|
||||||
|
ECLField other = (ECLField) o;
|
||||||
|
return this.startPos == other.startPos && this.endPos == other.endPos && this.length == other.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return java.util.Objects.hash(startPos, endPos, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("ECLField[start=%d, end=%d, len=%d, prot=%b, mod=%b, text=\"%s\"]",
|
||||||
|
startPos, endPos, length, isProtected(), isModified(), getText());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
package haus.nightmare.lib3270j.ecl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||||
|
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages the collection of all 3270 fields currently present in the Presentation Space.
|
||||||
|
* Conforms to IBM ECL ECLFieldList specification.
|
||||||
|
*/
|
||||||
|
public class ECLFieldList {
|
||||||
|
|
||||||
|
private final ECLPS ps;
|
||||||
|
private final ScreenBuffer screen;
|
||||||
|
private final List<ECLField> fields = new ArrayList<>();
|
||||||
|
|
||||||
|
public ECLFieldList(ECLPS ps, ScreenBuffer screen) {
|
||||||
|
this.ps = ps;
|
||||||
|
this.screen = screen;
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the field list by scanning the presentation space buffer.
|
||||||
|
*/
|
||||||
|
public synchronized void refresh() {
|
||||||
|
fields.clear();
|
||||||
|
if (screen == null || !screen.isFormatted()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rows = screen.getRows();
|
||||||
|
int cols = screen.getCols();
|
||||||
|
int size = rows * cols;
|
||||||
|
if (size <= 0) return;
|
||||||
|
|
||||||
|
// Collect all field attribute positions
|
||||||
|
List<Integer> faPositions = new ArrayList<>();
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
ExtendedAttribute ea = screen.getCell(i);
|
||||||
|
if (ea.isFieldAttribute()) {
|
||||||
|
faPositions.add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (faPositions.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int numFields = faPositions.size();
|
||||||
|
for (int i = 0; i < numFields; i++) {
|
||||||
|
int startPos = faPositions.get(i);
|
||||||
|
int nextFaPos = faPositions.get((i + 1) % numFields);
|
||||||
|
|
||||||
|
int dataStart = (startPos + 1) % size;
|
||||||
|
int endPos = (nextFaPos == 0) ? size - 1 : nextFaPos - 1;
|
||||||
|
|
||||||
|
int len;
|
||||||
|
if (nextFaPos > startPos) {
|
||||||
|
len = nextFaPos - startPos - 1;
|
||||||
|
} else {
|
||||||
|
len = (size - startPos - 1) + nextFaPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte faVal = screen.getCell(startPos).fa;
|
||||||
|
fields.add(new ECLField(ps, startPos, dataStart, endPos, len, faVal));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Refresh() { refresh(); }
|
||||||
|
|
||||||
|
public synchronized int getFieldCount() {
|
||||||
|
return fields.size();
|
||||||
|
}
|
||||||
|
public int GetFieldCount() { return getFieldCount(); }
|
||||||
|
|
||||||
|
public synchronized List<ECLField> getFields() {
|
||||||
|
return Collections.unmodifiableList(new ArrayList<>(fields));
|
||||||
|
}
|
||||||
|
public List<ECLField> GetFields() { return getFields(); }
|
||||||
|
|
||||||
|
public synchronized ECLField getFirstField() {
|
||||||
|
if (fields.isEmpty()) return null;
|
||||||
|
return fields.get(0);
|
||||||
|
}
|
||||||
|
public ECLField GetFirstField() { return getFirstField(); }
|
||||||
|
|
||||||
|
public synchronized ECLField getNextField(ECLField prev) {
|
||||||
|
if (prev == null || fields.isEmpty()) return getFirstField();
|
||||||
|
for (int i = 0; i < fields.size(); i++) {
|
||||||
|
ECLField f = fields.get(i);
|
||||||
|
if (f.equals(prev) || f.getStart() == prev.getStart()) {
|
||||||
|
if (i + 1 < fields.size()) {
|
||||||
|
return fields.get(i + 1);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
public ECLField GetNextField(ECLField prev) { return getNextField(prev); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the field that contains the specified buffer position.
|
||||||
|
*/
|
||||||
|
public synchronized ECLField findField(int pos) {
|
||||||
|
if (fields.isEmpty() || screen == null) return null;
|
||||||
|
int size = screen.getRows() * screen.getCols();
|
||||||
|
if (size <= 0) return null;
|
||||||
|
pos = ((pos % size) + size) % size;
|
||||||
|
|
||||||
|
for (ECLField f : fields) {
|
||||||
|
int start = f.getStart();
|
||||||
|
int end = f.getEnd();
|
||||||
|
if (start <= end) {
|
||||||
|
if (pos >= start && pos <= end) return f;
|
||||||
|
} else {
|
||||||
|
// Wrapped field
|
||||||
|
if (pos >= start || pos <= end) return f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
public ECLField FindField(int pos) { return findField(pos); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the field at the specified 0-indexed row and column.
|
||||||
|
*/
|
||||||
|
public ECLField findField(int row, int col) {
|
||||||
|
if (screen == null) return null;
|
||||||
|
int cols = screen.getCols();
|
||||||
|
return findField(row * cols + col);
|
||||||
|
}
|
||||||
|
public ECLField FindField(int row, int col) { return findField(row, col); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the field preceding the given field in the field list.
|
||||||
|
*/
|
||||||
|
public synchronized ECLField getPreviousField(ECLField next) {
|
||||||
|
if (next == null || fields.isEmpty()) return null;
|
||||||
|
for (int i = 0; i < fields.size(); i++) {
|
||||||
|
ECLField f = fields.get(i);
|
||||||
|
if (f.equals(next) || f.getStart() == next.getStart()) {
|
||||||
|
if (i > 0) {
|
||||||
|
return fields.get(i - 1);
|
||||||
|
} else {
|
||||||
|
return fields.get(fields.size() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
public ECLField GetPreviousField(ECLField next) { return getPreviousField(next); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the field at the given buffer position (alias for findField).
|
||||||
|
*/
|
||||||
|
public ECLField findFieldAt(int pos) {
|
||||||
|
return findField(pos);
|
||||||
|
}
|
||||||
|
public ECLField FindFieldAt(int pos) { return findFieldAt(pos); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the field at the given row and column.
|
||||||
|
*/
|
||||||
|
public ECLField findFieldAt(int row, int col) {
|
||||||
|
return findField(row, col);
|
||||||
|
}
|
||||||
|
public ECLField FindFieldAt(int row, int col) { return findFieldAt(row, col); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the field preceding the one at the given buffer position.
|
||||||
|
*/
|
||||||
|
public synchronized ECLField findPrevField(int pos) {
|
||||||
|
ECLField curr = findField(pos);
|
||||||
|
if (curr == null) return null;
|
||||||
|
return getPreviousField(curr);
|
||||||
|
}
|
||||||
|
public ECLField FindPrevField(int pos) { return findPrevField(pos); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the field succeeding the one at the given buffer position.
|
||||||
|
*/
|
||||||
|
public synchronized ECLField findNextField(int pos) {
|
||||||
|
ECLField curr = findField(pos);
|
||||||
|
if (curr == null) return null;
|
||||||
|
return getNextField(curr);
|
||||||
|
}
|
||||||
|
public ECLField FindNextField(int pos) { return findNextField(pos); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find field containing the given text string.
|
||||||
|
*/
|
||||||
|
public synchronized ECLField findField(String text, int startPos) {
|
||||||
|
if (text == null || text.isEmpty() || fields.isEmpty()) return null;
|
||||||
|
int size = screen.getRows() * screen.getCols();
|
||||||
|
if (size <= 0) return null;
|
||||||
|
startPos = ((startPos % size) + size) % size;
|
||||||
|
|
||||||
|
// Find starting index in field list
|
||||||
|
int startIdx = 0;
|
||||||
|
for (int i = 0; i < fields.size(); i++) {
|
||||||
|
if (fields.get(i).getStart() >= startPos) {
|
||||||
|
startIdx = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = startIdx; i < fields.size(); i++) {
|
||||||
|
ECLField f = fields.get(i);
|
||||||
|
if (f.getText().contains(text)) {
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Wrap around search to beginning of field list
|
||||||
|
for (int i = 0; i < startIdx; i++) {
|
||||||
|
ECLField f = fields.get(i);
|
||||||
|
if (f.getText().contains(text)) {
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
public ECLField FindField(String text, int startPos) { return findField(text, startPos); }
|
||||||
|
}
|
||||||
@@ -0,0 +1,365 @@
|
|||||||
|
package haus.nightmare.lib3270j.ecl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.input.InputProcessor;
|
||||||
|
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||||
|
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
|
||||||
|
import haus.nightmare.lib3270j.telnet.TelnetFSM;
|
||||||
|
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Operator Information Area (OIA) status engine matching IBM Host On-Demand ECL specification.
|
||||||
|
* Provides synchronization primitives (waitForInput, waitForSysAvailable) and status inspection.
|
||||||
|
*/
|
||||||
|
public class ECLOIA implements ECLConstants {
|
||||||
|
|
||||||
|
private final ScreenBuffer screen;
|
||||||
|
private final InputProcessor inputProcessor;
|
||||||
|
private final TelnetFSM fsm;
|
||||||
|
private final List<ECLOIANotify> listeners = new ArrayList<>();
|
||||||
|
private final List<ECLOIAListener> oiaListeners = new java.util.concurrent.CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
|
public interface ECLOIANotify {
|
||||||
|
void onOIAChanged(ECLOIA oia);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLOIA(ScreenBuffer screen, InputProcessor inputProcessor, TelnetFSM fsm) {
|
||||||
|
this.screen = screen;
|
||||||
|
this.inputProcessor = inputProcessor;
|
||||||
|
this.fsm = fsm;
|
||||||
|
|
||||||
|
if (inputProcessor != null) {
|
||||||
|
inputProcessor.setLockStateListener(locked -> notifyOIAChanged());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void registerOIAEvent(ECLOIANotify listener) {
|
||||||
|
if (listener != null && !listeners.contains(listener)) {
|
||||||
|
listeners.add(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void unregisterOIAEvent(ECLOIANotify listener) {
|
||||||
|
listeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterOIAEvent(ECLOIAListener listener) {
|
||||||
|
if (listener != null && !oiaListeners.contains(listener)) {
|
||||||
|
oiaListeners.add(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnregisterOIAEvent(ECLOIAListener listener) {
|
||||||
|
oiaListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerOIAListener(ECLOIAListener listener) {
|
||||||
|
RegisterOIAEvent(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterOIAListener(ECLOIAListener listener) {
|
||||||
|
UnregisterOIAEvent(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void notifyOIAChanged() {
|
||||||
|
for (ECLOIANotify l : listeners) {
|
||||||
|
try {
|
||||||
|
l.onOIAChanged(this);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
ECLOIAEvent event = new ECLOIAEvent(this, ECLOIAEvent.OIA_UPDATE, getInputInhibited(),
|
||||||
|
getAlphanumericType(), isInsertMode(), getStatusString());
|
||||||
|
for (ECLOIAListener l : oiaListeners) {
|
||||||
|
try {
|
||||||
|
l.oiaChanged(event);
|
||||||
|
if (inputProcessor != null) {
|
||||||
|
l.oiaLockStateChanged(event);
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInsertMode() {
|
||||||
|
return inputProcessor != null && inputProcessor.isInsertMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean IsInsertMode() {
|
||||||
|
return isInsertMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNumeric() {
|
||||||
|
if (screen == null || !screen.isFormatted()) return false;
|
||||||
|
byte fa = screen.getFieldAttributeAt(screen.getCursorAddress());
|
||||||
|
return faIsNumeric(fa & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean IsNumeric() {
|
||||||
|
return isNumeric();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAlphanumeric() {
|
||||||
|
return !isNumeric();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean IsAlphanumeric() {
|
||||||
|
return isAlphanumeric();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDBCS() {
|
||||||
|
return getAlphanumericType() == TYPE_DBCS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean IsDBCS() {
|
||||||
|
return isDBCS();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMessageWaiting() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean IsMessageWaiting() {
|
||||||
|
return isMessageWaiting();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCommError() {
|
||||||
|
return fsm != null && fsm.getConnectionState() != null && !fsm.getConnectionState().isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean IsCommError() {
|
||||||
|
return isCommError();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int inhibitOverride = -1;
|
||||||
|
|
||||||
|
public void setInputInhibited(int reason) {
|
||||||
|
if (this.inhibitOverride != reason) {
|
||||||
|
this.inhibitOverride = reason;
|
||||||
|
notifyOIAChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetInputInhibited(int reason) {
|
||||||
|
setInputInhibited(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the alphanumeric character entry type allowed at current cursor position.
|
||||||
|
* Returns TYPE_ALPHANUMERIC (0), TYPE_NUMERIC (1), or TYPE_DBCS (2).
|
||||||
|
*/
|
||||||
|
public int getAlphanumericType() {
|
||||||
|
if (screen == null || !screen.isFormatted()) {
|
||||||
|
return TYPE_ALPHANUMERIC;
|
||||||
|
}
|
||||||
|
int cur = screen.getCursorAddress();
|
||||||
|
ExtendedAttribute ea = screen.getCell(cur);
|
||||||
|
if (ea != null && (ea.cs == ExtendedAttribute.CS_DBCS || ea.db != 0)) {
|
||||||
|
return TYPE_DBCS;
|
||||||
|
}
|
||||||
|
byte fa = screen.getFieldAttributeAt(cur);
|
||||||
|
if (faIsNumeric(fa & 0xFF)) {
|
||||||
|
return TYPE_NUMERIC;
|
||||||
|
}
|
||||||
|
return TYPE_ALPHANUMERIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetAlphanumericType() {
|
||||||
|
return getAlphanumericType();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAlphanumericTypeString() {
|
||||||
|
switch (getAlphanumericType()) {
|
||||||
|
case TYPE_NUMERIC: return "N";
|
||||||
|
case TYPE_DBCS: return "D";
|
||||||
|
case TYPE_ALPHANUMERIC:
|
||||||
|
default: return "A";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String GetAlphanumericTypeString() {
|
||||||
|
return getAlphanumericTypeString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isXSystem() {
|
||||||
|
return getInputInhibited() == INHIBIT_SYSTEM_LOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean IsXSystem() {
|
||||||
|
return isXSystem();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isXProt() {
|
||||||
|
return getInputInhibited() == INHIBIT_PROTECTED_FIELD;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean IsXProt() {
|
||||||
|
return isXProt();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isXNum() {
|
||||||
|
return getInputInhibited() == INHIBIT_NUMERIC_ONLY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean IsXNum() {
|
||||||
|
return isXNum();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isXWait() {
|
||||||
|
return isXSystem();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean IsXWait() {
|
||||||
|
return isXWait();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isXInsert() {
|
||||||
|
return isInsertMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean IsXInsert() {
|
||||||
|
return isXInsert();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isXComm() {
|
||||||
|
return isCommError() || getInputInhibited() == INHIBIT_COMM_CHECK;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean IsXComm() {
|
||||||
|
return isXComm();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isXOverflow() {
|
||||||
|
return getInputInhibited() == INHIBIT_OVERFLOW;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean IsXOverflow() {
|
||||||
|
return isXOverflow();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isXOperatorDue() {
|
||||||
|
return getInputInhibited() == INHIBIT_OPERATOR_DUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean IsXOperatorDue() {
|
||||||
|
return isXOperatorDue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatusString() {
|
||||||
|
int inhibit = getInputInhibited();
|
||||||
|
switch (inhibit) {
|
||||||
|
case INHIBIT_SYSTEM_LOCK: return "X-SYSTEM";
|
||||||
|
case INHIBIT_NUMERIC_ONLY: return "X-NUM";
|
||||||
|
case INHIBIT_PROTECTED_FIELD: return "X-PROT";
|
||||||
|
case INHIBIT_OVERFLOW: return "X-OVERFLOW";
|
||||||
|
case INHIBIT_COMM_CHECK: return "X-COMM";
|
||||||
|
case INHIBIT_OPERATOR_DUE: return "X-OP";
|
||||||
|
default:
|
||||||
|
if (isInsertMode()) return "X-INSERT";
|
||||||
|
return "READY";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String GetStatusString() {
|
||||||
|
return getStatusString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current Input Inhibited code.
|
||||||
|
* Returns one of INHIBIT_* constants from ECLConstants.
|
||||||
|
*/
|
||||||
|
public int getInputInhibited() {
|
||||||
|
if (inhibitOverride >= 0) {
|
||||||
|
return inhibitOverride;
|
||||||
|
}
|
||||||
|
if (isCommError()) {
|
||||||
|
return INHIBIT_COMM_CHECK;
|
||||||
|
}
|
||||||
|
if (inputProcessor != null && inputProcessor.isKeyboardLocked()) {
|
||||||
|
return INHIBIT_SYSTEM_LOCK;
|
||||||
|
}
|
||||||
|
return INHIBIT_NOT_INHIBITED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetInputInhibited() {
|
||||||
|
return getInputInhibited();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getInputInhibitedType() {
|
||||||
|
return getInputInhibited();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetInputInhibitedType() {
|
||||||
|
return getInputInhibited();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block until the keyboard becomes unlocked and ready for user input, or timeout expires.
|
||||||
|
* @return true if keyboard unlocked, false if timeout occurred.
|
||||||
|
*/
|
||||||
|
public boolean waitForInput(long timeoutMs) {
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
while (System.currentTimeMillis() - start < timeoutMs) {
|
||||||
|
if (getInputInhibited() == INHIBIT_NOT_INHIBITED) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(20);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getInputInhibited() == INHIBIT_NOT_INHIBITED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block until the host system is available.
|
||||||
|
*/
|
||||||
|
public boolean waitForSysAvailable(long timeoutMs) {
|
||||||
|
return waitForInput(timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean WaitForSystemAvailable(long timeoutMs) {
|
||||||
|
return waitForSysAvailable(timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean WaitForInput(long timeoutMs) {
|
||||||
|
return waitForInput(timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block until application is available.
|
||||||
|
*/
|
||||||
|
public boolean waitForAppAvailable(long timeoutMs) {
|
||||||
|
return waitForInput(timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean WaitForAppAvailable(long timeoutMs) {
|
||||||
|
return waitForAppAvailable(timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block until any OIA transition occurs.
|
||||||
|
*/
|
||||||
|
public boolean waitForTransition(long timeoutMs) {
|
||||||
|
int initialInhibit = getInputInhibited();
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
while (System.currentTimeMillis() - start < timeoutMs) {
|
||||||
|
if (getInputInhibited() != initialInhibit) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(20);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getInputInhibited() != initialInhibit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean WaitForTransition(long timeoutMs) {
|
||||||
|
return waitForTransition(timeoutMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package haus.nightmare.lib3270j.ecl;
|
||||||
|
|
||||||
|
import java.util.EventObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event object dispatched on Operator Information Area (ECLOIA) status changes.
|
||||||
|
*/
|
||||||
|
public class ECLOIAEvent extends EventObject {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public static final int OIA_UPDATE = 1;
|
||||||
|
public static final int OIA_LOCK_CHANGE = 2;
|
||||||
|
public static final int OIA_COMM_CHANGE = 3;
|
||||||
|
public static final int OIA_INPUT_INHIBITED = 4;
|
||||||
|
|
||||||
|
public static final int EVENT_UPDATE = OIA_UPDATE;
|
||||||
|
public static final int EVENT_LOCK_CHANGE = OIA_LOCK_CHANGE;
|
||||||
|
public static final int EVENT_COMM_CHANGE = OIA_COMM_CHANGE;
|
||||||
|
public static final int EVENT_INPUT_INHIBITED = OIA_INPUT_INHIBITED;
|
||||||
|
|
||||||
|
private final int eventType;
|
||||||
|
private final int inputInhibited;
|
||||||
|
private final int alphanumericType;
|
||||||
|
private final boolean insertMode;
|
||||||
|
private final String statusString;
|
||||||
|
|
||||||
|
public ECLOIAEvent(Object source, int eventType, int inputInhibited, int alphanumericType,
|
||||||
|
boolean insertMode, String statusString) {
|
||||||
|
super(source);
|
||||||
|
this.eventType = eventType;
|
||||||
|
this.inputInhibited = inputInhibited;
|
||||||
|
this.alphanumericType = alphanumericType;
|
||||||
|
this.insertMode = insertMode;
|
||||||
|
this.statusString = statusString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEventType() { return eventType; }
|
||||||
|
public int getInputInhibited() { return inputInhibited; }
|
||||||
|
public int getInhibitedReason() { return inputInhibited; }
|
||||||
|
public int getAlphanumericType() { return alphanumericType; }
|
||||||
|
public boolean isInsertMode() { return insertMode; }
|
||||||
|
public String getStatusString() { return statusString; }
|
||||||
|
|
||||||
|
public boolean isInputInhibited() {
|
||||||
|
return inputInhibited != ECLConstants.INHIBIT_NOT_INHIBITED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLOIA getOIA() {
|
||||||
|
return (getSource() instanceof ECLOIA) ? (ECLOIA) getSource() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("ECLOIAEvent[type=%d, status='%s', inhibited=%d, insert=%b]",
|
||||||
|
eventType, statusString, inputInhibited, insertMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package haus.nightmare.lib3270j.ecl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener interface for Operator Information Area (ECLOIA) status change events.
|
||||||
|
*/
|
||||||
|
public interface ECLOIAListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the OIA status, input inhibited flag, or keyboard lock state changes.
|
||||||
|
* @param event ECLOIAEvent containing OIA status information
|
||||||
|
*/
|
||||||
|
void oiaChanged(ECLOIAEvent event);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the input inhibited condition changes specifically.
|
||||||
|
* @param event ECLOIAEvent
|
||||||
|
*/
|
||||||
|
default void oiaInhibited(ECLOIAEvent event) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the keyboard lock / unlock state changes specifically.
|
||||||
|
* @param event ECLOIAEvent
|
||||||
|
*/
|
||||||
|
default void oiaLockStateChanged(ECLOIAEvent event) {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,811 @@
|
|||||||
|
package haus.nightmare.lib3270j.ecl;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||||
|
import haus.nightmare.lib3270j.input.InputProcessor;
|
||||||
|
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
|
||||||
|
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||||
|
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Presentation Space (ECLPS) implementation conforming to IBM Host On-Demand ECL specification.
|
||||||
|
* Provides multi-plane presentation buffer access, string searches, formatted field navigation,
|
||||||
|
* and automated keystroke streaming.
|
||||||
|
*/
|
||||||
|
public class ECLPS implements ECLConstants {
|
||||||
|
|
||||||
|
private final ScreenBuffer screen;
|
||||||
|
private final InputProcessor inputProcessor;
|
||||||
|
private final EbcdicTranslator translator;
|
||||||
|
private final ECLFieldList fieldList;
|
||||||
|
|
||||||
|
public ECLPS(ScreenBuffer screen, InputProcessor inputProcessor, EbcdicTranslator translator) {
|
||||||
|
this.screen = screen;
|
||||||
|
this.inputProcessor = inputProcessor;
|
||||||
|
this.translator = translator;
|
||||||
|
this.fieldList = new ECLFieldList(this, screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScreenBuffer getScreenBuffer() { return screen; }
|
||||||
|
public InputProcessor getInputProcessor() { return inputProcessor; }
|
||||||
|
public EbcdicTranslator getTranslator() { return translator; }
|
||||||
|
public ECLFieldList getFieldList() {
|
||||||
|
fieldList.refresh();
|
||||||
|
return fieldList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean nvtMode = false;
|
||||||
|
|
||||||
|
public boolean isNVTmode() {
|
||||||
|
if (inputProcessor != null && inputProcessor.isNvtMode()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return nvtMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNVTmode(boolean nvt) {
|
||||||
|
this.nvtMode = nvt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSize() { return screen.getRows() * screen.getCols(); }
|
||||||
|
public int getRows() { return screen.getRows(); }
|
||||||
|
public int getCols() { return screen.getCols(); }
|
||||||
|
public int getCursorPos(){ return screen.getCursorAddress(); }
|
||||||
|
public int getCursorRow(){ return screen.getCursorRow(); }
|
||||||
|
public int getCursorCol(){ return screen.getCursorCol(); }
|
||||||
|
|
||||||
|
public void setCursorPos(int pos) {
|
||||||
|
screen.setCursorAddress(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCursorPos(int row, int col) {
|
||||||
|
int pos = screen.rowColToAddress(row, col);
|
||||||
|
screen.setCursorAddress(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy presentation data from a specific plane into a destination buffer.
|
||||||
|
*/
|
||||||
|
public synchronized int getPlane(int planeType, char[] destBuffer, int start, int length) {
|
||||||
|
if (destBuffer == null || screen == null || length <= 0) return 0;
|
||||||
|
int size = screen.getRows() * screen.getCols();
|
||||||
|
if (size <= 0) return 0;
|
||||||
|
|
||||||
|
start = ((start % size) + size) % size;
|
||||||
|
int copyLen = Math.min(length, destBuffer.length);
|
||||||
|
|
||||||
|
for (int i = 0; i < copyLen; i++) {
|
||||||
|
int addr = (start + i) % size;
|
||||||
|
ExtendedAttribute ea = screen.getCell(addr);
|
||||||
|
switch (planeType) {
|
||||||
|
case PLANE_TEXT:
|
||||||
|
if (ea.isFieldAttribute()) {
|
||||||
|
destBuffer[i] = ' ';
|
||||||
|
} else if (ea.ucs4 != 0) {
|
||||||
|
destBuffer[i] = (char) ea.ucs4;
|
||||||
|
} else if (ea.ec != 0) {
|
||||||
|
destBuffer[i] = translator.ebcdicToUnicode(ea.ec & 0xFF);
|
||||||
|
} else {
|
||||||
|
destBuffer[i] = ' ';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PLANE_COLOR:
|
||||||
|
destBuffer[i] = (char) (ea.fg & 0xFF);
|
||||||
|
break;
|
||||||
|
case PLANE_HILITE:
|
||||||
|
destBuffer[i] = (char) (ea.gr & 0xFF);
|
||||||
|
break;
|
||||||
|
case PLANE_EXTENDED:
|
||||||
|
destBuffer[i] = (char) (ea.cs & 0xFF);
|
||||||
|
break;
|
||||||
|
case PLANE_FIELD:
|
||||||
|
destBuffer[i] = ea.isFieldAttribute() ? (char) (ea.fa & 0xFF) : 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
destBuffer[i] = ' ';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return copyLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a string of characters from the presentation space starting at address pos.
|
||||||
|
*/
|
||||||
|
public synchronized String getString(int pos, int length) {
|
||||||
|
if (length <= 0) return "";
|
||||||
|
char[] buf = new char[length];
|
||||||
|
getPlane(PLANE_TEXT, buf, pos, length);
|
||||||
|
return new String(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a string of characters from the presentation space starting at row/column.
|
||||||
|
*/
|
||||||
|
public String getString(int row, int col, int length) {
|
||||||
|
int pos = screen.rowColToAddress(row, col);
|
||||||
|
return getString(pos, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert text directly into unprotected fields in the presentation space starting at pos.
|
||||||
|
*/
|
||||||
|
public synchronized void setText(String text, int pos) {
|
||||||
|
if (text == null || text.isEmpty() || screen == null) return;
|
||||||
|
int size = screen.getRows() * screen.getCols();
|
||||||
|
if (size <= 0) return;
|
||||||
|
|
||||||
|
pos = ((pos % size) + size) % size;
|
||||||
|
setCursorPos(pos);
|
||||||
|
|
||||||
|
for (int i = 0; i < text.length(); i++) {
|
||||||
|
char ch = text.charAt(i);
|
||||||
|
inputProcessor.typeCharacter(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert text starting at row and column.
|
||||||
|
*/
|
||||||
|
public void setText(String text, int row, int col) {
|
||||||
|
int pos = screen.rowColToAddress(row, col);
|
||||||
|
setText(text, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for a string in the presentation space (0-based indexing).
|
||||||
|
* Returns 0-based position, or -1 if not found.
|
||||||
|
*/
|
||||||
|
public synchronized int searchString(String target, int startRow, int startCol, int dir, boolean ignoreCase) {
|
||||||
|
if (target == null || target.isEmpty() || screen == null) return -1;
|
||||||
|
int rows = screen.getRows();
|
||||||
|
int cols = screen.getCols();
|
||||||
|
int size = rows * cols;
|
||||||
|
if (size <= 0) return -1;
|
||||||
|
|
||||||
|
int startPos = (startRow * cols + startCol) % size;
|
||||||
|
char[] fullScreen = new char[size];
|
||||||
|
getPlane(PLANE_TEXT, fullScreen, 0, size);
|
||||||
|
String screenText = new String(fullScreen);
|
||||||
|
|
||||||
|
if (ignoreCase) {
|
||||||
|
screenText = screenText.toLowerCase();
|
||||||
|
target = target.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
int targetLen = target.length();
|
||||||
|
if (dir == SEARCH_FORWARD) {
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
int pos = (startPos + i) % size;
|
||||||
|
if (matchesAt(screenText, target, pos, size)) {
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
int pos = (startPos - i + size) % size;
|
||||||
|
if (matchesAt(screenText, target, pos, size)) {
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int searchString(String target) {
|
||||||
|
return searchString(target, 0, 0, SEARCH_FORWARD, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int searchString(String target, int startRow, int startCol) {
|
||||||
|
return searchString(target, startRow, startCol, SEARCH_FORWARD, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== IBM HoD SearchPS / SearchPSExt (1-based API) ==========
|
||||||
|
|
||||||
|
public int SearchPS(String text) {
|
||||||
|
return SearchPSExt(text, 1, getSize(), SEARCH_FORWARD, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int searchPS(String text) {
|
||||||
|
return SearchPS(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int SearchPS(String text, int startPos) {
|
||||||
|
return SearchPSExt(text, startPos, getSize(), SEARCH_FORWARD, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int searchPS(String text, int startPos) {
|
||||||
|
return SearchPS(text, startPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int SearchPS(String text, int startRow, int startCol) {
|
||||||
|
int pos = (startRow - 1) * getCols() + startCol;
|
||||||
|
return SearchPSExt(text, pos, getSize(), SEARCH_FORWARD, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int searchPS(String text, int startRow, int startCol) {
|
||||||
|
return SearchPS(text, startRow, startCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int SearchPS(String text, int startRow, int startCol, int dir) {
|
||||||
|
int pos = (startRow - 1) * getCols() + startCol;
|
||||||
|
return SearchPSExt(text, pos, getSize(), dir, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int searchPS(String text, int startRow, int startCol, int dir) {
|
||||||
|
return SearchPS(text, startRow, startCol, dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int SearchPS(String text, int startRow, int startCol, int dir, boolean ignoreCase) {
|
||||||
|
int pos = (startRow - 1) * getCols() + startCol;
|
||||||
|
return SearchPSExt(text, pos, getSize(), dir, ignoreCase, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int searchPS(String text, int startRow, int startCol, int dir, boolean ignoreCase) {
|
||||||
|
return SearchPS(text, startRow, startCol, dir, ignoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int SearchPS(String text, int startRow, int startCol, int endRow, int endCol) {
|
||||||
|
int sPos = (startRow - 1) * getCols() + startCol;
|
||||||
|
int ePos = (endRow - 1) * getCols() + endCol;
|
||||||
|
return SearchPSExt(text, sPos, ePos, SEARCH_FORWARD, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int searchPS(String text, int startRow, int startCol, int endRow, int endCol) {
|
||||||
|
return SearchPS(text, startRow, startCol, endRow, endCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int SearchPS(String text, int startRow, int startCol, int endRow, int endCol, int dir) {
|
||||||
|
int sPos = (startRow - 1) * getCols() + startCol;
|
||||||
|
int ePos = (endRow - 1) * getCols() + endCol;
|
||||||
|
return SearchPSExt(text, sPos, ePos, dir, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int searchPS(String text, int startRow, int startCol, int endRow, int endCol, int dir) {
|
||||||
|
return SearchPS(text, startRow, startCol, endRow, endCol, dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int SearchPS(String text, int startRow, int startCol, int endRow, int endCol, int dir, boolean ignoreCase) {
|
||||||
|
int sPos = (startRow - 1) * getCols() + startCol;
|
||||||
|
int ePos = (endRow - 1) * getCols() + endCol;
|
||||||
|
return SearchPSExt(text, sPos, ePos, dir, ignoreCase, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int searchPS(String text, int startRow, int startCol, int endRow, int endCol, int dir, boolean ignoreCase) {
|
||||||
|
return SearchPS(text, startRow, startCol, endRow, endCol, dir, ignoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SearchPS extended method conforming to IBM ECL specification.
|
||||||
|
* Uses 1-based positions and returns 1-based index (or 0 if not found).
|
||||||
|
*/
|
||||||
|
public synchronized int SearchPSExt(String text, int startPos, int endPos, int dir, boolean ignoreCase, boolean wrap) {
|
||||||
|
if (text == null || text.isEmpty() || screen == null) return 0;
|
||||||
|
int size = screen.getRows() * screen.getCols();
|
||||||
|
if (size <= 0) return 0;
|
||||||
|
|
||||||
|
int s0 = Math.max(0, Math.min(startPos - 1, size - 1));
|
||||||
|
int e0 = Math.max(0, Math.min(endPos - 1, size - 1));
|
||||||
|
|
||||||
|
char[] fullScreen = new char[size];
|
||||||
|
getPlane(PLANE_TEXT, fullScreen, 0, size);
|
||||||
|
String screenText = new String(fullScreen);
|
||||||
|
|
||||||
|
if (ignoreCase) {
|
||||||
|
screenText = screenText.toLowerCase();
|
||||||
|
text = text.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = wrap ? size : (dir == SEARCH_FORWARD ? (e0 >= s0 ? e0 - s0 + 1 : size - s0 + e0 + 1)
|
||||||
|
: (s0 >= e0 ? s0 - e0 + 1 : s0 + size - e0 + 1));
|
||||||
|
if (dir == SEARCH_FORWARD) {
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
int pos = (s0 + i) % size;
|
||||||
|
if (!wrap && e0 >= s0 && pos > e0) break;
|
||||||
|
if (matchesAt(screenText, text, pos, size)) {
|
||||||
|
return pos + 1; // 1-based
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
int pos = (s0 - i + size) % size;
|
||||||
|
if (!wrap && s0 >= e0 && pos < e0) break;
|
||||||
|
if (matchesAt(screenText, text, pos, size)) {
|
||||||
|
return pos + 1; // 1-based
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int searchPSExt(String text, int startPos, int endPos, int dir, boolean ignoreCase, boolean wrap) {
|
||||||
|
return SearchPSExt(text, startPos, endPos, dir, ignoreCase, wrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int SearchPSExt(String text, int startRow, int startCol, int endRow, int endCol, int dir, boolean ignoreCase, boolean wrap) {
|
||||||
|
int cols = getCols();
|
||||||
|
int sPos = (startRow - 1) * cols + startCol;
|
||||||
|
int ePos = (endRow - 1) * cols + endCol;
|
||||||
|
return SearchPSExt(text, sPos, ePos, dir, ignoreCase, wrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int searchPSExt(String text, int startRow, int startCol, int endRow, int endCol, int dir, boolean ignoreCase, boolean wrap) {
|
||||||
|
return SearchPSExt(text, startRow, startCol, endRow, endCol, dir, ignoreCase, wrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean matchesAt(String screenText, String target, int pos, int size) {
|
||||||
|
int len = target.length();
|
||||||
|
for (int j = 0; j < len; j++) {
|
||||||
|
int charPos = (pos + j) % size;
|
||||||
|
if (screenText.charAt(charPos) != target.charAt(j)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Rectangular Block Copy & Paste ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy a rectangular text region from (sRow, sCol) to (eRow, eCol) inclusive.
|
||||||
|
*/
|
||||||
|
public synchronized String copyString(int sRow, int sCol, int eRow, int eCol) {
|
||||||
|
if (screen == null) return "";
|
||||||
|
int rows = screen.getRows();
|
||||||
|
int cols = screen.getCols();
|
||||||
|
if (rows <= 0 || cols <= 0) return "";
|
||||||
|
|
||||||
|
int minR = Math.max(0, Math.min(sRow, eRow));
|
||||||
|
int maxR = Math.min(rows - 1, Math.max(sRow, eRow));
|
||||||
|
int minC = Math.max(0, Math.min(sCol, eCol));
|
||||||
|
int maxC = Math.min(cols - 1, Math.max(sCol, eCol));
|
||||||
|
int sliceWidth = maxC - minC + 1;
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int r = minR; r <= maxR; r++) {
|
||||||
|
char[] rowBuf = new char[sliceWidth];
|
||||||
|
getPlane(PLANE_TEXT, rowBuf, r * cols + minC, sliceWidth);
|
||||||
|
sb.append(rowBuf);
|
||||||
|
if (r < maxR) {
|
||||||
|
sb.append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String CopyString(int sRow, int sCol, int eRow, int eCol) {
|
||||||
|
return copyString(sRow, sCol, eRow, eCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paste a multi-line rectangular block of text starting at (row, col).
|
||||||
|
*/
|
||||||
|
public synchronized int pasteString(String text, int row, int col) {
|
||||||
|
if (text == null || text.isEmpty() || screen == null) return 0;
|
||||||
|
int rows = screen.getRows();
|
||||||
|
int cols = screen.getCols();
|
||||||
|
if (rows <= 0 || cols <= 0) return 0;
|
||||||
|
|
||||||
|
String[] lines = text.split("\r?\n");
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < lines.length; i++) {
|
||||||
|
int targetRow = (row + i) % rows;
|
||||||
|
String line = lines[i];
|
||||||
|
for (int c = 0; c < line.length() && (col + c) < cols; c++) {
|
||||||
|
int pos = targetRow * cols + (col + c);
|
||||||
|
if (screen.isFormatted()) {
|
||||||
|
byte fa = screen.getFieldAttributeAt(pos);
|
||||||
|
if (faIsProtected(fa & 0xFF) || screen.getCell(pos).isFieldAttribute()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setCursorPos(pos);
|
||||||
|
if (inputProcessor != null) {
|
||||||
|
inputProcessor.typeCharacter(line.charAt(c));
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int PasteString(String text, int row, int col) {
|
||||||
|
return pasteString(text, row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int pasteRectangular(String text, int row, int col) {
|
||||||
|
return pasteString(text, row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int PasteRectangular(String text, int row, int col) {
|
||||||
|
return pasteString(text, row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paste text with line wrapping across unprotected fields.
|
||||||
|
*/
|
||||||
|
public synchronized int pasteLineWrap(String text, int startPos, int endCol, boolean wordWrap) {
|
||||||
|
if (text == null || text.isEmpty() || screen == null) return 0;
|
||||||
|
int cols = screen.getCols();
|
||||||
|
int rows = screen.getRows();
|
||||||
|
int size = rows * cols;
|
||||||
|
if (size <= 0) return 0;
|
||||||
|
|
||||||
|
setCursorPos(startPos);
|
||||||
|
int charsPasted = 0;
|
||||||
|
String[] lines = text.split("\r?\n");
|
||||||
|
|
||||||
|
for (int l = 0; l < lines.length; l++) {
|
||||||
|
String line = lines[l];
|
||||||
|
for (int i = 0; i < line.length(); i++) {
|
||||||
|
int curPos = screen.getCursorAddress();
|
||||||
|
int curCol = curPos % cols;
|
||||||
|
if (endCol > 0 && curCol >= endCol) {
|
||||||
|
int nextRow = (curPos / cols + 1) % rows;
|
||||||
|
setCursorPos(nextRow * cols);
|
||||||
|
}
|
||||||
|
if (inputProcessor != null) {
|
||||||
|
inputProcessor.typeCharacter(line.charAt(i));
|
||||||
|
}
|
||||||
|
charsPasted++;
|
||||||
|
}
|
||||||
|
if (l < lines.length - 1 && inputProcessor != null) {
|
||||||
|
inputProcessor.newline();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return charsPasted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int PasteLineWrap(String text, int startPos, int endCol, boolean wordWrap) {
|
||||||
|
return pasteLineWrap(text, startPos, endCol, wordWrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Entry Assist & DOC Mode Operations ==========
|
||||||
|
|
||||||
|
public boolean isEntryAssistDOCmode() { return screen != null && screen.isEntryAssistDOCmode(); }
|
||||||
|
public boolean IsEntryAssistDOCmode() { return isEntryAssistDOCmode(); }
|
||||||
|
public void setEntryAssistDOCmode(boolean bl) { if (screen != null) screen.setEntryAssistDOCmode(bl); }
|
||||||
|
public void SetEntryAssistDOCmode(boolean bl) { setEntryAssistDOCmode(bl); }
|
||||||
|
|
||||||
|
public boolean isEntryAssistWordWrap() { return screen != null && screen.isEntryAssistWordWrap(); }
|
||||||
|
public boolean IsEntryAssistWordWrap() { return isEntryAssistWordWrap(); }
|
||||||
|
public void setEntryAssistWordWrap(boolean bl) { if (screen != null) screen.setEntryAssistWordWrap(bl); }
|
||||||
|
public void SetEntryAssistWordWrap(boolean bl) { setEntryAssistWordWrap(bl); }
|
||||||
|
|
||||||
|
public int getEntryAssistStartColumn() { return screen != null ? screen.getEntryAssistStartColumn() : 0; }
|
||||||
|
public int GetEntryAssistStartColumn() { return getEntryAssistStartColumn(); }
|
||||||
|
public void setEntryAssistStartColumn(int n) { if (screen != null) screen.setEntryAssistStartColumn(n); }
|
||||||
|
public void SetEntryAssistStartColumn(int n) { setEntryAssistStartColumn(n); }
|
||||||
|
|
||||||
|
public int getEntryAssistEndColumn() { return screen != null ? screen.getEntryAssistEndColumn() : 0; }
|
||||||
|
public int GetEntryAssistEndColumn() { return getEntryAssistEndColumn(); }
|
||||||
|
public void setEntryAssistEndColumn(int n) { if (screen != null) screen.setEntryAssistEndColumn(n); }
|
||||||
|
public void SetEntryAssistEndColumn(int n) { setEntryAssistEndColumn(n); }
|
||||||
|
|
||||||
|
public int[] getEntryAssistTabStops() { return screen != null ? screen.getEntryAssistTabStops() : null; }
|
||||||
|
public int[] GetEntryAssistTabStops() { return getEntryAssistTabStops(); }
|
||||||
|
public void setEntryAssistTabStops(int[] stops) { if (screen != null) screen.setEntryAssistTabStops(stops); }
|
||||||
|
public void SetEntryAssistTabStops(int[] stops) { setEntryAssistTabStops(stops); }
|
||||||
|
|
||||||
|
public void processWordTab(boolean forward) { if (inputProcessor != null) inputProcessor.processWordTab(forward); else if (screen != null) screen.processWordTab(forward); }
|
||||||
|
public void ProcessWordTab(boolean forward) { processWordTab(forward); }
|
||||||
|
public void wordTab(boolean forward) { processWordTab(forward); }
|
||||||
|
public void WordTab(boolean forward) { processWordTab(forward); }
|
||||||
|
|
||||||
|
public void processDeleteWord() { if (inputProcessor != null) inputProcessor.processDeleteWord(); else if (screen != null) screen.processDeleteWord(); }
|
||||||
|
public void ProcessDeleteWord() { processDeleteWord(); }
|
||||||
|
public void deleteWord() { processDeleteWord(); }
|
||||||
|
public void DeleteWord() { processDeleteWord(); }
|
||||||
|
|
||||||
|
public void processWordLeft() { if (inputProcessor != null) inputProcessor.processWordLeft(); }
|
||||||
|
public void ProcessWordLeft() { processWordLeft(); }
|
||||||
|
public void wordLeft() { processWordLeft(); }
|
||||||
|
public void WordLeft() { processWordLeft(); }
|
||||||
|
|
||||||
|
public void processWordRight() { if (inputProcessor != null) inputProcessor.processWordRight(); }
|
||||||
|
public void ProcessWordRight() { processWordRight(); }
|
||||||
|
public void wordRight() { processWordRight(); }
|
||||||
|
public void WordRight() { processWordRight(); }
|
||||||
|
|
||||||
|
public void processFieldEnd() { if (inputProcessor != null) inputProcessor.processFieldEnd(); }
|
||||||
|
public void ProcessFieldEnd() { processFieldEnd(); }
|
||||||
|
public void fieldEnd() { processFieldEnd(); }
|
||||||
|
public void FieldEnd() { processFieldEnd(); }
|
||||||
|
|
||||||
|
// ========== Field Management Accessors ==========
|
||||||
|
|
||||||
|
public ECLField getField(int pos) {
|
||||||
|
return getFieldList().findField(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLField GetField(int pos) {
|
||||||
|
return getField(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLField getField(int row, int col) {
|
||||||
|
return getFieldList().findField(row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLField GetField(int row, int col) {
|
||||||
|
return getField(row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLField getFirstField() {
|
||||||
|
return getFieldList().getFirstField();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLField GetFirstField() {
|
||||||
|
return getFirstField();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLField getNextField(ECLField prev) {
|
||||||
|
return getFieldList().getNextField(prev);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLField GetNextField(ECLField prev) {
|
||||||
|
return getNextField(prev);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLField getPreviousField(ECLField next) {
|
||||||
|
return getFieldList().getPreviousField(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLField GetPreviousField(ECLField next) {
|
||||||
|
return getPreviousField(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLFieldList GetFieldList() {
|
||||||
|
return getFieldList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== ECLPS Event Listener Management ==========
|
||||||
|
|
||||||
|
private final java.util.List<ECLPSListener> psListeners = new java.util.concurrent.CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
|
public void RegisterPSEvent(ECLPSListener listener) {
|
||||||
|
if (listener != null && !psListeners.contains(listener)) {
|
||||||
|
psListeners.add(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerPSEvent(ECLPSListener listener) {
|
||||||
|
RegisterPSEvent(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnregisterPSEvent(ECLPSListener listener) {
|
||||||
|
psListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterPSEvent(ECLPSListener listener) {
|
||||||
|
UnregisterPSEvent(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notifyPSEvent(ECLPSEvent event) {
|
||||||
|
for (ECLPSListener l : psListeners) {
|
||||||
|
try {
|
||||||
|
l.psChanged(event);
|
||||||
|
if (event.getEventType() == ECLPSEvent.PS_CURSOR) {
|
||||||
|
l.psCursorMoved(event);
|
||||||
|
} else if (event.getEventType() == ECLPSEvent.PS_ALARM) {
|
||||||
|
l.psAlarm(event);
|
||||||
|
} else if (event.getEventType() == ECLPSEvent.PS_RESIZE) {
|
||||||
|
l.psResized(event);
|
||||||
|
} else if (event.getEventType() == ECLPSEvent.PS_CLOSE) {
|
||||||
|
l.psClosed(event);
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notifyPSUpdate(int startRow, int startCol, int endRow, int endCol, boolean full) {
|
||||||
|
int r = (screen != null) ? screen.getRows() : 0;
|
||||||
|
int c = (screen != null) ? screen.getCols() : 0;
|
||||||
|
int cur = (screen != null) ? screen.getCursorAddress() : 0;
|
||||||
|
notifyPSEvent(new ECLPSEvent(this, ECLPSEvent.PS_UPDATE, startRow, startCol, endRow, endCol, cur, cur, r, c, full));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notifyCursorMoved(int oldAddress, int newAddress) {
|
||||||
|
int r = (screen != null) ? screen.getRows() : 0;
|
||||||
|
int c = (screen != null) ? screen.getCols() : 0;
|
||||||
|
int row = (c > 0) ? newAddress / c : 0;
|
||||||
|
int col = (c > 0) ? newAddress % c : 0;
|
||||||
|
notifyPSEvent(new ECLPSEvent(this, ECLPSEvent.PS_CURSOR, row, col, row, col, oldAddress, newAddress, r, c, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notifyAlarm() {
|
||||||
|
notifyPSEvent(new ECLPSEvent(this, ECLPSEvent.PS_ALARM));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notifyScreenResized(int rows, int cols) {
|
||||||
|
int cur = (screen != null) ? screen.getCursorAddress() : 0;
|
||||||
|
notifyPSEvent(new ECLPSEvent(this, ECLPSEvent.PS_RESIZE, 0, 0, rows - 1, cols - 1, cur, cur, rows, cols, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send standard IBM ECL mnemonic keystrokes.
|
||||||
|
*/
|
||||||
|
public void sendKeys(String keys) {
|
||||||
|
if (inputProcessor != null) {
|
||||||
|
inputProcessor.sendKeys(keys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendKeys(String keys) {
|
||||||
|
sendKeys(keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendKeys(String keys, int row, int col) {
|
||||||
|
sendKeys(keys, row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendKeys(String keys, int row, int col) {
|
||||||
|
if (row > 0 && col > 0) {
|
||||||
|
setCursorPos(row - 1, col - 1);
|
||||||
|
}
|
||||||
|
sendKeys(keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send keystrokes with configurable inter-character or inter-mnemonic delay.
|
||||||
|
*/
|
||||||
|
public void sendCharacters(String keys) {
|
||||||
|
sendCharacters(keys, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendCharacters(String keys) {
|
||||||
|
sendCharacters(keys, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendCharacters(String keys, int delayMs) {
|
||||||
|
sendCharacters(keys, delayMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendCharacters(String keys, int row, int col, int delayMs) {
|
||||||
|
if (row > 0 && col > 0) {
|
||||||
|
setCursorPos(row - 1, col - 1);
|
||||||
|
}
|
||||||
|
sendCharacters(keys, delayMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendCharacters(String keys, int row, int col, int delayMs) {
|
||||||
|
sendCharacters(keys, row, col, delayMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendCharacters(String keys, int delayMs) {
|
||||||
|
if (keys == null || keys.isEmpty()) return;
|
||||||
|
if (delayMs <= 0) {
|
||||||
|
sendKeys(keys);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
int len = keys.length();
|
||||||
|
while (i < len) {
|
||||||
|
if (keys.charAt(i) == '[') {
|
||||||
|
if (i + 1 < len && keys.charAt(i + 1) == '[') {
|
||||||
|
// Escaped bracket "[["
|
||||||
|
sendKeys("[");
|
||||||
|
i += 2;
|
||||||
|
} else {
|
||||||
|
int close = keys.indexOf(']', i);
|
||||||
|
if (close > i) {
|
||||||
|
String mnemonic = keys.substring(i, close + 1);
|
||||||
|
sendKeys(mnemonic);
|
||||||
|
i = close + 1;
|
||||||
|
} else {
|
||||||
|
sendKeys(keys.substring(i, i + 1));
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendKeys(keys.substring(i, i + 1));
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delayMs > 0 && i < len) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(delayMs);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Synchronization & ECL Automation Waits ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block until the specified screen descriptor conditions are met.
|
||||||
|
*/
|
||||||
|
public boolean waitForScreen(ECLScreenDesc desc, long timeoutMs) {
|
||||||
|
if (desc == null) return true;
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
while (System.currentTimeMillis() - start < timeoutMs) {
|
||||||
|
if (desc.Matches(this, null)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(25);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return desc.Matches(this, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean WaitForScreen(ECLScreenDesc desc, long timeoutMs) {
|
||||||
|
return waitForScreen(desc, timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block until the specified text appears anywhere on the presentation space.
|
||||||
|
*/
|
||||||
|
public boolean waitForScreen(String text, long timeoutMs) {
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
while (System.currentTimeMillis() - start < timeoutMs) {
|
||||||
|
if (searchString(text) >= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(25);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return searchString(text) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean WaitForScreen(String text, long timeoutMs) {
|
||||||
|
return waitForScreen(text, timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block until the specified text appears at the given (row, col) coordinate.
|
||||||
|
*/
|
||||||
|
public boolean waitForScreen(String text, int row, int col, long timeoutMs) {
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
while (System.currentTimeMillis() - start < timeoutMs) {
|
||||||
|
String onScreen = getString(row, col, text.length());
|
||||||
|
if (text.equals(onScreen)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(25);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return text.equals(getString(row, col, text.length()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean WaitForScreen(String text, int row, int col, long timeoutMs) {
|
||||||
|
return waitForScreen(text, row, col, timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block until the cursor moves to (row, col).
|
||||||
|
*/
|
||||||
|
public boolean waitForCursor(int row, int col, long timeoutMs) {
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
while (System.currentTimeMillis() - start < timeoutMs) {
|
||||||
|
if (getCursorRow() == row && getCursorCol() == col) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(25);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getCursorRow() == row && getCursorCol() == col;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean WaitForCursor(int row, int col, long timeoutMs) {
|
||||||
|
return waitForCursor(row, col, timeoutMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package haus.nightmare.lib3270j.ecl;
|
||||||
|
|
||||||
|
import java.util.EventObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event object dispatched on Presentation Space (ECLPS) modifications.
|
||||||
|
*/
|
||||||
|
public class ECLPSEvent extends EventObject {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public static final int PS_UPDATE = 1;
|
||||||
|
public static final int PS_CURSOR = 2;
|
||||||
|
public static final int PS_ALARM = 3;
|
||||||
|
public static final int PS_RESIZE = 4;
|
||||||
|
public static final int PS_CLOSE = 5;
|
||||||
|
|
||||||
|
public static final int EVENT_UPDATE = PS_UPDATE;
|
||||||
|
public static final int EVENT_CURSOR = PS_CURSOR;
|
||||||
|
public static final int EVENT_ALARM = PS_ALARM;
|
||||||
|
public static final int EVENT_RESIZE = PS_RESIZE;
|
||||||
|
public static final int EVENT_CLOSE = PS_CLOSE;
|
||||||
|
|
||||||
|
private final int eventType;
|
||||||
|
private final int startRow;
|
||||||
|
private final int startCol;
|
||||||
|
private final int endRow;
|
||||||
|
private final int endCol;
|
||||||
|
private final int oldCursorAddress;
|
||||||
|
private final int newCursorAddress;
|
||||||
|
private final int rows;
|
||||||
|
private final int cols;
|
||||||
|
private final boolean fullUpdate;
|
||||||
|
|
||||||
|
public ECLPSEvent(Object source, int eventType, int startRow, int startCol, int endRow, int endCol,
|
||||||
|
int oldCursorAddress, int newCursorAddress, int rows, int cols, boolean fullUpdate) {
|
||||||
|
super(source);
|
||||||
|
this.eventType = eventType;
|
||||||
|
this.startRow = startRow;
|
||||||
|
this.startCol = startCol;
|
||||||
|
this.endRow = endRow;
|
||||||
|
this.endCol = endCol;
|
||||||
|
this.oldCursorAddress = oldCursorAddress;
|
||||||
|
this.newCursorAddress = newCursorAddress;
|
||||||
|
this.rows = rows;
|
||||||
|
this.cols = cols;
|
||||||
|
this.fullUpdate = fullUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLPSEvent(Object source, int eventType) {
|
||||||
|
this(source, eventType, 0, 0, 0, 0, 0, 0, 0, 0, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEventType() { return eventType; }
|
||||||
|
public int getStartRow() { return startRow; }
|
||||||
|
public int getStartCol() { return startCol; }
|
||||||
|
public int getEndRow() { return endRow; }
|
||||||
|
public int getEndCol() { return endCol; }
|
||||||
|
public int getOldCursorAddress() { return oldCursorAddress; }
|
||||||
|
public int getNewCursorAddress() { return newCursorAddress; }
|
||||||
|
public int getRows() { return rows; }
|
||||||
|
public int getCols() { return cols; }
|
||||||
|
public boolean isFullUpdate() { return fullUpdate; }
|
||||||
|
|
||||||
|
public ECLPS getPS() {
|
||||||
|
return (getSource() instanceof ECLPS) ? (ECLPS) getSource() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("ECLPSEvent[type=%d, start=(%d,%d), end=(%d,%d), full=%b]",
|
||||||
|
eventType, startRow, startCol, endRow, endCol, fullUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package haus.nightmare.lib3270j.ecl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener interface for Presentation Space (ECLPS) update events.
|
||||||
|
*/
|
||||||
|
public interface ECLPSListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the presentation space is modified.
|
||||||
|
* @param event ECLPSEvent containing update boundaries and state
|
||||||
|
*/
|
||||||
|
void psChanged(ECLPSEvent event);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the cursor position changes within the presentation space.
|
||||||
|
* @param event ECLPSEvent containing cursor positions
|
||||||
|
*/
|
||||||
|
default void psCursorMoved(ECLPSEvent event) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a host sound alarm is triggered.
|
||||||
|
* @param event ECLPSEvent
|
||||||
|
*/
|
||||||
|
default void psAlarm(ECLPSEvent event) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the presentation space dimensions change.
|
||||||
|
* @param event ECLPSEvent containing new rows and columns
|
||||||
|
*/
|
||||||
|
default void psResized(ECLPSEvent event) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the presentation space is closed / disconnected.
|
||||||
|
* @param event ECLPSEvent
|
||||||
|
*/
|
||||||
|
default void psClosed(ECLPSEvent event) {}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user