Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
0201c7c277
|
|||
|
b61702a57c
|
|||
|
bd4c1f3040
|
|||
|
0d6ab31f0a
|
|||
|
1f0e36c561
|
|||
|
8286af19e6
|
|||
|
3d3a939ece
|
|||
|
d05f1a4c45
|
|||
|
aa724fbcef
|
|||
|
8a30efe9c0
|
|||
|
5049d8df74
|
|||
|
badd026055
|
|||
|
ef70ea0f5a
|
@@ -27,6 +27,30 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
|
|
||||||
private static final Logger log = Logger.getLogger(J3270App.class.getName());
|
private static final Logger log = Logger.getLogger(J3270App.class.getName());
|
||||||
|
|
||||||
|
// Active open windows tracking
|
||||||
|
private static final java.util.List<J3270App> openWindows = new java.util.concurrent.CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
|
public static java.util.List<J3270App> getOpenWindows() {
|
||||||
|
return java.util.Collections.unmodifiableList(openWindows);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ConnectChoiceHandler {
|
||||||
|
ConnectChoiceDialog.Choice askChoice(Window owner, String currentTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConnectChoiceHandler connectChoiceHandler = null;
|
||||||
|
|
||||||
|
public void setConnectChoiceHandler(ConnectChoiceHandler handler) {
|
||||||
|
this.connectChoiceHandler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConnectChoiceHandler getConnectChoiceHandler() {
|
||||||
|
return connectChoiceHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final java.util.function.Consumer<UITheme> themeChangeListener = this::onThemeChanged;
|
||||||
|
|
||||||
private Telnet3270Client client;
|
private Telnet3270Client client;
|
||||||
private TerminalPanel terminalPanel;
|
private TerminalPanel terminalPanel;
|
||||||
private StatusBar statusBar;
|
private StatusBar statusBar;
|
||||||
@@ -42,8 +66,10 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
// Dialog instances
|
// Dialog instances
|
||||||
private FindDialog findDialog;
|
private FindDialog findDialog;
|
||||||
private ScriptDialog scriptDialog;
|
private ScriptDialog scriptDialog;
|
||||||
|
private MacroDialog macroDialog;
|
||||||
private FieldInspectorDialog fieldInspectorDialog;
|
private FieldInspectorDialog fieldInspectorDialog;
|
||||||
private PrinterSessionDialog printerSessionDialog;
|
private PrinterSessionDialog printerSessionDialog;
|
||||||
|
private ScreenHistoryDialog screenHistoryDialog;
|
||||||
|
|
||||||
// Modes menu items
|
// Modes menu items
|
||||||
private JCheckBoxMenuItem docModeItem;
|
private JCheckBoxMenuItem docModeItem;
|
||||||
@@ -58,14 +84,16 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
|
|
||||||
public J3270App() {
|
public J3270App() {
|
||||||
super("j3270 — Java TN3270 Terminal Emulator");
|
super("j3270 — Java TN3270 Terminal Emulator");
|
||||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
|
||||||
setBackground(Color.BLACK);
|
setBackground(Color.BLACK);
|
||||||
AppIcon.applyTo(this);
|
AppIcon.applyTo(this);
|
||||||
|
|
||||||
|
openWindows.add(this);
|
||||||
|
|
||||||
buildUI();
|
buildUI();
|
||||||
buildMenuBar();
|
buildMenuBar();
|
||||||
|
|
||||||
ThemeManager.addThemeChangeListener(this::onThemeChanged);
|
ThemeManager.addThemeChangeListener(themeChangeListener);
|
||||||
|
|
||||||
pack();
|
pack();
|
||||||
setLocationRelativeTo(null);
|
setLocationRelativeTo(null);
|
||||||
@@ -76,7 +104,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
if (client != null) {
|
if (client != null) {
|
||||||
statusBar.updateStatus();
|
statusBar.updateStatus();
|
||||||
syncModeMenuItems();
|
syncModeMenuItems();
|
||||||
if (!isAnyChildDialogActive() && KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow() == this) {
|
if (!isModalDialogActive() && KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow() == this) {
|
||||||
if (!terminalPanel.hasFocus()) {
|
if (!terminalPanel.hasFocus()) {
|
||||||
terminalPanel.requestFocusInWindow();
|
terminalPanel.requestFocusInWindow();
|
||||||
}
|
}
|
||||||
@@ -85,38 +113,62 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
});
|
});
|
||||||
refreshTimer.start();
|
refreshTimer.start();
|
||||||
|
|
||||||
|
setAutoRequestFocus(true);
|
||||||
|
|
||||||
addWindowListener(new WindowAdapter() {
|
addWindowListener(new WindowAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void windowClosing(WindowEvent e) {
|
public void windowClosing(WindowEvent e) {
|
||||||
disconnect();
|
closeWindow();
|
||||||
if (refreshTimer != null)
|
|
||||||
refreshTimer.stop();
|
|
||||||
if (printerSessionDialog != null) {
|
|
||||||
printerSessionDialog.dispose();
|
|
||||||
}
|
|
||||||
terminalPanel.dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void windowActivated(WindowEvent e) {
|
public void windowActivated(WindowEvent e) {
|
||||||
if (!isAnyChildDialogActive()) {
|
if (!isModalDialogActive() && terminalPanel != null) {
|
||||||
terminalPanel.requestFocusInWindow();
|
terminalPanel.requestFocus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addWindowFocusListener(new WindowAdapter() {
|
||||||
|
@Override
|
||||||
|
public void windowGainedFocus(WindowEvent e) {
|
||||||
|
if (!isModalDialogActive() && terminalPanel != null) {
|
||||||
|
terminalPanel.requestFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ensure clicking anywhere inside the frame brings focus to window and terminalPanel
|
||||||
|
Toolkit.getDefaultToolkit().addAWTEventListener(event -> {
|
||||||
|
if (event instanceof MouseEvent && event.getID() == MouseEvent.MOUSE_PRESSED) {
|
||||||
|
MouseEvent me = (MouseEvent) event;
|
||||||
|
Object src = me.getSource();
|
||||||
|
if (src instanceof Component) {
|
||||||
|
Window win = SwingUtilities.getWindowAncestor((Component) src);
|
||||||
|
if (win == J3270App.this && !isModalDialogActive()) {
|
||||||
|
if (!J3270App.this.isFocused() || !J3270App.this.isActive()) {
|
||||||
|
J3270App.this.toFront();
|
||||||
|
}
|
||||||
|
boolean isMenu = (src instanceof JMenuBar) || (src instanceof JMenuItem) || (src instanceof JPopupMenu)
|
||||||
|
|| SwingUtilities.getAncestorOfClass(JMenuBar.class, (Component) src) != null
|
||||||
|
|| SwingUtilities.getAncestorOfClass(JPopupMenu.class, (Component) src) != null;
|
||||||
|
if (!isMenu && terminalPanel != null && !terminalPanel.hasFocus()) {
|
||||||
|
terminalPanel.requestFocus();
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
if (terminalPanel != null && !terminalPanel.hasFocus()) {
|
||||||
|
terminalPanel.requestFocus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, AWTEvent.MOUSE_EVENT_MASK);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAnyChildDialogActive() {
|
private boolean isModalDialogActive() {
|
||||||
Window focusedWindow = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
|
|
||||||
if (focusedWindow != null && focusedWindow != this) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (findDialog != null && findDialog.isVisible()) return true;
|
|
||||||
if (scriptDialog != null && scriptDialog.isVisible()) return true;
|
|
||||||
if (fieldInspectorDialog != null && fieldInspectorDialog.isVisible()) return true;
|
|
||||||
if (printerSessionDialog != null && printerSessionDialog.isVisible()) return true;
|
|
||||||
for (Window owned : getOwnedWindows()) {
|
for (Window owned : getOwnedWindows()) {
|
||||||
if (owned != null && owned.isVisible()) {
|
if (owned instanceof Dialog && owned.isVisible() && ((Dialog) owned).isModal()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,6 +219,8 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
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("Close Window", KeyEvent.VK_W, this::closeWindow));
|
||||||
|
fileMenu.addSeparator();
|
||||||
fileMenu.add(createMenuItem("Save Screen As...", -1, () -> ScreenExporter.showExportDialog(this, client, terminalPanel)));
|
fileMenu.add(createMenuItem("Save Screen As...", -1, () -> ScreenExporter.showExportDialog(this, client, terminalPanel)));
|
||||||
fileMenu.add(createMenuItem("3287 Printer Session...", -1, this::showPrinterSessionDialog));
|
fileMenu.add(createMenuItem("3287 Printer Session...", -1, this::showPrinterSessionDialog));
|
||||||
fileMenu.add(createMenuItem("Print Screen...", KeyEvent.VK_P, this::printScreen));
|
fileMenu.add(createMenuItem("Print Screen...", KeyEvent.VK_P, this::printScreen));
|
||||||
@@ -174,13 +228,51 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
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, () -> {
|
||||||
disconnect();
|
for (J3270App win : new java.util.ArrayList<>(openWindows)) {
|
||||||
|
win.disconnect();
|
||||||
|
win.dispose();
|
||||||
|
}
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
}));
|
}));
|
||||||
menuBar.add(fileMenu);
|
menuBar.add(fileMenu);
|
||||||
|
|
||||||
// 2. Edit menu
|
// 2. Connection menu
|
||||||
|
JMenu connectionMenu = createMenu("Connection");
|
||||||
|
connectionMenu.add(createMenuItem("Connect...", KeyEvent.VK_N, this::showConnectDialog));
|
||||||
|
connectionMenu.add(createMenuItem("Disconnect", KeyEvent.VK_D, this::disconnect));
|
||||||
|
connectionMenu.addSeparator();
|
||||||
|
|
||||||
|
// Host Type Submenu (CMS/TSO/CICS)
|
||||||
|
JMenu hostTypeMenu = createMenu("Host Type");
|
||||||
|
ButtonGroup hostTypeGroup = new ButtonGroup();
|
||||||
|
for (haus.nightmare.lib3270j.ft.FTConfig.HostType ht : haus.nightmare.lib3270j.ft.FTConfig.HostType.values()) {
|
||||||
|
JRadioButtonMenuItem htItem = new JRadioButtonMenuItem(ht.name());
|
||||||
|
ThemeManager.styleMenuItem(htItem);
|
||||||
|
hostTypeGroup.add(htItem);
|
||||||
|
htItem.addActionListener(e -> setConnectionHostType(ht));
|
||||||
|
hostTypeMenu.add(htItem);
|
||||||
|
}
|
||||||
|
connectionMenu.add(hostTypeMenu);
|
||||||
|
connectionMenu.addSeparator();
|
||||||
|
|
||||||
|
// Saved Connections Submenu
|
||||||
|
JMenu savedHostsMenu = createMenu("Saved Connections");
|
||||||
|
connectionMenu.add(savedHostsMenu);
|
||||||
|
|
||||||
|
connectionMenu.addMenuListener(new javax.swing.event.MenuListener() {
|
||||||
|
@Override
|
||||||
|
public void menuSelected(javax.swing.event.MenuEvent e) {
|
||||||
|
updateConnectionMenu(hostTypeMenu, hostTypeGroup, savedHostsMenu);
|
||||||
|
}
|
||||||
|
@Override public void menuDeselected(javax.swing.event.MenuEvent e) {}
|
||||||
|
@Override public void menuCanceled(javax.swing.event.MenuEvent e) {}
|
||||||
|
});
|
||||||
|
|
||||||
|
menuBar.add(connectionMenu);
|
||||||
|
|
||||||
|
// 3. Edit menu
|
||||||
JMenu editMenu = createMenu("Edit");
|
JMenu editMenu = createMenu("Edit");
|
||||||
|
editMenu.add(createMenuItem("Cut", KeyEvent.VK_X, () -> terminalPanel.cutSelection()));
|
||||||
editMenu.add(createMenuItem("Copy", KeyEvent.VK_C, () -> terminalPanel.copySelection()));
|
editMenu.add(createMenuItem("Copy", KeyEvent.VK_C, () -> terminalPanel.copySelection()));
|
||||||
editMenu.add(createMenuItem("Paste", KeyEvent.VK_V, () -> terminalPanel.pasteClipboard()));
|
editMenu.add(createMenuItem("Paste", KeyEvent.VK_V, () -> terminalPanel.pasteClipboard()));
|
||||||
editMenu.add(createMenuItem("Paste with Line Wrap...", -1, this::showPasteLineWrapDialog));
|
editMenu.add(createMenuItem("Paste with Line Wrap...", -1, this::showPasteLineWrapDialog));
|
||||||
@@ -312,6 +404,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
viewMenu.add(gfxMenu);
|
viewMenu.add(gfxMenu);
|
||||||
|
|
||||||
viewMenu.addSeparator();
|
viewMenu.addSeparator();
|
||||||
|
viewMenu.add(createMenuItem("Screen History...", KeyEvent.VK_H, this::showScreenHistoryDialog, true));
|
||||||
viewMenu.add(createMenuItem("Field Inspector...", -1, this::showFieldInspectorDialog));
|
viewMenu.add(createMenuItem("Field Inspector...", -1, this::showFieldInspectorDialog));
|
||||||
menuBar.add(viewMenu);
|
menuBar.add(viewMenu);
|
||||||
|
|
||||||
@@ -430,6 +523,8 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
}, true));
|
}, true));
|
||||||
actionsMenu.addSeparator();
|
actionsMenu.addSeparator();
|
||||||
actionsMenu.add(createMenuItem("Run Keystrokes / Script...", -1, this::showScriptDialog));
|
actionsMenu.add(createMenuItem("Run Keystrokes / Script...", -1, this::showScriptDialog));
|
||||||
|
actionsMenu.add(createMenuItem("Macro Runtime (<HASCRIPT>)...", KeyEvent.VK_M, this::showMacroDialog, true));
|
||||||
|
actionsMenu.add(createMenuItem("Screen History...", KeyEvent.VK_H, this::showScreenHistoryDialog, true));
|
||||||
actionsMenu.add(createMenuItem("File Transfer...", KeyEvent.VK_T, this::showFileTransferDialog, true));
|
actionsMenu.add(createMenuItem("File Transfer...", KeyEvent.VK_T, this::showFileTransferDialog, true));
|
||||||
menuBar.add(actionsMenu);
|
menuBar.add(actionsMenu);
|
||||||
|
|
||||||
@@ -536,6 +631,13 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
scriptDialog.setVisible(true);
|
scriptDialog.setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showMacroDialog() {
|
||||||
|
if (macroDialog == null) {
|
||||||
|
macroDialog = new MacroDialog(this, client, terminalPanel);
|
||||||
|
}
|
||||||
|
macroDialog.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
private void showFieldInspectorDialog() {
|
private void showFieldInspectorDialog() {
|
||||||
if (client == null) {
|
if (client == null) {
|
||||||
JOptionPane.showMessageDialog(this, "Connect to a host before inspecting fields.", "Not Connected", JOptionPane.WARNING_MESSAGE);
|
JOptionPane.showMessageDialog(this, "Connect to a host before inspecting fields.", "Not Connected", JOptionPane.WARNING_MESSAGE);
|
||||||
@@ -559,6 +661,18 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
printerSessionDialog.setVisible(true);
|
printerSessionDialog.setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showScreenHistoryDialog() {
|
||||||
|
if (screenHistoryDialog == null) {
|
||||||
|
screenHistoryDialog = new ScreenHistoryDialog(this, client);
|
||||||
|
} else {
|
||||||
|
if (client != null && client.getScreenHistoryBuffer() != null) {
|
||||||
|
screenHistoryDialog.refreshHistoryView(client.getScreenHistoryBuffer().size() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
screenHistoryDialog.setVisible(true);
|
||||||
|
screenHistoryDialog.toFront();
|
||||||
|
}
|
||||||
|
|
||||||
private void showPasteLineWrapDialog() {
|
private void showPasteLineWrapDialog() {
|
||||||
if (client == null || !client.getConnectionState().isFullSession()) {
|
if (client == null || !client.getConnectionState().isFullSession()) {
|
||||||
JOptionPane.showMessageDialog(this, "Connect to a 3270 session before pasting.", "Not Connected", JOptionPane.WARNING_MESSAGE);
|
JOptionPane.showMessageDialog(this, "Connect to a 3270 session before pasting.", "Not Connected", JOptionPane.WARNING_MESSAGE);
|
||||||
@@ -685,7 +799,20 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
|
|
||||||
// ========== Connection management ==========
|
// ========== Connection management ==========
|
||||||
|
|
||||||
private void showConnectDialog() {
|
public void showConnectDialog() {
|
||||||
|
boolean wasConnected = isConnected();
|
||||||
|
if (wasConnected && (!GraphicsEnvironment.isHeadless() || connectChoiceHandler != null)) {
|
||||||
|
ConnectChoiceDialog.Choice choice = promptConnectConflict();
|
||||||
|
if (choice == ConnectChoiceDialog.Choice.NEW_WINDOW) {
|
||||||
|
J3270App newWindow = createNewWindow(null);
|
||||||
|
SwingUtilities.invokeLater(newWindow::showConnectDialog);
|
||||||
|
return;
|
||||||
|
} else if (choice == ConnectChoiceDialog.Choice.CANCEL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// choice == ABORT_CURRENT: proceed to showConnectDialog in this window
|
||||||
|
}
|
||||||
|
|
||||||
ConnectDialog dialog = new ConnectDialog(this);
|
ConnectDialog dialog = new ConnectDialog(this);
|
||||||
String initialHost = lastHost != null && !lastHost.isEmpty() ? lastHost : haus.nightmare.j3270.config.Settings.getAutoConnectHost();
|
String initialHost = lastHost != null && !lastHost.isEmpty() ? lastHost : haus.nightmare.j3270.config.Settings.getAutoConnectHost();
|
||||||
int initialPort = lastPort > 0 ? lastPort : haus.nightmare.j3270.config.Settings.getAutoConnectPort();
|
int initialPort = lastPort > 0 ? lastPort : haus.nightmare.j3270.config.Settings.getAutoConnectPort();
|
||||||
@@ -694,13 +821,77 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
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.setInitialCodePage(haus.nightmare.j3270.config.Settings.getCodePage());
|
||||||
|
dialog.setInitialGraphicsMode(haus.nightmare.j3270.config.Settings.getGraphicsMode());
|
||||||
|
dialog.setInitialHostType(haus.nightmare.j3270.config.Settings.getDefaultHostType());
|
||||||
dialog.setVisible(true);
|
dialog.setVisible(true);
|
||||||
|
|
||||||
if (dialog.isConfirmed()) {
|
if (dialog.isConfirmed()) {
|
||||||
ConnectionConfig config = dialog.getConnectionConfig();
|
ConnectionConfig config = dialog.getConnectionConfig();
|
||||||
lastHost = config.getHost();
|
lastHost = config.getHost();
|
||||||
lastPort = config.getPort();
|
lastPort = config.getPort();
|
||||||
connect(config);
|
connect(config, !wasConnected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConnectionHostType(haus.nightmare.lib3270j.ft.FTConfig.HostType ht) {
|
||||||
|
if (ht == null) return;
|
||||||
|
haus.nightmare.j3270.config.Settings.setDefaultHostType(ht.name());
|
||||||
|
if (client != null && client.getConfig() != null) {
|
||||||
|
client.getConfig().setFtHostType(ht);
|
||||||
|
String profileId = client.getConfig().getSavedProfileId();
|
||||||
|
if (profileId != null) {
|
||||||
|
haus.nightmare.j3270.storage.SavedHost saved = haus.nightmare.j3270.storage.HostStorage.findById(profileId);
|
||||||
|
if (saved != null) {
|
||||||
|
saved.setHostType(ht.name());
|
||||||
|
haus.nightmare.j3270.storage.HostStorage.saveHost(saved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (FileTransferDialog.getLastTransferState() != null) {
|
||||||
|
FileTransferDialog.getLastTransferState().hostType = ht;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateConnectionMenu(JMenu hostTypeMenu, ButtonGroup hostTypeGroup, JMenu savedHostsMenu) {
|
||||||
|
haus.nightmare.lib3270j.ft.FTConfig.HostType currentHt = haus.nightmare.lib3270j.ft.FTConfig.HostType.TSO;
|
||||||
|
if (client != null && client.getConfig() != null) {
|
||||||
|
currentHt = client.getConfig().getFtHostType();
|
||||||
|
} else {
|
||||||
|
String def = haus.nightmare.j3270.config.Settings.getDefaultHostType();
|
||||||
|
try {
|
||||||
|
if (def != null) currentHt = haus.nightmare.lib3270j.ft.FTConfig.HostType.valueOf(def.trim().toUpperCase());
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < hostTypeMenu.getItemCount(); i++) {
|
||||||
|
JMenuItem item = hostTypeMenu.getItem(i);
|
||||||
|
if (item instanceof JRadioButtonMenuItem) {
|
||||||
|
JRadioButtonMenuItem rb = (JRadioButtonMenuItem) item;
|
||||||
|
if (rb.getText().equalsIgnoreCase(currentHt.name())) {
|
||||||
|
rb.setSelected(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
savedHostsMenu.removeAll();
|
||||||
|
java.util.List<haus.nightmare.j3270.storage.SavedHost> savedList = haus.nightmare.j3270.storage.HostStorage.getSavedHosts();
|
||||||
|
if (savedList.isEmpty()) {
|
||||||
|
JMenuItem noneItem = new JMenuItem("(No Saved Connections)");
|
||||||
|
noneItem.setEnabled(false);
|
||||||
|
ThemeManager.styleMenuItem(noneItem);
|
||||||
|
savedHostsMenu.add(noneItem);
|
||||||
|
} else {
|
||||||
|
for (haus.nightmare.j3270.storage.SavedHost sh : savedList) {
|
||||||
|
String label = sh.getName();
|
||||||
|
if (label == null || label.trim().isEmpty()) {
|
||||||
|
label = sh.getHost() + ":" + sh.getPort();
|
||||||
|
}
|
||||||
|
String ht = (sh.getHostType() != null && !sh.getHostType().trim().isEmpty()) ? sh.getHostType().trim() : "TSO";
|
||||||
|
JMenuItem hostItem = new JMenuItem(label + " [" + ht + "]");
|
||||||
|
ThemeManager.styleMenuItem(hostItem);
|
||||||
|
hostItem.addActionListener(ev -> connect(sh.toConnectionConfig(), true));
|
||||||
|
savedHostsMenu.add(hostItem);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -709,6 +900,79 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
dialog.setVisible(true);
|
dialog.setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void closeWindow() {
|
||||||
|
disconnect();
|
||||||
|
if (refreshTimer != null) {
|
||||||
|
refreshTimer.stop();
|
||||||
|
}
|
||||||
|
if (printerSessionDialog != null) {
|
||||||
|
printerSessionDialog.dispose();
|
||||||
|
}
|
||||||
|
if (screenHistoryDialog != null) {
|
||||||
|
screenHistoryDialog.dispose();
|
||||||
|
}
|
||||||
|
terminalPanel.dispose();
|
||||||
|
ThemeManager.removeThemeChangeListener(themeChangeListener);
|
||||||
|
openWindows.remove(this);
|
||||||
|
dispose();
|
||||||
|
if (openWindows.isEmpty() && !GraphicsEnvironment.isHeadless()) {
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
openWindows.remove(this);
|
||||||
|
ThemeManager.removeThemeChangeListener(themeChangeListener);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnected() {
|
||||||
|
return client != null && (client.isConnected() ||
|
||||||
|
(client.getConnectionState() != null && client.getConnectionState().isConnected()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getConnectedTarget() {
|
||||||
|
if (client != null && client.getConfig() != null) {
|
||||||
|
String profileName = client.getConfig().getSavedProfileName();
|
||||||
|
String host = client.getConfig().getHost();
|
||||||
|
int port = client.getConfig().getPort();
|
||||||
|
if (profileName != null && !profileName.trim().isEmpty()) {
|
||||||
|
return profileName + " (" + host + ":" + port + ")";
|
||||||
|
}
|
||||||
|
if (host != null && !host.trim().isEmpty()) {
|
||||||
|
return host + ":" + port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lastHost != null && !lastHost.trim().isEmpty()) {
|
||||||
|
return lastHost + ":" + lastPort;
|
||||||
|
}
|
||||||
|
return "the current host";
|
||||||
|
}
|
||||||
|
|
||||||
|
public J3270App createNewWindow(ConnectionConfig config) {
|
||||||
|
J3270App newApp = new J3270App();
|
||||||
|
try {
|
||||||
|
Point loc = getLocation();
|
||||||
|
newApp.setLocation(loc.x + 30, loc.y + 30);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
newApp.setVisible(true);
|
||||||
|
if (config != null) {
|
||||||
|
newApp.connect(config, false);
|
||||||
|
}
|
||||||
|
return newApp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConnectChoiceDialog.Choice promptConnectConflict() {
|
||||||
|
if (connectChoiceHandler != null) {
|
||||||
|
return connectChoiceHandler.askChoice(this, getConnectedTarget());
|
||||||
|
}
|
||||||
|
if (GraphicsEnvironment.isHeadless()) {
|
||||||
|
return ConnectChoiceDialog.Choice.ABORT_CURRENT;
|
||||||
|
}
|
||||||
|
return ConnectChoiceDialog.showPrompt(this, getConnectedTarget());
|
||||||
|
}
|
||||||
|
|
||||||
public TerminalPanel getTerminalPanel() {
|
public TerminalPanel getTerminalPanel() {
|
||||||
return terminalPanel;
|
return terminalPanel;
|
||||||
}
|
}
|
||||||
@@ -721,7 +985,22 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
return statusBar;
|
return statusBar;
|
||||||
}
|
}
|
||||||
|
|
||||||
void connect(ConnectionConfig config) {
|
public void connect(ConnectionConfig config) {
|
||||||
|
connect(config, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void connect(ConnectionConfig config, boolean promptIfConnected) {
|
||||||
|
if (promptIfConnected && isConnected() && (!GraphicsEnvironment.isHeadless() || connectChoiceHandler != null)) {
|
||||||
|
ConnectChoiceDialog.Choice choice = promptConnectConflict();
|
||||||
|
if (choice == ConnectChoiceDialog.Choice.NEW_WINDOW) {
|
||||||
|
createNewWindow(config);
|
||||||
|
return;
|
||||||
|
} else if (choice == ConnectChoiceDialog.Choice.CANCEL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// choice == ABORT_CURRENT: proceed to connect in this window
|
||||||
|
}
|
||||||
|
|
||||||
lastHost = config.getHost();
|
lastHost = config.getHost();
|
||||||
lastPort = config.getPort();
|
lastPort = config.getPort();
|
||||||
|
|
||||||
@@ -730,8 +1009,29 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
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());
|
haus.nightmare.j3270.config.Settings.setCodePage(config.getCodePage());
|
||||||
|
if (config.getHostType() != null) {
|
||||||
|
haus.nightmare.j3270.config.Settings.setDefaultHostType(config.getHostType());
|
||||||
|
}
|
||||||
|
if (FileTransferDialog.getLastTransferState() != null && config.isSavedConnection()) {
|
||||||
|
FileTransferDialog.getLastTransferState().hostType = config.getFtHostType();
|
||||||
|
}
|
||||||
|
if (config.getGraphicsMode() != null) {
|
||||||
|
haus.nightmare.j3270.config.Settings.setGraphicsMode(config.getGraphicsMode());
|
||||||
|
}
|
||||||
|
|
||||||
disconnect();
|
Telnet3270Client oldClient = client;
|
||||||
|
if (oldClient != null) {
|
||||||
|
if (fileTransfer != null) {
|
||||||
|
fileTransfer.cancel();
|
||||||
|
fileTransfer = null;
|
||||||
|
}
|
||||||
|
if (macroDialog != null) {
|
||||||
|
macroDialog.stopMacro();
|
||||||
|
}
|
||||||
|
oldClient.removeConnectionListener(this);
|
||||||
|
oldClient.removeScreenUpdateListener(this);
|
||||||
|
client = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (config.getCertificateVerifier() == null) {
|
if (config.getCertificateVerifier() == null) {
|
||||||
config.setCertificateVerifier((chain, authType, exception) ->
|
config.setCertificateVerifier((chain, authType, exception) ->
|
||||||
@@ -749,10 +1049,27 @@ 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);
|
||||||
|
|
||||||
terminalPanel.guardedPack();
|
int targetRows = 24;
|
||||||
|
int targetCols = 80;
|
||||||
|
TerminalModel model = config.getModel();
|
||||||
|
if (model != null) {
|
||||||
|
if (model.isDynamic()) {
|
||||||
|
targetRows = config.getDynamicRows();
|
||||||
|
targetCols = config.getDynamicCols();
|
||||||
|
} else {
|
||||||
|
targetRows = model.getAlternateRows();
|
||||||
|
targetCols = model.getAlternateCols();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
terminalPanel.packForModel(targetRows, targetCols);
|
||||||
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
|
if (oldClient != null) {
|
||||||
|
try {
|
||||||
|
oldClient.disconnect(1000);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
client.connect();
|
client.connect();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.log(Level.WARNING, "Connection failed", e);
|
log.log(Level.WARNING, "Connection failed", e);
|
||||||
@@ -767,20 +1084,31 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
terminalPanel.requestFocusInWindow();
|
terminalPanel.requestFocusInWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void disconnect() {
|
public void disconnect() {
|
||||||
|
disconnect(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disconnect(boolean resetUi) {
|
||||||
if (client != null) {
|
if (client != null) {
|
||||||
if (fileTransfer != null) {
|
if (fileTransfer != null) {
|
||||||
fileTransfer.cancel();
|
fileTransfer.cancel();
|
||||||
fileTransfer = null;
|
fileTransfer = null;
|
||||||
}
|
}
|
||||||
|
if (macroDialog != null) {
|
||||||
|
macroDialog.stopMacro();
|
||||||
|
}
|
||||||
Telnet3270Client c = client;
|
Telnet3270Client c = client;
|
||||||
client = null;
|
client = null;
|
||||||
|
c.removeConnectionListener(this);
|
||||||
|
c.removeScreenUpdateListener(this);
|
||||||
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
c.disconnect();
|
c.disconnect(1000);
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {}
|
||||||
}, "Disconnect-Thread").start();
|
}, "Disconnect-Thread").start();
|
||||||
|
|
||||||
|
if (resetUi) {
|
||||||
Runnable uiReset = () -> {
|
Runnable uiReset = () -> {
|
||||||
terminalPanel.setClient(null);
|
terminalPanel.setClient(null);
|
||||||
statusBar.setClient(null, terminalPanel);
|
statusBar.setClient(null, terminalPanel);
|
||||||
@@ -795,6 +1123,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void adjustFontSize(int delta) {
|
private void adjustFontSize(int delta) {
|
||||||
int current = terminalPanel.getFontSize();
|
int current = terminalPanel.getFontSize();
|
||||||
@@ -843,7 +1172,8 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
String dev = (client != null && client.getTelnetFSM() != null && client.getTelnetFSM().getConnectedLu() != null) ?
|
String dev = (client != null && client.getTelnetFSM() != null && client.getTelnetFSM().getConnectedLu() != null) ?
|
||||||
" [" + client.getTelnetFSM().getConnectedLu() + "]" : "";
|
" [" + client.getTelnetFSM().getConnectedLu() + "]" : "";
|
||||||
setTitle("j3270 — " + lastHost + ":" + lastPort + tlsIndicator + dev);
|
setTitle("j3270 — " + lastHost + ":" + lastPort + tlsIndicator + dev);
|
||||||
terminalPanel.guardedPack();
|
terminalPanel.revalidate();
|
||||||
|
terminalPanel.repaint();
|
||||||
terminalPanel.requestFocusInWindow();
|
terminalPanel.requestFocusInWindow();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -894,7 +1224,8 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
@Override
|
@Override
|
||||||
public void onScreenSizeChanged(int rows, int cols) {
|
public void onScreenSizeChanged(int rows, int cols) {
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
terminalPanel.guardedPack();
|
terminalPanel.revalidate();
|
||||||
|
terminalPanel.repaint();
|
||||||
statusBar.updateStatus();
|
statusBar.updateStatus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1122,6 +1453,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
String configFile = null;
|
String configFile = null;
|
||||||
String cliProxyUrl = null;
|
String cliProxyUrl = null;
|
||||||
String cliProfileName = null;
|
String cliProfileName = null;
|
||||||
|
String cliHostType = 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++) {
|
||||||
@@ -1140,6 +1472,10 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
cliAutoSysUnlock = true;
|
cliAutoSysUnlock = true;
|
||||||
} else if ("--no-auto-sys-unlock".equals(arg)) {
|
} else if ("--no-auto-sys-unlock".equals(arg)) {
|
||||||
cliAutoSysUnlock = false;
|
cliAutoSysUnlock = false;
|
||||||
|
} else if (arg.startsWith("--host-type=")) {
|
||||||
|
cliHostType = arg.substring(12).trim().toUpperCase();
|
||||||
|
} else if (("--host-type".equals(arg) || "-ht".equals(arg)) && i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
||||||
|
cliHostType = args[++i].trim().toUpperCase();
|
||||||
} else if (arg.startsWith("--graphics=")) {
|
} else if (arg.startsWith("--graphics=")) {
|
||||||
cliGraphicsMode = GraphicsMode.fromString(arg.substring(11));
|
cliGraphicsMode = GraphicsMode.fromString(arg.substring(11));
|
||||||
} else if (("--graphics".equals(arg) || "-g".equals(arg)) && i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
} else if (("--graphics".equals(arg) || "-g".equals(arg)) && i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
||||||
@@ -1194,6 +1530,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
final GraphicsMode finalGraphicsMode = cliGraphicsMode;
|
final GraphicsMode finalGraphicsMode = cliGraphicsMode;
|
||||||
final String finalProxyUrl = cliProxyUrl;
|
final String finalProxyUrl = cliProxyUrl;
|
||||||
final String finalProfileName = cliProfileName;
|
final String finalProfileName = cliProfileName;
|
||||||
|
final String finalHostType = cliHostType;
|
||||||
|
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
J3270App app = new J3270App();
|
J3270App app = new J3270App();
|
||||||
@@ -1206,6 +1543,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
}
|
}
|
||||||
if (profile != null) {
|
if (profile != null) {
|
||||||
ConnectionConfig config = profile.toConnectionConfig();
|
ConnectionConfig config = profile.toConnectionConfig();
|
||||||
|
if (finalHostType != null) config.setHostType(finalHostType);
|
||||||
if (finalTls) config.setUseTls(true);
|
if (finalTls) config.setUseTls(true);
|
||||||
if (finalNoVerify) config.setTlsVerifyCert(false);
|
if (finalNoVerify) config.setTlsVerifyCert(false);
|
||||||
if (finalTn3270e != null) config.setTn3270eEnabled(finalTn3270e);
|
if (finalTn3270e != null) config.setTn3270eEnabled(finalTn3270e);
|
||||||
@@ -1262,6 +1600,11 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
if (finalProxyUrl != null) {
|
if (finalProxyUrl != null) {
|
||||||
applyProxyUrl(config, finalProxyUrl);
|
applyProxyUrl(config, finalProxyUrl);
|
||||||
}
|
}
|
||||||
|
if (finalHostType != null) {
|
||||||
|
config.setHostType(finalHostType);
|
||||||
|
} else {
|
||||||
|
config.setHostType(haus.nightmare.j3270.config.Settings.getDefaultHostType());
|
||||||
|
}
|
||||||
app.connect(config);
|
app.connect(config);
|
||||||
} else {
|
} else {
|
||||||
haus.nightmare.j3270.config.Settings.StartupBehavior behavior = haus.nightmare.j3270.config.Settings
|
haus.nightmare.j3270.config.Settings.StartupBehavior behavior = haus.nightmare.j3270.config.Settings
|
||||||
@@ -1274,6 +1617,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
haus.nightmare.j3270.storage.SavedHost autoHost = haus.nightmare.j3270.storage.HostStorage.getAutoConnectHost();
|
haus.nightmare.j3270.storage.SavedHost autoHost = haus.nightmare.j3270.storage.HostStorage.getAutoConnectHost();
|
||||||
if (autoHost != null) {
|
if (autoHost != null) {
|
||||||
ConnectionConfig config = autoHost.toConnectionConfig();
|
ConnectionConfig config = autoHost.toConnectionConfig();
|
||||||
|
if (finalHostType != null) config.setHostType(finalHostType);
|
||||||
if (finalTls) config.setUseTls(true);
|
if (finalTls) config.setUseTls(true);
|
||||||
if (finalNoVerify) config.setTlsVerifyCert(false);
|
if (finalNoVerify) config.setTlsVerifyCert(false);
|
||||||
if (finalTn3270e != null) config.setTn3270eEnabled(finalTn3270e);
|
if (finalTn3270e != null) config.setTn3270eEnabled(finalTn3270e);
|
||||||
@@ -1302,6 +1646,11 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
if (finalProxyUrl != null) {
|
if (finalProxyUrl != null) {
|
||||||
applyProxyUrl(config, finalProxyUrl);
|
applyProxyUrl(config, finalProxyUrl);
|
||||||
}
|
}
|
||||||
|
if (finalHostType != null) {
|
||||||
|
config.setHostType(finalHostType);
|
||||||
|
} else {
|
||||||
|
config.setHostType(haus.nightmare.j3270.config.Settings.getDefaultHostType());
|
||||||
|
}
|
||||||
app.connect(config);
|
app.connect(config);
|
||||||
} else {
|
} else {
|
||||||
SwingUtilities.invokeLater(app::showConnectDialog);
|
SwingUtilities.invokeLater(app::showConnectDialog);
|
||||||
|
|||||||
@@ -163,6 +163,15 @@ public class Settings {
|
|||||||
return getAutoConnectAutoReconnect();
|
return getAutoConnectAutoReconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getDefaultHostType() {
|
||||||
|
return prefs.get("defaultHostType", "TSO");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setDefaultHostType(String hostType) {
|
||||||
|
prefs.put("defaultHostType", (hostType != null && !hostType.trim().isEmpty()) ? hostType.trim().toUpperCase() : "TSO");
|
||||||
|
flushPrefs();
|
||||||
|
}
|
||||||
|
|
||||||
public static void setAutoReconnect(boolean autoReconnect) {
|
public static void setAutoReconnect(boolean autoReconnect) {
|
||||||
setAutoConnectAutoReconnect(autoReconnect);
|
setAutoConnectAutoReconnect(autoReconnect);
|
||||||
}
|
}
|
||||||
@@ -439,6 +448,33 @@ public class Settings {
|
|||||||
flushPrefs();
|
flushPrefs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean getTrimRectSizingHandles() {
|
||||||
|
return prefs.getBoolean("trimRectSizingHandles", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setTrimRectSizingHandles(boolean val) {
|
||||||
|
prefs.putBoolean("trimRectSizingHandles", val);
|
||||||
|
flushPrefs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean getTrimRectRemainAfterEdit() {
|
||||||
|
return prefs.getBoolean("trimRectRemainAfterEdit", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setTrimRectRemainAfterEdit(boolean val) {
|
||||||
|
prefs.putBoolean("trimRectRemainAfterEdit", val);
|
||||||
|
flushPrefs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean getPasteWordBreak() {
|
||||||
|
return prefs.getBoolean("pasteWordBreak", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setPasteWordBreak(boolean val) {
|
||||||
|
prefs.putBoolean("pasteWordBreak", val);
|
||||||
|
flushPrefs();
|
||||||
|
}
|
||||||
|
|
||||||
// ========== Crosshair Ruler ==========
|
// ========== Crosshair Ruler ==========
|
||||||
|
|
||||||
public static boolean getCrosshairRuler() {
|
public static boolean getCrosshairRuler() {
|
||||||
@@ -449,6 +485,71 @@ public class Settings {
|
|||||||
prefs.putBoolean("crosshairRuler", enabled);
|
prefs.putBoolean("crosshairRuler", enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== Phase 25: Hotspots & Mouse Wheel Navigation ==========
|
||||||
|
|
||||||
|
public static boolean getHotspotsEnabled() {
|
||||||
|
return prefs.getBoolean("hotspotsEnabled", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setHotspotsEnabled(boolean enabled) {
|
||||||
|
prefs.putBoolean("hotspotsEnabled", enabled);
|
||||||
|
flushPrefs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean getHotspotsUrlEnabled() {
|
||||||
|
return prefs.getBoolean("hotspotsUrlEnabled", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setHotspotsUrlEnabled(boolean enabled) {
|
||||||
|
prefs.putBoolean("hotspotsUrlEnabled", enabled);
|
||||||
|
flushPrefs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean getHotspotsPfEnabled() {
|
||||||
|
return prefs.getBoolean("hotspotsPfEnabled", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setHotspotsPfEnabled(boolean enabled) {
|
||||||
|
prefs.putBoolean("hotspotsPfEnabled", enabled);
|
||||||
|
flushPrefs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean getHotspotsMenuChoicesEnabled() {
|
||||||
|
return prefs.getBoolean("hotspotsMenuChoicesEnabled", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setHotspotsMenuChoicesEnabled(boolean enabled) {
|
||||||
|
prefs.putBoolean("hotspotsMenuChoicesEnabled", enabled);
|
||||||
|
flushPrefs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean getMouseWheelEnabled() {
|
||||||
|
return prefs.getBoolean("mouseWheelEnabled", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setMouseWheelEnabled(boolean enabled) {
|
||||||
|
prefs.putBoolean("mouseWheelEnabled", enabled);
|
||||||
|
flushPrefs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getMouseWheelUpKey() {
|
||||||
|
return prefs.get("mouseWheelUpKey", "[pf7]");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setMouseWheelUpKey(String key) {
|
||||||
|
prefs.put("mouseWheelUpKey", key != null && !key.trim().isEmpty() ? key.trim() : "[pf7]");
|
||||||
|
flushPrefs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getMouseWheelDownKey() {
|
||||||
|
return prefs.get("mouseWheelDownKey", "[pf8]");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setMouseWheelDownKey(String key) {
|
||||||
|
prefs.put("mouseWheelDownKey", key != null && !key.trim().isEmpty() ? key.trim() : "[pf8]");
|
||||||
|
flushPrefs();
|
||||||
|
}
|
||||||
|
|
||||||
// ========== Status Bar Visibility ==========
|
// ========== Status Bar Visibility ==========
|
||||||
|
|
||||||
public static boolean getStatusBarVisible() {
|
public static boolean getStatusBarVisible() {
|
||||||
@@ -646,6 +747,11 @@ public class Settings {
|
|||||||
case "dynamic_cols":
|
case "dynamic_cols":
|
||||||
setDynamicCols(Integer.parseInt(value));
|
setDynamicCols(Integer.parseInt(value));
|
||||||
break;
|
break;
|
||||||
|
case "hostType":
|
||||||
|
case "hosttype":
|
||||||
|
case "host_type":
|
||||||
|
setDefaultHostType(value);
|
||||||
|
break;
|
||||||
case "blockSelectMode": setBlockSelectMode(Boolean.parseBoolean(value)); break;
|
case "blockSelectMode": setBlockSelectMode(Boolean.parseBoolean(value)); break;
|
||||||
case "blockPaste": setBlockPaste(Boolean.parseBoolean(value)); break;
|
case "blockPaste": setBlockPaste(Boolean.parseBoolean(value)); break;
|
||||||
case "autoReconnect":
|
case "autoReconnect":
|
||||||
@@ -685,6 +791,18 @@ public class Settings {
|
|||||||
case "pasteStopAtProtected":
|
case "pasteStopAtProtected":
|
||||||
setPasteStopAtProtectedLine(Boolean.parseBoolean(value));
|
setPasteStopAtProtectedLine(Boolean.parseBoolean(value));
|
||||||
break;
|
break;
|
||||||
|
case "trimRectSizingHandles":
|
||||||
|
case "sizingHandles":
|
||||||
|
setTrimRectSizingHandles(Boolean.parseBoolean(value));
|
||||||
|
break;
|
||||||
|
case "trimRectRemainAfterEdit":
|
||||||
|
case "remainAfterEdit":
|
||||||
|
setTrimRectRemainAfterEdit(Boolean.parseBoolean(value));
|
||||||
|
break;
|
||||||
|
case "pasteWordBreak":
|
||||||
|
case "wordBreak":
|
||||||
|
setPasteWordBreak(Boolean.parseBoolean(value));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
log.warning("Unknown behavior/connection key: " + key);
|
log.warning("Unknown behavior/connection key: " + key);
|
||||||
}
|
}
|
||||||
@@ -706,6 +824,18 @@ public class Settings {
|
|||||||
case "pasteStopAtProtected":
|
case "pasteStopAtProtected":
|
||||||
setPasteStopAtProtectedLine(Boolean.parseBoolean(value));
|
setPasteStopAtProtectedLine(Boolean.parseBoolean(value));
|
||||||
break;
|
break;
|
||||||
|
case "trimRectSizingHandles":
|
||||||
|
case "sizingHandles":
|
||||||
|
setTrimRectSizingHandles(Boolean.parseBoolean(value));
|
||||||
|
break;
|
||||||
|
case "trimRectRemainAfterEdit":
|
||||||
|
case "remainAfterEdit":
|
||||||
|
setTrimRectRemainAfterEdit(Boolean.parseBoolean(value));
|
||||||
|
break;
|
||||||
|
case "pasteWordBreak":
|
||||||
|
case "wordBreak":
|
||||||
|
setPasteWordBreak(Boolean.parseBoolean(value));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
log.warning("Unknown clipboard key: " + key);
|
log.warning("Unknown clipboard key: " + key);
|
||||||
}
|
}
|
||||||
@@ -829,6 +959,7 @@ public class Settings {
|
|||||||
w.println("autoConnectTn3270e = " + getAutoConnectTn3270e());
|
w.println("autoConnectTn3270e = " + getAutoConnectTn3270e());
|
||||||
}
|
}
|
||||||
w.println("codePage = " + getCodePage());
|
w.println("codePage = " + getCodePage());
|
||||||
|
w.println("hostType = " + getDefaultHostType());
|
||||||
w.println("dynamicRows = " + getDynamicRows());
|
w.println("dynamicRows = " + getDynamicRows());
|
||||||
w.println("dynamicCols = " + getDynamicCols());
|
w.println("dynamicCols = " + getDynamicCols());
|
||||||
w.println("blockSelectMode = " + getBlockSelectMode());
|
w.println("blockSelectMode = " + getBlockSelectMode());
|
||||||
@@ -840,6 +971,9 @@ public class Settings {
|
|||||||
w.println("autoSysUnlock = " + getAutoSysUnlock());
|
w.println("autoSysUnlock = " + getAutoSysUnlock());
|
||||||
w.println("enablePasteFromExcel = " + getEnablePasteFromExcel());
|
w.println("enablePasteFromExcel = " + getEnablePasteFromExcel());
|
||||||
w.println("pasteStopAtProtectedLine = " + getPasteStopAtProtectedLine());
|
w.println("pasteStopAtProtectedLine = " + getPasteStopAtProtectedLine());
|
||||||
|
w.println("trimRectSizingHandles = " + getTrimRectSizingHandles());
|
||||||
|
w.println("trimRectRemainAfterEdit = " + getTrimRectRemainAfterEdit());
|
||||||
|
w.println("pasteWordBreak = " + getPasteWordBreak());
|
||||||
w.println();
|
w.println();
|
||||||
|
|
||||||
// [entryassist]
|
// [entryassist]
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import haus.nightmare.j3270.ui.HostDirectoryDialog;
|
|||||||
import haus.nightmare.j3270.ui.ThemeManager;
|
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 haus.nightmare.lib3270j.ft.FTConstants;
|
||||||
|
import haus.nightmare.lib3270j.ft.dir.HostDirectoryEntry;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.border.EmptyBorder;
|
import javax.swing.border.EmptyBorder;
|
||||||
@@ -28,6 +29,8 @@ public class FileTransferDialog extends JDialog {
|
|||||||
public String lrecl = "";
|
public String lrecl = "";
|
||||||
public String blksize = "";
|
public String blksize = "";
|
||||||
public String space = "";
|
public String space = "";
|
||||||
|
public FTConfig.AllocationUnit units = FTConfig.AllocationUnit.TRACKS;
|
||||||
|
public String dirBlocks = "";
|
||||||
public String options = "";
|
public String options = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,6 +85,8 @@ public class FileTransferDialog extends JDialog {
|
|||||||
private JTextField lreclField;
|
private JTextField lreclField;
|
||||||
private JTextField blksizeField;
|
private JTextField blksizeField;
|
||||||
private JTextField spaceField;
|
private JTextField spaceField;
|
||||||
|
private JComboBox<FTConfig.AllocationUnit> unitsCombo;
|
||||||
|
private JTextField dirField;
|
||||||
|
|
||||||
// VM specific
|
// VM specific
|
||||||
private JTextField optionsField;
|
private JTextField optionsField;
|
||||||
@@ -220,7 +225,7 @@ public class FileTransferDialog extends JDialog {
|
|||||||
formPanel.add(createRow("File Options:", optionsPanel2));
|
formPanel.add(createRow("File Options:", optionsPanel2));
|
||||||
|
|
||||||
// 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(3, 4, 5, 5));
|
||||||
hostOptsPanel.setOpaque(false);
|
hostOptsPanel.setOpaque(false);
|
||||||
hostOptsPanel.setBorder(ThemeManager.createTitledBorder("TSO Allocation Options (Send Only)"));
|
hostOptsPanel.setBorder(ThemeManager.createTitledBorder("TSO Allocation Options (Send Only)"));
|
||||||
|
|
||||||
@@ -236,10 +241,24 @@ public class FileTransferDialog extends JDialog {
|
|||||||
blksizeField = new JTextField(5);
|
blksizeField = new JTextField(5);
|
||||||
hostOptsPanel.add(blksizeField);
|
hostOptsPanel.add(blksizeField);
|
||||||
|
|
||||||
hostOptsPanel.add(new JLabel("SPACE:"));
|
hostOptsPanel.add(new JLabel("Units:"));
|
||||||
|
unitsCombo = new JComboBox<>(new FTConfig.AllocationUnit[]{
|
||||||
|
FTConfig.AllocationUnit.TRACKS,
|
||||||
|
FTConfig.AllocationUnit.CYLINDERS,
|
||||||
|
FTConfig.AllocationUnit.AVBLOCK,
|
||||||
|
FTConfig.AllocationUnit.DEFAULT
|
||||||
|
});
|
||||||
|
unitsCombo.setSelectedItem(FTConfig.AllocationUnit.TRACKS);
|
||||||
|
hostOptsPanel.add(unitsCombo);
|
||||||
|
|
||||||
|
hostOptsPanel.add(new JLabel("SPACE (Pri,Sec):"));
|
||||||
spaceField = new JTextField(15);
|
spaceField = new JTextField(15);
|
||||||
hostOptsPanel.add(spaceField);
|
hostOptsPanel.add(spaceField);
|
||||||
|
|
||||||
|
hostOptsPanel.add(new JLabel("Directory (PDS):"));
|
||||||
|
dirField = new JTextField(5);
|
||||||
|
hostOptsPanel.add(dirField);
|
||||||
|
|
||||||
formPanel.add(Box.createRigidArea(new Dimension(0, 10)));
|
formPanel.add(Box.createRigidArea(new Dimension(0, 10)));
|
||||||
formPanel.add(hostOptsPanel);
|
formPanel.add(hostOptsPanel);
|
||||||
|
|
||||||
@@ -306,9 +325,25 @@ public class FileTransferDialog extends JDialog {
|
|||||||
if (lastTransferState.recfm != null) recfmField.setText(lastTransferState.recfm);
|
if (lastTransferState.recfm != null) recfmField.setText(lastTransferState.recfm);
|
||||||
if (lastTransferState.lrecl != null) lreclField.setText(lastTransferState.lrecl);
|
if (lastTransferState.lrecl != null) lreclField.setText(lastTransferState.lrecl);
|
||||||
if (lastTransferState.blksize != null) blksizeField.setText(lastTransferState.blksize);
|
if (lastTransferState.blksize != null) blksizeField.setText(lastTransferState.blksize);
|
||||||
|
if (lastTransferState.units != null) unitsCombo.setSelectedItem(lastTransferState.units);
|
||||||
if (lastTransferState.space != null) spaceField.setText(lastTransferState.space);
|
if (lastTransferState.space != null) spaceField.setText(lastTransferState.space);
|
||||||
|
if (lastTransferState.dirBlocks != null) dirField.setText(lastTransferState.dirBlocks);
|
||||||
if (lastTransferState.options != null) optionsField.setText(lastTransferState.options);
|
if (lastTransferState.options != null) optionsField.setText(lastTransferState.options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When using a saved connection (or when no previous transfer state exists for this connection),
|
||||||
|
// use the configured host type (CMS/TSO/CICS) for File Transfers
|
||||||
|
if (coordinator != null && coordinator.getClient() != null) {
|
||||||
|
haus.nightmare.lib3270j.ConnectionConfig cfg = coordinator.getClient().getConfig();
|
||||||
|
if (cfg != null) {
|
||||||
|
FTConfig.HostType configuredType = cfg.getFtHostType();
|
||||||
|
if (configuredType != null) {
|
||||||
|
if (cfg.isSavedConnection() || lastTransferState == null) {
|
||||||
|
hostTypeCombo.setSelectedItem(configuredType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateOptionStates() {
|
private void updateOptionStates() {
|
||||||
@@ -327,7 +362,9 @@ public class FileTransferDialog extends JDialog {
|
|||||||
recfmField.setEnabled(isSend && isTso);
|
recfmField.setEnabled(isSend && isTso);
|
||||||
lreclField.setEnabled(isSend && isTso);
|
lreclField.setEnabled(isSend && isTso);
|
||||||
blksizeField.setEnabled(isSend && isTso);
|
blksizeField.setEnabled(isSend && isTso);
|
||||||
|
unitsCombo.setEnabled(isSend && isTso);
|
||||||
spaceField.setEnabled(isSend && isTso);
|
spaceField.setEnabled(isSend && isTso);
|
||||||
|
dirField.setEnabled(isSend && isTso);
|
||||||
optionsField.setEnabled(isCms);
|
optionsField.setEnabled(isCms);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,11 +396,26 @@ public class FileTransferDialog extends JDialog {
|
|||||||
private void browseHostDirectory() {
|
private void browseHostDirectory() {
|
||||||
FTConfig.HostType hostType = (FTConfig.HostType) hostTypeCombo.getSelectedItem();
|
FTConfig.HostType hostType = (FTConfig.HostType) hostTypeCombo.getSelectedItem();
|
||||||
String initialQuery = hostFileField.getText().trim();
|
String initialQuery = hostFileField.getText().trim();
|
||||||
|
if (hostType == FTConfig.HostType.CMS && (initialQuery.isEmpty() || initialQuery.equals("* * *") || initialQuery.equals("*"))) {
|
||||||
|
initialQuery = "* * A";
|
||||||
|
}
|
||||||
HostDirectoryDialog dialog = new HostDirectoryDialog(this, coordinator.getClient(), hostType, initialQuery);
|
HostDirectoryDialog dialog = new HostDirectoryDialog(this, coordinator.getClient(), hostType, initialQuery);
|
||||||
dialog.setVisible(true);
|
dialog.setVisible(true);
|
||||||
|
|
||||||
if (dialog.isConfirmed() && dialog.getSelectedHostFile() != null) {
|
if (dialog.isConfirmed() && dialog.getSelectedHostFile() != null) {
|
||||||
hostFileField.setText(dialog.getSelectedHostFile());
|
hostFileField.setText(dialog.getSelectedHostFile());
|
||||||
|
HostDirectoryEntry entry = dialog.getSelectedEntry();
|
||||||
|
if (entry != null) {
|
||||||
|
if (entry.getRecfm() != null && !entry.getRecfm().trim().isEmpty()) {
|
||||||
|
recfmField.setText(entry.getRecfm().trim());
|
||||||
|
}
|
||||||
|
if (entry.getLrecl() > 0) {
|
||||||
|
lreclField.setText(String.valueOf(entry.getLrecl()));
|
||||||
|
}
|
||||||
|
if (entry.getBlksize() > 0) {
|
||||||
|
blksizeField.setText(String.valueOf(entry.getBlksize()));
|
||||||
|
}
|
||||||
|
}
|
||||||
boolean shouldAutofill = localFileField.getText().trim().isEmpty() ||
|
boolean shouldAutofill = localFileField.getText().trim().isEmpty() ||
|
||||||
(lastTransferState != null && localFileField.getText().trim().equals(lastTransferState.localFile));
|
(lastTransferState != null && localFileField.getText().trim().equals(lastTransferState.localFile));
|
||||||
if (shouldAutofill) {
|
if (shouldAutofill) {
|
||||||
@@ -410,7 +462,10 @@ public class FileTransferDialog extends JDialog {
|
|||||||
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());
|
||||||
|
FTConfig.AllocationUnit selectedUnits = (FTConfig.AllocationUnit) unitsCombo.getSelectedItem();
|
||||||
|
config.setUnits(selectedUnits != null ? selectedUnits : FTConfig.AllocationUnit.DEFAULT);
|
||||||
config.setSpace(spaceField.getText().trim());
|
config.setSpace(spaceField.getText().trim());
|
||||||
|
config.setDirectoryBlocks(dirField.getText().trim());
|
||||||
config.setOptions(optionsField.getText().trim());
|
config.setOptions(optionsField.getText().trim());
|
||||||
|
|
||||||
recordSessionTransfer();
|
recordSessionTransfer();
|
||||||
@@ -439,7 +494,9 @@ public class FileTransferDialog extends JDialog {
|
|||||||
state.recfm = recfmField.getText().trim();
|
state.recfm = recfmField.getText().trim();
|
||||||
state.lrecl = lreclField.getText().trim();
|
state.lrecl = lreclField.getText().trim();
|
||||||
state.blksize = blksizeField.getText().trim();
|
state.blksize = blksizeField.getText().trim();
|
||||||
|
state.units = (FTConfig.AllocationUnit) unitsCombo.getSelectedItem();
|
||||||
state.space = spaceField.getText().trim();
|
state.space = spaceField.getText().trim();
|
||||||
|
state.dirBlocks = dirField.getText().trim();
|
||||||
state.options = optionsField.getText().trim();
|
state.options = optionsField.getText().trim();
|
||||||
lastTransferState = state;
|
lastTransferState = state;
|
||||||
}
|
}
|
||||||
@@ -459,7 +516,9 @@ public class FileTransferDialog extends JDialog {
|
|||||||
public JTextField getRecfmField() { return recfmField; }
|
public JTextField getRecfmField() { return recfmField; }
|
||||||
public JTextField getLreclField() { return lreclField; }
|
public JTextField getLreclField() { return lreclField; }
|
||||||
public JTextField getBlksizeField() { return blksizeField; }
|
public JTextField getBlksizeField() { return blksizeField; }
|
||||||
|
public JComboBox<FTConfig.AllocationUnit> getUnitsCombo() { return unitsCombo; }
|
||||||
public JTextField getSpaceField() { return spaceField; }
|
public JTextField getSpaceField() { return spaceField; }
|
||||||
|
public JTextField getDirField() { return dirField; }
|
||||||
public JTextField getOptionsField() { return optionsField; }
|
public JTextField getOptionsField() { return optionsField; }
|
||||||
|
|
||||||
private void applyTheme(Container container) {
|
private void applyTheme(Container container) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package haus.nightmare.j3270.storage;
|
package haus.nightmare.j3270.storage;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
@@ -109,6 +110,267 @@ public class HostStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ================= INI File Export / Import =================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export all currently saved host connections to an INI file.
|
||||||
|
*/
|
||||||
|
public static synchronized void exportToIniFile(File file) throws IOException {
|
||||||
|
List<SavedHost> hosts = getSavedHosts();
|
||||||
|
exportToIniFile(file, hosts);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized void exportToIniFile(String path) throws IOException {
|
||||||
|
exportToIniFile(new File(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export a specified list of host connections to an INI file.
|
||||||
|
*/
|
||||||
|
public static void exportToIniFile(File file, List<SavedHost> hosts) throws IOException {
|
||||||
|
if (file == null) {
|
||||||
|
throw new IllegalArgumentException("Target file cannot be null");
|
||||||
|
}
|
||||||
|
File parent = file.getParentFile();
|
||||||
|
if (parent != null && !parent.exists()) {
|
||||||
|
parent.mkdirs();
|
||||||
|
}
|
||||||
|
try (PrintWriter w = new PrintWriter(new BufferedWriter(new FileWriter(file)))) {
|
||||||
|
w.println("; j3270 Saved Connections Configuration File");
|
||||||
|
w.println("; Exported on " + new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date()));
|
||||||
|
w.println("; Connections count: " + (hosts != null ? hosts.size() : 0));
|
||||||
|
w.println();
|
||||||
|
|
||||||
|
if (hosts != null) {
|
||||||
|
for (SavedHost h : hosts) {
|
||||||
|
String section = (h.getName() != null && !h.getName().trim().isEmpty())
|
||||||
|
? h.getName().trim()
|
||||||
|
: (h.getHost() != null && !h.getHost().trim().isEmpty() ? h.getHost().trim() : "Connection");
|
||||||
|
w.println("[" + section + "]");
|
||||||
|
if (h.getId() != null && !h.getId().trim().isEmpty()) {
|
||||||
|
w.println("id = " + h.getId());
|
||||||
|
}
|
||||||
|
w.println("name = " + (h.getName() != null ? h.getName() : ""));
|
||||||
|
w.println("host = " + (h.getHost() != null ? h.getHost() : ""));
|
||||||
|
w.println("port = " + h.getPort());
|
||||||
|
w.println("model = " + h.getModel());
|
||||||
|
w.println("dynamicRows = " + h.getDynamicRows());
|
||||||
|
w.println("dynamicCols = " + h.getDynamicCols());
|
||||||
|
w.println("luName = " + (h.getLuName() != null ? h.getLuName() : ""));
|
||||||
|
w.println("autoConnect = " + h.isAutoConnect());
|
||||||
|
w.println("hostType = " + (h.getHostType() != null ? h.getHostType() : "TSO"));
|
||||||
|
w.println("useTls = " + h.isUseTls());
|
||||||
|
w.println("tlsVerifyCert = " + h.isTlsVerifyCert());
|
||||||
|
w.println("tn3270e = " + h.isTn3270e());
|
||||||
|
w.println("graphicsMode = " + (h.getGraphicsMode() != null ? h.getGraphicsMode() : "BOTH"));
|
||||||
|
w.println("codePage = " + (h.getCodePage() != null ? h.getCodePage() : "037"));
|
||||||
|
w.println("proxyType = " + (h.getProxyType() != null ? h.getProxyType() : "NONE"));
|
||||||
|
w.println("proxyHost = " + (h.getProxyHost() != null ? h.getProxyHost() : ""));
|
||||||
|
w.println("proxyPort = " + h.getProxyPort());
|
||||||
|
w.println("proxyUsername = " + (h.getProxyUsername() != null ? h.getProxyUsername() : ""));
|
||||||
|
w.println("proxyPassword = " + (h.getProxyPassword() != null ? h.getProxyPassword() : ""));
|
||||||
|
w.println();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.info("Saved connections exported to: " + file.getAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load connections from an INI file without saving them to persistent storage.
|
||||||
|
*/
|
||||||
|
public static List<SavedHost> loadFromIniFile(File file) throws IOException {
|
||||||
|
if (file == null || !file.exists()) {
|
||||||
|
throw new FileNotFoundException("Connections file not found: " + (file != null ? file.getAbsolutePath() : "null"));
|
||||||
|
}
|
||||||
|
List<SavedHost> hosts = new ArrayList<>();
|
||||||
|
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
|
||||||
|
String line;
|
||||||
|
SavedHost current = null;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
line = line.trim();
|
||||||
|
if (line.isEmpty() || line.startsWith(";") || line.startsWith("#")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (line.startsWith("[") && line.endsWith("]")) {
|
||||||
|
if (current != null) {
|
||||||
|
hosts.add(current);
|
||||||
|
}
|
||||||
|
String section = line.substring(1, line.length() - 1).trim();
|
||||||
|
if (section.toLowerCase().startsWith("connection:")) {
|
||||||
|
section = section.substring("connection:".length()).trim();
|
||||||
|
} else if (section.toLowerCase().startsWith("host:")) {
|
||||||
|
section = section.substring("host:".length()).trim();
|
||||||
|
}
|
||||||
|
current = new SavedHost();
|
||||||
|
current.setName(section);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int eq = line.indexOf('=');
|
||||||
|
if (eq < 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String key = line.substring(0, eq).trim();
|
||||||
|
String value = line.substring(eq + 1).trim();
|
||||||
|
if (current == null) {
|
||||||
|
current = new SavedHost();
|
||||||
|
current.setName("Default");
|
||||||
|
}
|
||||||
|
applyIniEntry(current, key, value);
|
||||||
|
}
|
||||||
|
if (current != null) {
|
||||||
|
hosts.add(current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<SavedHost> loadFromIniFile(String path) throws IOException {
|
||||||
|
return loadFromIniFile(new File(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import connections from an INI file and merge them into persistent storage.
|
||||||
|
* Updates existing profiles matching by ID or Name, and appends new profiles.
|
||||||
|
*/
|
||||||
|
public static synchronized List<SavedHost> importFromIniFile(File file) throws IOException {
|
||||||
|
List<SavedHost> imported = loadFromIniFile(file);
|
||||||
|
if (imported == null || imported.isEmpty()) {
|
||||||
|
return getSavedHosts();
|
||||||
|
}
|
||||||
|
List<SavedHost> current = new ArrayList<>(getSavedHosts());
|
||||||
|
for (SavedHost host : imported) {
|
||||||
|
int index = -1;
|
||||||
|
for (int i = 0; i < current.size(); i++) {
|
||||||
|
SavedHost existing = current.get(i);
|
||||||
|
if ((host.getId() != null && !host.getId().trim().isEmpty() && Objects.equals(existing.getId(), host.getId())) ||
|
||||||
|
(host.getName() != null && !host.getName().trim().isEmpty() &&
|
||||||
|
host.getName().equalsIgnoreCase(existing.getName()))) {
|
||||||
|
index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (host.isAutoConnect()) {
|
||||||
|
for (SavedHost c : current) {
|
||||||
|
c.setAutoConnect(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (index >= 0) {
|
||||||
|
if (host.getId() == null || host.getId().trim().isEmpty()) {
|
||||||
|
host.setId(current.get(index).getId());
|
||||||
|
}
|
||||||
|
current.set(index, host);
|
||||||
|
} else {
|
||||||
|
current.add(host);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
saveAll(current);
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized List<SavedHost> importFromIniFile(String path) throws IOException {
|
||||||
|
return importFromIniFile(new File(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void applyIniEntry(SavedHost h, String key, String value) {
|
||||||
|
String lowerKey = key.toLowerCase();
|
||||||
|
switch (lowerKey) {
|
||||||
|
case "id":
|
||||||
|
if (!value.isEmpty()) h.setId(value);
|
||||||
|
break;
|
||||||
|
case "name":
|
||||||
|
case "profilename":
|
||||||
|
case "profile":
|
||||||
|
if (!value.isEmpty()) h.setName(value);
|
||||||
|
break;
|
||||||
|
case "host":
|
||||||
|
case "hostname":
|
||||||
|
case "address":
|
||||||
|
h.setHost(value);
|
||||||
|
break;
|
||||||
|
case "port":
|
||||||
|
h.setPort(parseInt(value, 23));
|
||||||
|
break;
|
||||||
|
case "model":
|
||||||
|
case "terminalmodel":
|
||||||
|
h.setModel(parseInt(value, 4));
|
||||||
|
break;
|
||||||
|
case "dynamicrows":
|
||||||
|
case "dynamic_rows":
|
||||||
|
case "rows":
|
||||||
|
h.setDynamicRows(parseInt(value, 62));
|
||||||
|
break;
|
||||||
|
case "dynamiccols":
|
||||||
|
case "dynamic_cols":
|
||||||
|
case "cols":
|
||||||
|
h.setDynamicCols(parseInt(value, 160));
|
||||||
|
break;
|
||||||
|
case "luname":
|
||||||
|
case "lu_name":
|
||||||
|
case "lu":
|
||||||
|
h.setLuName(value);
|
||||||
|
break;
|
||||||
|
case "autoconnect":
|
||||||
|
case "auto_connect":
|
||||||
|
h.setAutoConnect(Boolean.parseBoolean(value));
|
||||||
|
break;
|
||||||
|
case "hosttype":
|
||||||
|
case "host_type":
|
||||||
|
case "type":
|
||||||
|
h.setHostType(value.toUpperCase());
|
||||||
|
break;
|
||||||
|
case "usetls":
|
||||||
|
case "use_tls":
|
||||||
|
case "tls":
|
||||||
|
case "ssl":
|
||||||
|
h.setUseTls(Boolean.parseBoolean(value));
|
||||||
|
break;
|
||||||
|
case "tlsverifycert":
|
||||||
|
case "verifycert":
|
||||||
|
case "tls_verify_cert":
|
||||||
|
h.setTlsVerifyCert(Boolean.parseBoolean(value));
|
||||||
|
break;
|
||||||
|
case "tn3270e":
|
||||||
|
case "enabletn3270e":
|
||||||
|
h.setTn3270e(Boolean.parseBoolean(value));
|
||||||
|
break;
|
||||||
|
case "graphicsmode":
|
||||||
|
case "graphics_mode":
|
||||||
|
case "graphics":
|
||||||
|
h.setGraphicsMode(value.toUpperCase());
|
||||||
|
break;
|
||||||
|
case "codepage":
|
||||||
|
case "code_page":
|
||||||
|
case "charset":
|
||||||
|
h.setCodePage(value);
|
||||||
|
break;
|
||||||
|
case "proxytype":
|
||||||
|
case "proxy_type":
|
||||||
|
h.setProxyType(value.toUpperCase());
|
||||||
|
break;
|
||||||
|
case "proxyhost":
|
||||||
|
case "proxy_host":
|
||||||
|
h.setProxyHost(value);
|
||||||
|
break;
|
||||||
|
case "proxyport":
|
||||||
|
case "proxy_port":
|
||||||
|
h.setProxyPort(parseInt(value, 0));
|
||||||
|
break;
|
||||||
|
case "proxyusername":
|
||||||
|
case "proxy_username":
|
||||||
|
case "proxyuser":
|
||||||
|
h.setProxyUsername(value);
|
||||||
|
break;
|
||||||
|
case "proxypassword":
|
||||||
|
case "proxy_password":
|
||||||
|
case "proxypass":
|
||||||
|
h.setProxyPassword(value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ================= JSON Serialization =================
|
// ================= JSON Serialization =================
|
||||||
|
|
||||||
public static String serializeHostsJson(List<SavedHost> hosts) {
|
public static String serializeHostsJson(List<SavedHost> hosts) {
|
||||||
@@ -234,7 +496,6 @@ public class HostStorage {
|
|||||||
if (map.containsKey("tn3270e")) h.setTn3270e(parseBool(map.get("tn3270e"), true));
|
if (map.containsKey("tn3270e")) h.setTn3270e(parseBool(map.get("tn3270e"), true));
|
||||||
if (map.containsKey("graphicsMode")) {
|
if (map.containsKey("graphicsMode")) {
|
||||||
String gm = String.valueOf(map.get("graphicsMode"));
|
String gm = String.valueOf(map.get("graphicsMode"));
|
||||||
if ("PROGRAMMED_SYMBOLS".equalsIgnoreCase(gm)) gm = "BOTH";
|
|
||||||
h.setGraphicsMode(gm);
|
h.setGraphicsMode(gm);
|
||||||
}
|
}
|
||||||
if (map.containsKey("codePage")) h.setCodePage(String.valueOf(map.get("codePage")));
|
if (map.containsKey("codePage")) h.setCodePage(String.valueOf(map.get("codePage")));
|
||||||
|
|||||||
@@ -108,6 +108,10 @@ public class SavedHost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ConnectionConfig config = new ConnectionConfig(host, port, tm, useTls);
|
ConnectionConfig config = new ConnectionConfig(host, port, tm, useTls);
|
||||||
|
config.setSavedConnection(true);
|
||||||
|
config.setSavedProfileId(this.id);
|
||||||
|
config.setSavedProfileName(this.name);
|
||||||
|
config.setHostType(this.hostType != null ? this.hostType : "TSO");
|
||||||
if (model == 0 || tm.isDynamic()) {
|
if (model == 0 || tm.isDynamic()) {
|
||||||
config.setDynamicDimensions(dynamicRows, dynamicCols);
|
config.setDynamicDimensions(dynamicRows, dynamicCols);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,146 @@
|
|||||||
|
package haus.nightmare.j3270.ui;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dialogue shown when a connection is initiated while j3270 is already connected.
|
||||||
|
* Asks the user whether the connection should be opened in a new window or
|
||||||
|
* if the current connection should be aborted.
|
||||||
|
*/
|
||||||
|
public class ConnectChoiceDialog extends JDialog {
|
||||||
|
|
||||||
|
public enum Choice {
|
||||||
|
NEW_WINDOW,
|
||||||
|
ABORT_CURRENT,
|
||||||
|
CANCEL
|
||||||
|
}
|
||||||
|
|
||||||
|
private Choice result = Choice.CANCEL;
|
||||||
|
private final JButton newWindowBtn;
|
||||||
|
private final JButton abortBtn;
|
||||||
|
private final JButton cancelBtn;
|
||||||
|
private final JLabel titleLabel;
|
||||||
|
private final JLabel line1;
|
||||||
|
private final JLabel line2;
|
||||||
|
|
||||||
|
public ConnectChoiceDialog(Window owner, String currentTarget) {
|
||||||
|
super(owner, "Active Connection", ModalityType.APPLICATION_MODAL);
|
||||||
|
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
||||||
|
setResizable(false);
|
||||||
|
|
||||||
|
JPanel mainPanel = new JPanel(new BorderLayout(16, 16));
|
||||||
|
mainPanel.setBorder(new EmptyBorder(20, 24, 20, 24));
|
||||||
|
|
||||||
|
// Message Panel
|
||||||
|
JPanel messagePanel = new JPanel();
|
||||||
|
messagePanel.setLayout(new BoxLayout(messagePanel, BoxLayout.Y_AXIS));
|
||||||
|
messagePanel.setOpaque(false);
|
||||||
|
|
||||||
|
titleLabel = new JLabel("Connection Already Active");
|
||||||
|
titleLabel.setFont(ThemeManager.getUiFont(Font.BOLD, 2));
|
||||||
|
messagePanel.add(titleLabel);
|
||||||
|
messagePanel.add(Box.createVerticalStrut(10));
|
||||||
|
|
||||||
|
String targetText = (currentTarget != null && !currentTarget.trim().isEmpty())
|
||||||
|
? currentTarget.trim()
|
||||||
|
: "the host";
|
||||||
|
line1 = new JLabel("j3270 is currently connected to " + targetText + ".");
|
||||||
|
line1.setFont(ThemeManager.getUiFont(Font.PLAIN, 0));
|
||||||
|
messagePanel.add(line1);
|
||||||
|
|
||||||
|
messagePanel.add(Box.createVerticalStrut(6));
|
||||||
|
line2 = new JLabel("Would you like to open the connection in a new window or abort the current connection?");
|
||||||
|
line2.setFont(ThemeManager.getUiFont(Font.PLAIN, 0));
|
||||||
|
messagePanel.add(line2);
|
||||||
|
|
||||||
|
mainPanel.add(messagePanel, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 0));
|
||||||
|
buttonPanel.setOpaque(false);
|
||||||
|
|
||||||
|
cancelBtn = new JButton("Cancel");
|
||||||
|
ThemeManager.styleButton(cancelBtn, ThemeManager.ButtonVariant.CANCEL);
|
||||||
|
cancelBtn.setFont(ThemeManager.getUiFont(Font.PLAIN, 0));
|
||||||
|
cancelBtn.addActionListener(e -> {
|
||||||
|
result = Choice.CANCEL;
|
||||||
|
dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
abortBtn = new JButton("Abort Current Connection");
|
||||||
|
ThemeManager.styleButton(abortBtn, ThemeManager.ButtonVariant.DANGER);
|
||||||
|
abortBtn.setFont(ThemeManager.getUiFont(Font.PLAIN, 0));
|
||||||
|
abortBtn.addActionListener(e -> {
|
||||||
|
result = Choice.ABORT_CURRENT;
|
||||||
|
dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
newWindowBtn = new JButton("New Window");
|
||||||
|
ThemeManager.styleButton(newWindowBtn, ThemeManager.ButtonVariant.PRIMARY);
|
||||||
|
newWindowBtn.setFont(ThemeManager.getUiFont(Font.BOLD, 0));
|
||||||
|
newWindowBtn.addActionListener(e -> {
|
||||||
|
result = Choice.NEW_WINDOW;
|
||||||
|
dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonPanel.add(cancelBtn);
|
||||||
|
buttonPanel.add(abortBtn);
|
||||||
|
buttonPanel.add(newWindowBtn);
|
||||||
|
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
setContentPane(mainPanel);
|
||||||
|
ThemeManager.applyThemeToWindow(this);
|
||||||
|
|
||||||
|
// ESC key cancels
|
||||||
|
getRootPane().registerKeyboardAction(e -> {
|
||||||
|
result = Choice.CANCEL;
|
||||||
|
dispose();
|
||||||
|
}, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
|
||||||
|
|
||||||
|
getRootPane().setDefaultButton(newWindowBtn);
|
||||||
|
|
||||||
|
pack();
|
||||||
|
if (owner != null) {
|
||||||
|
setLocationRelativeTo(owner);
|
||||||
|
} else {
|
||||||
|
setLocationRelativeTo(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Choice getChoice() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JButton getNewWindowButton() {
|
||||||
|
return newWindowBtn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JButton getAbortButton() {
|
||||||
|
return abortBtn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JButton getCancelButton() {
|
||||||
|
return cancelBtn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JLabel getTitleLabel() {
|
||||||
|
return titleLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JLabel getLine1() {
|
||||||
|
return line1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JLabel getLine2() {
|
||||||
|
return line2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Choice showPrompt(Window owner, String currentTarget) {
|
||||||
|
ConnectChoiceDialog dialog = new ConnectChoiceDialog(owner, currentTarget);
|
||||||
|
dialog.setVisible(true);
|
||||||
|
return dialog.getChoice();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import haus.nightmare.lib3270j.ConnectionConfig;
|
|||||||
import haus.nightmare.lib3270j.TerminalModel;
|
import haus.nightmare.lib3270j.TerminalModel;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
import javax.swing.border.TitledBorder;
|
import javax.swing.border.TitledBorder;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -35,6 +36,7 @@ public class ConnectDialog extends JDialog {
|
|||||||
private JComboBox<haus.nightmare.lib3270j.graphics.GraphicsMode> graphicsCombo;
|
private JComboBox<haus.nightmare.lib3270j.graphics.GraphicsMode> graphicsCombo;
|
||||||
private JComboBox<String> codePageCombo;
|
private JComboBox<String> codePageCombo;
|
||||||
private JTextField luField;
|
private JTextField luField;
|
||||||
|
private JComboBox<String> hostTypeCombo;
|
||||||
private JCheckBox tlsCheckBox;
|
private JCheckBox tlsCheckBox;
|
||||||
private JCheckBox verifyCertCheckBox;
|
private JCheckBox verifyCertCheckBox;
|
||||||
private JCheckBox tn3270eCheckBox;
|
private JCheckBox tn3270eCheckBox;
|
||||||
@@ -213,6 +215,25 @@ public class ConnectDialog extends JDialog {
|
|||||||
|
|
||||||
gridy++;
|
gridy++;
|
||||||
|
|
||||||
|
// Host Type (CMS/TSO/CICS)
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = gridy;
|
||||||
|
gbc.weightx = 0;
|
||||||
|
JLabel hostTypeLabel = new JLabel("Host Type:");
|
||||||
|
hostTypeLabel.setFont(labelFont);
|
||||||
|
contentPanel.add(hostTypeLabel, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 1;
|
||||||
|
gbc.weightx = 1.0;
|
||||||
|
String[] hostTypes = {"TSO", "CMS", "CICS"};
|
||||||
|
hostTypeCombo = new JComboBox<>(hostTypes);
|
||||||
|
hostTypeCombo.setFont(ThemeManager.getUiFont());
|
||||||
|
ThemeManager.styleComboBox(hostTypeCombo);
|
||||||
|
setInitialHostType(haus.nightmare.j3270.config.Settings.getDefaultHostType());
|
||||||
|
contentPanel.add(hostTypeCombo, gbc);
|
||||||
|
|
||||||
|
gridy++;
|
||||||
|
|
||||||
// 6. Graphics Mode
|
// 6. Graphics Mode
|
||||||
gbc.gridx = 0;
|
gbc.gridx = 0;
|
||||||
gbc.gridy = gridy;
|
gbc.gridy = gridy;
|
||||||
@@ -226,6 +247,24 @@ public class ConnectDialog extends JDialog {
|
|||||||
graphicsCombo.setSelectedItem(haus.nightmare.j3270.config.Settings.getGraphicsMode());
|
graphicsCombo.setSelectedItem(haus.nightmare.j3270.config.Settings.getGraphicsMode());
|
||||||
graphicsCombo.setFont(ThemeManager.getUiFont());
|
graphicsCombo.setFont(ThemeManager.getUiFont());
|
||||||
ThemeManager.styleComboBox(graphicsCombo);
|
ThemeManager.styleComboBox(graphicsCombo);
|
||||||
|
graphicsCombo.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 (value instanceof haus.nightmare.lib3270j.graphics.GraphicsMode) {
|
||||||
|
setText(((haus.nightmare.lib3270j.graphics.GraphicsMode) value).getDescription());
|
||||||
|
}
|
||||||
|
if (isSelected) {
|
||||||
|
setBackground(ThemeManager.getSelectionBg());
|
||||||
|
setForeground(ThemeManager.getSelectionFg());
|
||||||
|
} else {
|
||||||
|
setBackground(ThemeManager.getBgComponent());
|
||||||
|
setForeground(ThemeManager.getFgMain());
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
});
|
||||||
contentPanel.add(graphicsCombo, gbc);
|
contentPanel.add(graphicsCombo, gbc);
|
||||||
|
|
||||||
gridy++;
|
gridy++;
|
||||||
@@ -546,6 +585,7 @@ public class ConnectDialog extends JDialog {
|
|||||||
} else {
|
} else {
|
||||||
selectedProfileId = null;
|
selectedProfileId = null;
|
||||||
deleteProfileBtn.setEnabled(false);
|
deleteProfileBtn.setEnabled(false);
|
||||||
|
setInitialHostType(haus.nightmare.j3270.config.Settings.getDefaultHostType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -571,6 +611,7 @@ public class ConnectDialog extends JDialog {
|
|||||||
dynamicRowsSpinner.setValue(h.getDynamicRows() > 0 ? h.getDynamicRows() : 62);
|
dynamicRowsSpinner.setValue(h.getDynamicRows() > 0 ? h.getDynamicRows() : 62);
|
||||||
dynamicColsSpinner.setValue(h.getDynamicCols() > 0 ? h.getDynamicCols() : 160);
|
dynamicColsSpinner.setValue(h.getDynamicCols() > 0 ? h.getDynamicCols() : 160);
|
||||||
luField.setText(h.getLuName() != null ? h.getLuName() : "");
|
luField.setText(h.getLuName() != null ? h.getLuName() : "");
|
||||||
|
setInitialHostType(h.getHostType() != null ? h.getHostType() : "TSO");
|
||||||
tlsCheckBox.setSelected(h.isUseTls());
|
tlsCheckBox.setSelected(h.isUseTls());
|
||||||
verifyCertCheckBox.setEnabled(h.isUseTls());
|
verifyCertCheckBox.setEnabled(h.isUseTls());
|
||||||
verifyCertCheckBox.setSelected(h.isTlsVerifyCert());
|
verifyCertCheckBox.setSelected(h.isTlsVerifyCert());
|
||||||
@@ -658,13 +699,14 @@ public class ConnectDialog extends JDialog {
|
|||||||
int dRows = (Integer) dynamicRowsSpinner.getValue();
|
int dRows = (Integer) dynamicRowsSpinner.getValue();
|
||||||
int dCols = (Integer) dynamicColsSpinner.getValue();
|
int dCols = (Integer) dynamicColsSpinner.getValue();
|
||||||
String lu = luField.getText().trim();
|
String lu = luField.getText().trim();
|
||||||
|
String hostType = getSelectedHostType();
|
||||||
boolean tls = tlsCheckBox.isSelected();
|
boolean tls = tlsCheckBox.isSelected();
|
||||||
boolean verify = verifyCertCheckBox.isSelected();
|
boolean verify = verifyCertCheckBox.isSelected();
|
||||||
boolean tn3270e = tn3270eCheckBox.isSelected();
|
boolean tn3270e = tn3270eCheckBox.isSelected();
|
||||||
|
|
||||||
String gfx = "BOTH";
|
String gfx = "BOTH";
|
||||||
if (graphicsCombo.getSelectedItem() != null) {
|
if (graphicsCombo.getSelectedItem() != null) {
|
||||||
gfx = graphicsCombo.getSelectedItem().toString();
|
gfx = ((haus.nightmare.lib3270j.graphics.GraphicsMode) graphicsCombo.getSelectedItem()).name();
|
||||||
}
|
}
|
||||||
|
|
||||||
String cp = "037";
|
String cp = "037";
|
||||||
@@ -686,7 +728,7 @@ public class ConnectDialog extends JDialog {
|
|||||||
String pPass = new String(proxyPassField.getPassword());
|
String pPass = new String(proxyPassField.getPassword());
|
||||||
|
|
||||||
return new SavedHost(id, profileName, h, port, mNum, dRows, dCols, lu, false,
|
return new SavedHost(id, profileName, h, port, mNum, dRows, dCols, lu, false,
|
||||||
"TSO", tls, verify, tn3270e, gfx, cp, ptStr, pHost, pPort, pUser, pPass);
|
hostType, tls, verify, tn3270e, gfx, cp, ptStr, pHost, pPort, pUser, pPass);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================= Connect Action =================
|
// ================= Connect Action =================
|
||||||
@@ -731,7 +773,9 @@ public class ConnectDialog extends JDialog {
|
|||||||
result.setLuName(lu);
|
result.setLuName(lu);
|
||||||
}
|
}
|
||||||
if (graphicsCombo.getSelectedItem() != null) {
|
if (graphicsCombo.getSelectedItem() != null) {
|
||||||
result.setGraphicsMode((haus.nightmare.lib3270j.graphics.GraphicsMode) graphicsCombo.getSelectedItem());
|
haus.nightmare.lib3270j.graphics.GraphicsMode gm = (haus.nightmare.lib3270j.graphics.GraphicsMode) graphicsCombo.getSelectedItem();
|
||||||
|
result.setGraphicsMode(gm);
|
||||||
|
haus.nightmare.j3270.config.Settings.setGraphicsMode(gm);
|
||||||
}
|
}
|
||||||
|
|
||||||
Object cpSelection = codePageCombo.getSelectedItem();
|
Object cpSelection = codePageCombo.getSelectedItem();
|
||||||
@@ -776,6 +820,18 @@ public class ConnectDialog extends JDialog {
|
|||||||
result.setProxy(pType, pHost, pPort, pUser.isEmpty() ? null : pUser, pPass.isEmpty() ? null : pPass);
|
result.setProxy(pType, pHost, pPort, pUser.isEmpty() ? null : pUser, pPass.isEmpty() ? null : pPass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result.setHostType(getSelectedHostType());
|
||||||
|
if (selectedProfileId != null) {
|
||||||
|
result.setSavedConnection(true);
|
||||||
|
result.setSavedProfileId(selectedProfileId);
|
||||||
|
SavedHost existing = HostStorage.findById(selectedProfileId);
|
||||||
|
String pName = (existing != null && existing.getName() != null && !existing.getName().trim().isEmpty())
|
||||||
|
? existing.getName() : host;
|
||||||
|
result.setSavedProfileName(pName);
|
||||||
|
SavedHost updated = buildCurrentSavedHost(pName);
|
||||||
|
HostStorage.saveHost(updated);
|
||||||
|
}
|
||||||
|
|
||||||
confirmed = true;
|
confirmed = true;
|
||||||
dispose();
|
dispose();
|
||||||
}
|
}
|
||||||
@@ -810,6 +866,12 @@ public class ConnectDialog extends JDialog {
|
|||||||
tn3270eCheckBox.setSelected(tn3270e);
|
tn3270eCheckBox.setSelected(tn3270e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setInitialGraphicsMode(haus.nightmare.lib3270j.graphics.GraphicsMode mode) {
|
||||||
|
if (mode != null && selectedProfileId == null && graphicsCombo != null) {
|
||||||
|
graphicsCombo.setSelectedItem(mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setInitialCodePage(String cp) {
|
public void setInitialCodePage(String cp) {
|
||||||
if (cp != null && codePageCombo != null) {
|
if (cp != null && codePageCombo != null) {
|
||||||
for (int i = 0; i < codePageCombo.getItemCount(); i++) {
|
for (int i = 0; i < codePageCombo.getItemCount(); i++) {
|
||||||
@@ -867,4 +929,44 @@ public class ConnectDialog extends JDialog {
|
|||||||
public String getProxyPassword() {
|
public String getProxyPassword() {
|
||||||
return new String(proxyPassField.getPassword());
|
return new String(proxyPassField.getPassword());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setInitialHostType(String hostType) {
|
||||||
|
if (hostType != null && hostTypeCombo != null) {
|
||||||
|
for (int i = 0; i < hostTypeCombo.getItemCount(); i++) {
|
||||||
|
if (hostTypeCombo.getItemAt(i).equalsIgnoreCase(hostType.trim())) {
|
||||||
|
hostTypeCombo.setSelectedIndex(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInitialHostType(haus.nightmare.lib3270j.ft.FTConfig.HostType hostType) {
|
||||||
|
if (hostType != null) {
|
||||||
|
setInitialHostType(hostType.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSelectedHostType() {
|
||||||
|
if (hostTypeCombo != null && hostTypeCombo.getSelectedItem() != null) {
|
||||||
|
return hostTypeCombo.getSelectedItem().toString().trim().toUpperCase();
|
||||||
|
}
|
||||||
|
return "TSO";
|
||||||
|
}
|
||||||
|
|
||||||
|
public haus.nightmare.lib3270j.ft.FTConfig.HostType getSelectedFtHostType() {
|
||||||
|
try {
|
||||||
|
return haus.nightmare.lib3270j.ft.FTConfig.HostType.valueOf(getSelectedHostType());
|
||||||
|
} catch (Exception e) {
|
||||||
|
return haus.nightmare.lib3270j.ft.FTConfig.HostType.TSO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public JComboBox<String> getHostTypeCombo() {
|
||||||
|
return hostTypeCombo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSelectedProfileId() {
|
||||||
|
return selectedProfileId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,15 +3,19 @@ package haus.nightmare.j3270.ui;
|
|||||||
import haus.nightmare.lib3270j.Telnet3270Client;
|
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||||
import haus.nightmare.lib3270j.ft.FTConfig;
|
import haus.nightmare.lib3270j.ft.FTConfig;
|
||||||
import haus.nightmare.lib3270j.ft.dir.CMSDirectoryEntry;
|
import haus.nightmare.lib3270j.ft.dir.CMSDirectoryEntry;
|
||||||
|
import haus.nightmare.lib3270j.ft.dir.CMSDirectoryParser;
|
||||||
import haus.nightmare.lib3270j.ft.dir.HostDirectoryEntry;
|
import haus.nightmare.lib3270j.ft.dir.HostDirectoryEntry;
|
||||||
import haus.nightmare.lib3270j.ft.dir.TSODirectoryEntry;
|
import haus.nightmare.lib3270j.ft.dir.TSODirectoryEntry;
|
||||||
|
import haus.nightmare.lib3270j.ft.dir.TSODirectoryParser;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.border.EmptyBorder;
|
import javax.swing.border.EmptyBorder;
|
||||||
import javax.swing.border.LineBorder;
|
|
||||||
import javax.swing.table.DefaultTableModel;
|
import javax.swing.table.DefaultTableModel;
|
||||||
import javax.swing.table.JTableHeader;
|
import javax.swing.table.JTableHeader;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
import java.awt.datatransfer.Clipboard;
|
||||||
|
import java.awt.datatransfer.DataFlavor;
|
||||||
|
import java.awt.datatransfer.Transferable;
|
||||||
import java.awt.event.MouseAdapter;
|
import java.awt.event.MouseAdapter;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -19,6 +23,7 @@ import java.util.List;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Host Directory and Dataset Catalog browser dialog for TSO and VM/CMS environments.
|
* Host Directory and Dataset Catalog browser dialog for TSO and VM/CMS environments.
|
||||||
|
* Aligned with IBM Host On-Demand presentation space queries and clipboard parsing.
|
||||||
*/
|
*/
|
||||||
public class HostDirectoryDialog extends JDialog {
|
public class HostDirectoryDialog extends JDialog {
|
||||||
|
|
||||||
@@ -27,13 +32,16 @@ public class HostDirectoryDialog extends JDialog {
|
|||||||
|
|
||||||
private JComboBox<FTConfig.HostType> hostTypeCombo;
|
private JComboBox<FTConfig.HostType> hostTypeCombo;
|
||||||
private JTextField queryField;
|
private JTextField queryField;
|
||||||
|
private JButton queryHostBtn;
|
||||||
private JTable table;
|
private JTable table;
|
||||||
private DefaultTableModel tableModel;
|
private DefaultTableModel tableModel;
|
||||||
private JLabel statusLabel;
|
private JLabel statusLabel;
|
||||||
|
|
||||||
private String selectedHostFile = null;
|
private String selectedHostFile = null;
|
||||||
|
private HostDirectoryEntry selectedEntry = null;
|
||||||
private boolean confirmed = false;
|
private boolean confirmed = false;
|
||||||
|
|
||||||
|
private final List<HostDirectoryEntry> allEntries = new ArrayList<>();
|
||||||
private final List<HostDirectoryEntry> currentEntries = new ArrayList<>();
|
private final List<HostDirectoryEntry> currentEntries = new ArrayList<>();
|
||||||
|
|
||||||
public HostDirectoryDialog(Dialog parent, Telnet3270Client client, FTConfig.HostType hostType, String initialQuery) {
|
public HostDirectoryDialog(Dialog parent, Telnet3270Client client, FTConfig.HostType hostType, String initialQuery) {
|
||||||
@@ -43,7 +51,7 @@ public class HostDirectoryDialog extends JDialog {
|
|||||||
AppIcon.applyTo(this);
|
AppIcon.applyTo(this);
|
||||||
|
|
||||||
buildUI(initialQuery);
|
buildUI(initialQuery);
|
||||||
setSize(700, 420);
|
setSize(840, 480);
|
||||||
setLocationRelativeTo(parent);
|
setLocationRelativeTo(parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,33 +77,68 @@ public class HostDirectoryDialog extends JDialog {
|
|||||||
if (initialHostType == FTConfig.HostType.CMS) hostTypeCombo.setSelectedItem(FTConfig.HostType.CMS);
|
if (initialHostType == FTConfig.HostType.CMS) hostTypeCombo.setSelectedItem(FTConfig.HostType.CMS);
|
||||||
else hostTypeCombo.setSelectedItem(FTConfig.HostType.TSO);
|
else hostTypeCombo.setSelectedItem(FTConfig.HostType.TSO);
|
||||||
ThemeManager.styleComboBox(hostTypeCombo);
|
ThemeManager.styleComboBox(hostTypeCombo);
|
||||||
|
hostTypeCombo.addActionListener(e -> {
|
||||||
|
setupTableColumns();
|
||||||
|
FTConfig.HostType ht = (FTConfig.HostType) hostTypeCombo.getSelectedItem();
|
||||||
|
if (ht == FTConfig.HostType.CMS) {
|
||||||
|
String cur = queryField.getText().trim();
|
||||||
|
if (cur.isEmpty() || cur.equals("* * *") || cur.equals("*")) {
|
||||||
|
queryField.setText("* * A");
|
||||||
|
}
|
||||||
|
} else if (ht == FTConfig.HostType.TSO) {
|
||||||
|
String cur = queryField.getText().trim();
|
||||||
|
if (cur.equals("* * A") || cur.equals("* * *")) {
|
||||||
|
queryField.setText("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (client != null && client.getConnectionState().isFullSession()) {
|
||||||
|
queryHost();
|
||||||
|
} else {
|
||||||
|
parseCurrentScreen();
|
||||||
|
}
|
||||||
|
});
|
||||||
topPanel.add(hostTypeCombo, gbc);
|
topPanel.add(hostTypeCombo, gbc);
|
||||||
|
|
||||||
gbc.gridx = 2;
|
gbc.gridx = 2;
|
||||||
JLabel queryLabel = new JLabel("Query Pattern / Text:");
|
JLabel queryLabel = new JLabel("Filter / Pattern:");
|
||||||
queryLabel.setFont(font);
|
queryLabel.setFont(font);
|
||||||
topPanel.add(queryLabel, gbc);
|
topPanel.add(queryLabel, gbc);
|
||||||
|
|
||||||
gbc.gridx = 3;
|
gbc.gridx = 3;
|
||||||
gbc.weightx = 1.0;
|
gbc.weightx = 1.0;
|
||||||
queryField = new JTextField(initialQuery != null ? initialQuery : "", 16);
|
String defaultQuery = resolveDefaultQuery(initialHostType, initialQuery);
|
||||||
|
queryField = new JTextField(defaultQuery, 16);
|
||||||
queryField.setFont(ThemeManager.getMonospacedUiFont());
|
queryField.setFont(ThemeManager.getMonospacedUiFont());
|
||||||
ThemeManager.styleTextField(queryField);
|
queryField.addActionListener(e -> onFilterOrParse());
|
||||||
topPanel.add(queryField, gbc);
|
topPanel.add(queryField, gbc);
|
||||||
|
|
||||||
gbc.gridx = 4;
|
gbc.gridx = 4;
|
||||||
gbc.weightx = 0;
|
gbc.weightx = 0;
|
||||||
JButton parseBtn = new JButton("Query / Parse");
|
JButton filterBtn = new JButton("Filter / Parse");
|
||||||
ThemeManager.styleButton(parseBtn, ThemeManager.ButtonVariant.DEFAULT);
|
ThemeManager.styleButton(filterBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
parseBtn.setFont(font);
|
filterBtn.setFont(font);
|
||||||
parseBtn.addActionListener(e -> runQuery());
|
filterBtn.addActionListener(e -> onFilterOrParse());
|
||||||
topPanel.add(parseBtn, gbc);
|
topPanel.add(filterBtn, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 5;
|
||||||
|
queryHostBtn = new JButton("Query Host");
|
||||||
|
ThemeManager.styleButton(queryHostBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
queryHostBtn.setFont(font);
|
||||||
|
queryHostBtn.setToolTipText("Send LISTCAT or LISTFILE query to active host session");
|
||||||
|
queryHostBtn.addActionListener(e -> queryHost());
|
||||||
|
topPanel.add(queryHostBtn, gbc);
|
||||||
|
|
||||||
mainPanel.add(topPanel, BorderLayout.NORTH);
|
mainPanel.add(topPanel, BorderLayout.NORTH);
|
||||||
|
|
||||||
// Table
|
// Table
|
||||||
tableModel = new DefaultTableModel();
|
tableModel = new DefaultTableModel() {
|
||||||
|
@Override
|
||||||
|
public boolean isCellEditable(int row, int column) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
table = new JTable(tableModel);
|
table = new JTable(tableModel);
|
||||||
|
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
|
||||||
ThemeManager.styleTable(table);
|
ThemeManager.styleTable(table);
|
||||||
table.setFont(ThemeManager.getMonospacedUiFont());
|
table.setFont(ThemeManager.getMonospacedUiFont());
|
||||||
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||||
@@ -114,26 +157,42 @@ public class HostDirectoryDialog extends JDialog {
|
|||||||
});
|
});
|
||||||
|
|
||||||
JScrollPane scrollPane = new JScrollPane(table);
|
JScrollPane scrollPane = new JScrollPane(table);
|
||||||
|
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
||||||
|
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
|
||||||
ThemeManager.styleScrollPane(scrollPane);
|
ThemeManager.styleScrollPane(scrollPane);
|
||||||
mainPanel.add(scrollPane, BorderLayout.CENTER);
|
mainPanel.add(scrollPane, BorderLayout.CENTER);
|
||||||
|
|
||||||
// Bottom
|
// Bottom
|
||||||
JPanel bottomPanel = new JPanel(new BorderLayout(5, 5));
|
JPanel bottomPanel = new JPanel(new BorderLayout(5, 6));
|
||||||
bottomPanel.setOpaque(false);
|
bottomPanel.setOpaque(false);
|
||||||
|
bottomPanel.setBorder(BorderFactory.createEmptyBorder(6, 4, 4, 4));
|
||||||
|
|
||||||
statusLabel = new JLabel("Enter a dataset pattern or parse active screen.");
|
statusLabel = new JLabel("Enter a dataset pattern or parse active screen.");
|
||||||
statusLabel.setForeground(ThemeManager.getFgMuted());
|
statusLabel.setForeground(ThemeManager.getFgMuted());
|
||||||
statusLabel.setFont(font);
|
statusLabel.setFont(font);
|
||||||
bottomPanel.add(statusLabel, BorderLayout.WEST);
|
bottomPanel.add(statusLabel, BorderLayout.NORTH);
|
||||||
|
|
||||||
JPanel btnPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 6, 0));
|
JPanel btnPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 6, 0));
|
||||||
btnPanel.setOpaque(false);
|
btnPanel.setOpaque(false);
|
||||||
|
|
||||||
JButton pasteScreenBtn = new JButton("Parse Current Screen");
|
JButton inspectBtn = new JButton("Inspect (LISTDS)");
|
||||||
|
ThemeManager.styleButton(inspectBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
inspectBtn.setFont(font);
|
||||||
|
inspectBtn.setToolTipText("Retrieve VTOC attributes (RECFM, LRECL, BLKSIZE) for the selected dataset via TSO LISTDS");
|
||||||
|
inspectBtn.addActionListener(e -> inspectSelectedDataset());
|
||||||
|
|
||||||
|
JButton pasteScreenBtn = new JButton("Parse Screen");
|
||||||
ThemeManager.styleButton(pasteScreenBtn, ThemeManager.ButtonVariant.DEFAULT);
|
ThemeManager.styleButton(pasteScreenBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
pasteScreenBtn.setFont(font);
|
pasteScreenBtn.setFont(font);
|
||||||
|
pasteScreenBtn.setToolTipText("Parse items currently visible on the 3270 presentation space");
|
||||||
pasteScreenBtn.addActionListener(e -> parseCurrentScreen());
|
pasteScreenBtn.addActionListener(e -> parseCurrentScreen());
|
||||||
|
|
||||||
|
JButton pasteClipBtn = new JButton("Paste Clipboard");
|
||||||
|
ThemeManager.styleButton(pasteClipBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
pasteClipBtn.setFont(font);
|
||||||
|
pasteClipBtn.setToolTipText("Parse host filenames copied to the system clipboard (HoD compatible)");
|
||||||
|
pasteClipBtn.addActionListener(e -> parseClipboard());
|
||||||
|
|
||||||
JButton selectBtn = new JButton("Select Dataset");
|
JButton selectBtn = new JButton("Select Dataset");
|
||||||
ThemeManager.styleButton(selectBtn, ThemeManager.ButtonVariant.PRIMARY);
|
ThemeManager.styleButton(selectBtn, ThemeManager.ButtonVariant.PRIMARY);
|
||||||
selectBtn.setFont(font);
|
selectBtn.setFont(font);
|
||||||
@@ -147,23 +206,28 @@ public class HostDirectoryDialog extends JDialog {
|
|||||||
dispose();
|
dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
btnPanel.add(inspectBtn);
|
||||||
btnPanel.add(pasteScreenBtn);
|
btnPanel.add(pasteScreenBtn);
|
||||||
|
btnPanel.add(pasteClipBtn);
|
||||||
btnPanel.add(Box.createHorizontalStrut(10));
|
btnPanel.add(Box.createHorizontalStrut(10));
|
||||||
btnPanel.add(selectBtn);
|
btnPanel.add(selectBtn);
|
||||||
btnPanel.add(cancelBtn);
|
btnPanel.add(cancelBtn);
|
||||||
|
|
||||||
bottomPanel.add(btnPanel, BorderLayout.EAST);
|
bottomPanel.add(btnPanel, BorderLayout.SOUTH);
|
||||||
mainPanel.add(bottomPanel, BorderLayout.SOUTH);
|
mainPanel.add(bottomPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
setContentPane(mainPanel);
|
setContentPane(mainPanel);
|
||||||
ThemeManager.applyThemeToWindow(this);
|
ThemeManager.applyThemeToWindow(this);
|
||||||
|
|
||||||
// Initial setup
|
// Initial setup: columns and screen parse or host query
|
||||||
setupTableColumns();
|
setupTableColumns();
|
||||||
if (initialQuery != null && !initialQuery.isEmpty()) {
|
if (client != null && client.getConnectionState().isFullSession()) {
|
||||||
runQuery();
|
queryHost();
|
||||||
} else {
|
} else {
|
||||||
parseCurrentScreen();
|
parseCurrentScreen();
|
||||||
|
if (!queryField.getText().trim().isEmpty()) {
|
||||||
|
applyFilter();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,76 +238,665 @@ public class HostDirectoryDialog extends JDialog {
|
|||||||
tableModel.setColumnIdentifiers(new Object[]{
|
tableModel.setColumnIdentifiers(new Object[]{
|
||||||
"Filename", "Filetype", "Filemode", "Format", "Lrecl", "Records", "Date / Time"
|
"Filename", "Filetype", "Filemode", "Format", "Lrecl", "Records", "Date / Time"
|
||||||
});
|
});
|
||||||
|
int[] widths = {120, 100, 80, 70, 70, 90, 170};
|
||||||
|
for (int i = 0; i < widths.length && i < table.getColumnCount(); i++) {
|
||||||
|
table.getColumnModel().getColumn(i).setPreferredWidth(widths[i]);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
tableModel.setColumnIdentifiers(new Object[]{
|
tableModel.setColumnIdentifiers(new Object[]{
|
||||||
"Dataset Name", "Volume", "Recfm", "Lrecl", "Blksize", "Tracks", "Extents"
|
"Dataset Name", "Volume"
|
||||||
});
|
});
|
||||||
|
int[] widths = {620, 160};
|
||||||
|
for (int i = 0; i < widths.length && i < table.getColumnCount(); i++) {
|
||||||
|
table.getColumnModel().getColumn(i).setPreferredWidth(widths[i]);
|
||||||
|
if (i == 0) {
|
||||||
|
table.getColumnModel().getColumn(0).setMinWidth(250);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void runQuery() {
|
private void onFilterOrParse() {
|
||||||
setupTableColumns();
|
|
||||||
currentEntries.clear();
|
|
||||||
String query = queryField.getText().trim();
|
String query = queryField.getText().trim();
|
||||||
|
// If text contains multiple lines, treat it as pasted raw text to parse
|
||||||
|
if (query.contains("\n") || query.contains("\r") || query.toUpperCase().contains("NONVSAM")
|
||||||
|
|| query.toUpperCase().contains("IN-CAT") || query.toUpperCase().contains("FILENAME")) {
|
||||||
|
parseText(query, "query text");
|
||||||
|
} else if (allEntries.isEmpty() && client != null && client.getConnectionState().isFullSession()) {
|
||||||
|
queryHost();
|
||||||
|
} else {
|
||||||
|
applyFilter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void queryHost() {
|
||||||
|
if (client == null || !client.getConnectionState().isFullSession()) {
|
||||||
|
statusLabel.setText("No active session to query.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String pattern = queryField.getText().trim();
|
||||||
FTConfig.HostType hostType = (FTConfig.HostType) hostTypeCombo.getSelectedItem();
|
FTConfig.HostType hostType = (FTConfig.HostType) hostTypeCombo.getSelectedItem();
|
||||||
|
statusLabel.setText("Querying host (" + hostType + ")...");
|
||||||
|
if (queryHostBtn != null) queryHostBtn.setEnabled(false);
|
||||||
|
|
||||||
|
SwingWorker<String, Void> worker = new SwingWorker<>() {
|
||||||
|
@Override
|
||||||
|
protected String doInBackground() throws Exception {
|
||||||
|
if (client == null || client.getPS() == null) return "";
|
||||||
|
|
||||||
|
StringBuilder allPages = new StringBuilder();
|
||||||
|
int maxPages = 500;
|
||||||
|
|
||||||
if (client != null && client.getXfer() != null) {
|
|
||||||
if (hostType == FTConfig.HostType.CMS) {
|
if (hostType == FTConfig.HostType.CMS) {
|
||||||
List<CMSDirectoryEntry> cmsEntries = client.getXfer().getCmsDirectory(query);
|
// CMS Query:
|
||||||
currentEntries.addAll(cmsEntries);
|
// 1. Clear presentation space first to ensure clean state
|
||||||
for (CMSDirectoryEntry entry : cmsEntries) {
|
client.getPS().SendKeys("[clear]");
|
||||||
tableModel.addRow(new Object[]{
|
waitForKeyboardUnlock(client, 3000);
|
||||||
entry.getFilename(), entry.getFiletype(), entry.getFilemode(),
|
|
||||||
entry.getRecfm(), entry.getLrecl(), entry.getNumRecords(), entry.getDate() + " " + entry.getTime()
|
// 2. Format CMS LISTFILE query command (default to minidisk A unless specified)
|
||||||
});
|
String cmsFilter = formatCMSFilter(pattern);
|
||||||
|
client.getPS().SendKeys("LISTFILE " + cmsFilter + " ( nohead alloc date[enter]");
|
||||||
|
Thread.sleep(100);
|
||||||
|
|
||||||
|
// 3. Multi-page collection loop:
|
||||||
|
// CMS pagination displays MORE... or HOLDING when a screen is full.
|
||||||
|
// When advanced with [clear], CMS flashes RUNNING while generating the next screen.
|
||||||
|
// We poll up to 10 seconds per page for MORE... / HOLDING or Ready;
|
||||||
|
for (int page = 0; page < maxPages; page++) {
|
||||||
|
long startWait = System.currentTimeMillis();
|
||||||
|
long timeoutMs = 10000; // 10 seconds per page
|
||||||
|
String screen = "";
|
||||||
|
boolean isMore = false;
|
||||||
|
|
||||||
|
while (System.currentTimeMillis() - startWait < timeoutMs) {
|
||||||
|
waitForKeyboardUnlock(client, 500);
|
||||||
|
int rows = client.getPS().getRows();
|
||||||
|
int cols = client.getPS().getCols();
|
||||||
|
screen = client.getPS().copyString(0, 0, rows - 1, cols - 1);
|
||||||
|
String upper = screen.toUpperCase();
|
||||||
|
|
||||||
|
if (upper.contains("MORE...") || upper.contains("HOLDING")) {
|
||||||
|
isMore = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (upper.contains("READY;") || upper.contains("READY(")) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Still RUNNING or transitioning, wait and re-check
|
||||||
|
Thread.sleep(150);
|
||||||
|
}
|
||||||
|
|
||||||
|
allPages.append(screen).append("\n");
|
||||||
|
|
||||||
|
if (isMore) {
|
||||||
|
// In CMS, sending [clear] advances to the next page of output
|
||||||
|
client.getPS().SendKeys("[clear]");
|
||||||
|
Thread.sleep(100);
|
||||||
|
waitForKeyboardUnlock(client, 3000);
|
||||||
|
} else {
|
||||||
|
// Ready; encountered or timed out -> listing complete
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Automatically clear the listing from the terminal presentation space
|
||||||
|
// so the terminal screen is left clean for the user while keeping the dialog populated
|
||||||
|
if (client.getPS() != null) {
|
||||||
|
client.getPS().SendKeys("[clear]");
|
||||||
|
waitForKeyboardUnlock(client, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// TSO Query:
|
||||||
|
// Check if current screen is in ISPF (formatted screen or containing ===> or ISPF)
|
||||||
|
int rows = client.getPS().getRows();
|
||||||
|
int cols = client.getPS().getCols();
|
||||||
|
String initialScreen = client.getPS().copyString(0, 0, rows - 1, cols - 1);
|
||||||
|
boolean isFormatted = client.getScreenBuffer() != null && client.getScreenBuffer().isFormatted();
|
||||||
|
boolean hasCmdArrow = initialScreen.contains("===>");
|
||||||
|
boolean isIspf = isFormatted || hasCmdArrow || initialScreen.contains("ISPF");
|
||||||
|
|
||||||
|
String tsoCmd = "TSO LISTCAT ALL";
|
||||||
|
if (!pattern.isEmpty()) {
|
||||||
|
String dsnPattern = pattern.trim();
|
||||||
|
if (dsnPattern.startsWith("'") && dsnPattern.endsWith("'") && dsnPattern.length() > 2) {
|
||||||
|
dsnPattern = dsnPattern.substring(1, dsnPattern.length() - 1);
|
||||||
|
}
|
||||||
|
int paren = dsnPattern.indexOf('(');
|
||||||
|
if (paren > 0) {
|
||||||
|
dsnPattern = dsnPattern.substring(0, paren);
|
||||||
|
}
|
||||||
|
if (dsnPattern.equals("*") || dsnPattern.equals("* * *") || dsnPattern.equals("*.*")) {
|
||||||
|
tsoCmd = "TSO LISTCAT ALL";
|
||||||
|
} else {
|
||||||
|
String level = dsnPattern;
|
||||||
|
if (level.endsWith(".*")) {
|
||||||
|
level = level.substring(0, level.length() - 2);
|
||||||
|
} else if (level.endsWith("*")) {
|
||||||
|
level = level.substring(0, level.length() - 1);
|
||||||
|
}
|
||||||
|
if (level.isEmpty()) {
|
||||||
|
tsoCmd = "TSO LISTCAT ALL";
|
||||||
|
} else {
|
||||||
|
tsoCmd = "TSO LISTCAT LEVEL(" + level + ") ALL";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isIspf) {
|
||||||
|
// In ISPF, do NOT send [clear] because [clear] destroys the ISPF panel.
|
||||||
|
// Locate the command/option line (indicated by ===>) or home to first input field.
|
||||||
|
int arrowPos = initialScreen.indexOf("===>");
|
||||||
|
if (arrowPos >= 0 && client.getScreenBuffer() != null) {
|
||||||
|
int inputAddr = client.getScreenBuffer().findNextUnprotected(arrowPos);
|
||||||
|
if (inputAddr > 0) {
|
||||||
|
client.getScreenBuffer().setCursorAddress(inputAddr);
|
||||||
|
client.getScreenBuffer().updateDisplaySnapshot();
|
||||||
|
} else {
|
||||||
|
client.getPS().SendKeys("[home]");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
List<TSODirectoryEntry> tsoEntries = client.getXfer().getTsoDirectory(query);
|
client.getPS().SendKeys("[home]");
|
||||||
currentEntries.addAll(tsoEntries);
|
}
|
||||||
for (TSODirectoryEntry entry : tsoEntries) {
|
client.getPS().SendKeys("[erase_eof]");
|
||||||
tableModel.addRow(new Object[]{
|
client.getPS().SendKeys(tsoCmd + "[enter]");
|
||||||
entry.getDatasetName(), entry.getVolume(), entry.getRecfm(),
|
} else {
|
||||||
entry.getLrecl(), entry.getBlksize(), entry.getTracksAllocated(), entry.getExtents()
|
// In native TSO (ISPF closed, line mode READY prompt)
|
||||||
});
|
client.getPS().SendKeys("[clear]");
|
||||||
|
waitForKeyboardUnlock(client, 3000);
|
||||||
|
client.getPS().SendKeys(tsoCmd + "[enter]");
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.sleep(100);
|
||||||
|
|
||||||
|
// Multi-page collection loop for TSO:
|
||||||
|
// 1. Wait for output to start appearing after sending command
|
||||||
|
long startCmd = System.currentTimeMillis();
|
||||||
|
while (System.currentTimeMillis() - startCmd < 4000) {
|
||||||
|
waitForKeyboardUnlock(client, 200);
|
||||||
|
int r = client.getPS().getRows();
|
||||||
|
int c = client.getPS().getCols();
|
||||||
|
String s = client.getPS().copyString(0, 0, r - 1, c - 1);
|
||||||
|
String u = s.toUpperCase();
|
||||||
|
if (u.contains("NONVSAM") || u.contains("CLUSTER") || u.contains("GDG")
|
||||||
|
|| u.contains("ALIAS") || u.contains("IN-CAT") || u.contains("IDCAMS")
|
||||||
|
|| u.contains("***") || u.contains("READY") || u.contains("NOT FOUND")
|
||||||
|
|| u.contains("NO ENTRY") || s.contains(" --- ")
|
||||||
|
|| u.contains("--RECFM-") || u.contains("--VOLUMES--")) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Thread.sleep(80);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Multi-page collection
|
||||||
|
String prevScreen = "";
|
||||||
|
for (int page = 0; page < maxPages; page++) {
|
||||||
|
waitForKeyboardUnlock(client, 300);
|
||||||
|
int r = client.getPS().getRows();
|
||||||
|
int c = client.getPS().getCols();
|
||||||
|
String screen = client.getPS().copyString(0, 0, r - 1, c - 1);
|
||||||
|
String upper = screen.toUpperCase();
|
||||||
|
|
||||||
|
if (screen.equals(prevScreen) || screen.trim().isEmpty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
prevScreen = screen;
|
||||||
|
allPages.append(screen).append("\n");
|
||||||
|
|
||||||
|
if (upper.contains("***")) {
|
||||||
|
client.getPS().SendKeys("[enter]");
|
||||||
|
Thread.sleep(100);
|
||||||
|
waitForKeyboardUnlock(client, 2000);
|
||||||
|
} else if (upper.contains("NONVSAM") || upper.contains("CLUSTER") || upper.contains("GDG")
|
||||||
|
|| upper.contains("ALIAS") || upper.contains("IDCAMS") || screen.contains(" --- ")
|
||||||
|
|| upper.contains("--RECFM-") || upper.contains("--VOLUMES--")) {
|
||||||
|
// If screen shows dataset data without ***, send [enter] once to see if *** or more output is triggered
|
||||||
|
client.getPS().SendKeys("[enter]");
|
||||||
|
Thread.sleep(100);
|
||||||
|
waitForKeyboardUnlock(client, 1000);
|
||||||
|
|
||||||
|
int r2 = client.getPS().getRows();
|
||||||
|
int c2 = client.getPS().getCols();
|
||||||
|
String s2 = client.getPS().copyString(0, 0, r2 - 1, c2 - 1);
|
||||||
|
if (s2.toUpperCase().contains("***")) {
|
||||||
|
prevScreen = s2;
|
||||||
|
allPages.append(s2).append("\n");
|
||||||
|
client.getPS().SendKeys("[enter]");
|
||||||
|
Thread.sleep(100);
|
||||||
|
waitForKeyboardUnlock(client, 2000);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
if (!s2.equals(screen) && !s2.trim().isEmpty()) {
|
||||||
|
allPages.append(s2).append("\n");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Terminal clean-up: clear the list from the terminal presentation space
|
||||||
|
if (client.getPS() != null) {
|
||||||
|
if (isIspf) {
|
||||||
|
client.getPS().SendKeys("[enter]");
|
||||||
|
Thread.sleep(150);
|
||||||
|
waitForKeyboardUnlock(client, 1500);
|
||||||
|
|
||||||
|
int r = client.getPS().getRows();
|
||||||
|
int c = client.getPS().getCols();
|
||||||
|
String currentScr = client.getPS().copyString(0, 0, r - 1, c - 1);
|
||||||
|
if (currentScr.toUpperCase().contains("NONVSAM") || currentScr.toUpperCase().contains("IDCAMS")
|
||||||
|
|| currentScr.contains(" --- ") || currentScr.toUpperCase().contains("--RECFM-")
|
||||||
|
|| (client.getScreenBuffer() != null && !client.getScreenBuffer().isFormatted())) {
|
||||||
|
client.getPS().SendKeys("[clear]");
|
||||||
|
waitForKeyboardUnlock(client, 2000);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
client.getPS().SendKeys("[clear]");
|
||||||
|
waitForKeyboardUnlock(client, 2000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
statusLabel.setText(currentEntries.size() + " entries found.");
|
return allPages.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done() {
|
||||||
|
if (queryHostBtn != null) queryHostBtn.setEnabled(true);
|
||||||
|
try {
|
||||||
|
String screenText = get();
|
||||||
|
parseText(screenText, "host query");
|
||||||
|
if (!pattern.isEmpty() && !allEntries.isEmpty()) {
|
||||||
|
applyFilter();
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
statusLabel.setText("Host query failed: " + ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
worker.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void inspectSelectedDataset() {
|
||||||
|
int row = table.getSelectedRow();
|
||||||
|
if (row < 0 || row >= currentEntries.size()) {
|
||||||
|
JOptionPane.showMessageDialog(this, "Please select an entry from the table to inspect.",
|
||||||
|
"No Selection", JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
HostDirectoryEntry entry = currentEntries.get(row);
|
||||||
|
if (!(entry instanceof TSODirectoryEntry)) {
|
||||||
|
JOptionPane.showMessageDialog(this, "Inspection is only supported for TSO datasets.",
|
||||||
|
"Not Supported", JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client == null || !client.getConnectionState().isFullSession()) {
|
||||||
|
JOptionPane.showMessageDialog(this, "No active full 3270 session to query host.",
|
||||||
|
"Not Connected", JOptionPane.WARNING_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TSODirectoryEntry tso = (TSODirectoryEntry) entry;
|
||||||
|
String dsn = tso.getDatasetName();
|
||||||
|
if (dsn == null || dsn.trim().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
statusLabel.setText("Inspecting " + dsn + " via TSO LISTDS...");
|
||||||
|
|
||||||
|
SwingWorker<String, Void> worker = new SwingWorker<String, Void>() {
|
||||||
|
@Override
|
||||||
|
protected String doInBackground() throws Exception {
|
||||||
|
StringBuilder output = new StringBuilder();
|
||||||
|
int rows = client.getPS().getRows();
|
||||||
|
int cols = client.getPS().getCols();
|
||||||
|
String initialScreen = client.getPS().copyString(0, 0, rows - 1, cols - 1);
|
||||||
|
boolean isFormatted = client.getScreenBuffer() != null && client.getScreenBuffer().isFormatted();
|
||||||
|
boolean hasCmdArrow = initialScreen.contains("===>");
|
||||||
|
boolean isIspf = isFormatted || hasCmdArrow || initialScreen.contains("ISPF");
|
||||||
|
|
||||||
|
String tsoCmd = "TSO LISTDS '" + dsn + "' STATUS";
|
||||||
|
|
||||||
|
if (isIspf) {
|
||||||
|
int arrowPos = initialScreen.indexOf("===>");
|
||||||
|
if (arrowPos >= 0 && client.getScreenBuffer() != null) {
|
||||||
|
int inputAddr = client.getScreenBuffer().findNextUnprotected(arrowPos);
|
||||||
|
if (inputAddr > 0) {
|
||||||
|
client.getScreenBuffer().setCursorAddress(inputAddr);
|
||||||
|
client.getScreenBuffer().updateDisplaySnapshot();
|
||||||
|
} else {
|
||||||
|
client.getPS().SendKeys("[home]");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
client.getPS().SendKeys("[home]");
|
||||||
|
}
|
||||||
|
client.getPS().SendKeys("[erase_eof]");
|
||||||
|
client.getPS().SendKeys(tsoCmd + "[enter]");
|
||||||
|
} else {
|
||||||
|
client.getPS().SendKeys("[clear]");
|
||||||
|
waitForKeyboardUnlock(client, 3000);
|
||||||
|
client.getPS().SendKeys(tsoCmd + "[enter]");
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.sleep(100);
|
||||||
|
|
||||||
|
// Wait for output to start appearing
|
||||||
|
long startCmd = System.currentTimeMillis();
|
||||||
|
while (System.currentTimeMillis() - startCmd < 4000) {
|
||||||
|
waitForKeyboardUnlock(client, 200);
|
||||||
|
int r = client.getPS().getRows();
|
||||||
|
int c = client.getPS().getCols();
|
||||||
|
String s = client.getPS().copyString(0, 0, r - 1, c - 1);
|
||||||
|
String u = s.toUpperCase();
|
||||||
|
if (u.contains("--RECFM-") || u.contains("--VOLUMES--") || u.contains("***")
|
||||||
|
|| u.contains("READY") || u.contains("NOT IN CATALOG") || u.contains("NOT FOUND")) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Thread.sleep(80);
|
||||||
|
}
|
||||||
|
|
||||||
|
String prevScreen = "";
|
||||||
|
for (int page = 0; page < 5; page++) {
|
||||||
|
waitForKeyboardUnlock(client, 300);
|
||||||
|
int r = client.getPS().getRows();
|
||||||
|
int c = client.getPS().getCols();
|
||||||
|
String screen = client.getPS().copyString(0, 0, r - 1, c - 1);
|
||||||
|
String upper = screen.toUpperCase();
|
||||||
|
|
||||||
|
if (screen.equals(prevScreen) || screen.trim().isEmpty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
prevScreen = screen;
|
||||||
|
output.append(screen).append("\n");
|
||||||
|
|
||||||
|
if (upper.contains("***")) {
|
||||||
|
client.getPS().SendKeys("[enter]");
|
||||||
|
Thread.sleep(100);
|
||||||
|
waitForKeyboardUnlock(client, 2000);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terminal cleanup
|
||||||
|
if (client.getPS() != null) {
|
||||||
|
if (isIspf) {
|
||||||
|
client.getPS().SendKeys("[enter]");
|
||||||
|
Thread.sleep(150);
|
||||||
|
waitForKeyboardUnlock(client, 1500);
|
||||||
|
|
||||||
|
int r = client.getPS().getRows();
|
||||||
|
int c = client.getPS().getCols();
|
||||||
|
String currentScr = client.getPS().copyString(0, 0, r - 1, c - 1);
|
||||||
|
if (currentScr.toUpperCase().contains("--RECFM-") || (client.getScreenBuffer() != null && !client.getScreenBuffer().isFormatted())) {
|
||||||
|
client.getPS().SendKeys("[clear]");
|
||||||
|
waitForKeyboardUnlock(client, 2000);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
client.getPS().SendKeys("[clear]");
|
||||||
|
waitForKeyboardUnlock(client, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done() {
|
||||||
|
try {
|
||||||
|
String outText = get();
|
||||||
|
List<TSODirectoryEntry> parsed = TSODirectoryParser.parse(outText);
|
||||||
|
if (!parsed.isEmpty()) {
|
||||||
|
TSODirectoryEntry updated = parsed.get(0);
|
||||||
|
if (updated.getRecfm() != null) tso.setRecfm(updated.getRecfm());
|
||||||
|
if (updated.getLrecl() > 0) tso.setLrecl(updated.getLrecl());
|
||||||
|
if (updated.getBlksize() > 0) tso.setBlksize(updated.getBlksize());
|
||||||
|
if (updated.getDsorg() != null) tso.setDsorg(updated.getDsorg());
|
||||||
|
if (updated.getVolume() != null && (tso.getVolume() == null || tso.getVolume().equals("******") || tso.getVolume().isEmpty())) {
|
||||||
|
tso.setVolume(updated.getVolume());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update table row in view
|
||||||
|
int modelRow = table.convertRowIndexToModel(row);
|
||||||
|
if (modelRow >= 0 && modelRow < tableModel.getRowCount()) {
|
||||||
|
tableModel.setValueAt(tso.getVolume() != null ? tso.getVolume() : "", modelRow, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
statusLabel.setText("Attributes for " + dsn + ": RECFM=" + tso.getRecfm() + ", LRECL=" + tso.getLrecl() + ", BLKSIZE=" + tso.getBlksize() + ", VOL=" + tso.getVolume());
|
||||||
|
|
||||||
|
StringBuilder msg = new StringBuilder();
|
||||||
|
msg.append("Dataset: ").append(dsn).append("\n\n");
|
||||||
|
msg.append("Volume: ").append(tso.getVolume() != null ? tso.getVolume() : "N/A").append("\n");
|
||||||
|
msg.append("Record Format (RECFM): ").append(tso.getRecfm() != null ? tso.getRecfm() : "N/A").append("\n");
|
||||||
|
msg.append("Logical Record Length (LRECL): ").append(tso.getLrecl() > 0 ? String.valueOf(tso.getLrecl()) : "N/A").append("\n");
|
||||||
|
msg.append("Block Size (BLKSIZE): ").append(tso.getBlksize() > 0 ? String.valueOf(tso.getBlksize()) : "N/A").append("\n");
|
||||||
|
msg.append("Dataset Organization (DSORG): ").append(tso.getDsorg() != null ? tso.getDsorg() : "N/A").append("\n");
|
||||||
|
if (tso.getTracksAllocated() > 0) {
|
||||||
|
msg.append("Allocated Tracks: ").append(tso.getTracksAllocated()).append("\n");
|
||||||
|
}
|
||||||
|
if (tso.getExtents() > 0) {
|
||||||
|
msg.append("Extents: ").append(tso.getExtents()).append("\n");
|
||||||
|
}
|
||||||
|
JOptionPane.showMessageDialog(HostDirectoryDialog.this, msg.toString(), "Dataset Attributes (LISTDS)", JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
} else {
|
||||||
|
statusLabel.setText("No VTOC attributes found for " + dsn);
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
statusLabel.setText("Inspection failed: " + ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
worker.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean waitForKeyboardUnlock(Telnet3270Client client, long timeoutMs) {
|
||||||
|
if (client == null) return false;
|
||||||
|
long deadline = System.currentTimeMillis() + timeoutMs;
|
||||||
|
while (System.currentTimeMillis() < deadline) {
|
||||||
|
boolean locked = false;
|
||||||
|
if (client.getInputProcessor() != null && client.getInputProcessor().isKeyboardLocked()) {
|
||||||
|
locked = true;
|
||||||
|
}
|
||||||
|
if (client.getOIA() != null && (client.getOIA().isKeyLocked() || client.getOIA().isXSystem())) {
|
||||||
|
locked = true;
|
||||||
|
}
|
||||||
|
if (!locked) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Thread.sleep(40);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseCurrentScreen() {
|
private void parseCurrentScreen() {
|
||||||
if (client == null || !client.getConnectionState().isFullSession()) {
|
if (client == null || !client.getConnectionState().isFullSession() || client.getPS() == null) {
|
||||||
statusLabel.setText("No active session to parse.");
|
statusLabel.setText("No active session to parse.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int rows = client.getPS().getRows();
|
||||||
|
int cols = client.getPS().getCols();
|
||||||
|
String screenText = client.getPS().copyString(0, 0, rows - 1, cols - 1);
|
||||||
|
parseText(screenText, "presentation space");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseClipboard() {
|
||||||
|
try {
|
||||||
|
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||||
|
Transferable contents = clipboard.getContents(null);
|
||||||
|
if (contents != null && contents.isDataFlavorSupported(DataFlavor.stringFlavor)) {
|
||||||
|
String clipText = (String) contents.getTransferData(DataFlavor.stringFlavor);
|
||||||
|
if (clipText != null && !clipText.trim().isEmpty()) {
|
||||||
|
parseText(clipText, "clipboard");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
statusLabel.setText("Clipboard contains no text.");
|
||||||
|
} catch (Exception ex) {
|
||||||
|
statusLabel.setText("Error reading clipboard: " + ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseText(String text, String source) {
|
||||||
setupTableColumns();
|
setupTableColumns();
|
||||||
currentEntries.clear();
|
allEntries.clear();
|
||||||
FTConfig.HostType hostType = (FTConfig.HostType) hostTypeCombo.getSelectedItem();
|
FTConfig.HostType hostType = (FTConfig.HostType) hostTypeCombo.getSelectedItem();
|
||||||
String screenText = client.getPS().getString(0, client.getPS().getSize());
|
|
||||||
|
|
||||||
if (hostType == FTConfig.HostType.CMS) {
|
if (hostType == FTConfig.HostType.CMS) {
|
||||||
List<CMSDirectoryEntry> cmsEntries = client.getXfer().getCmsDirectory(screenText);
|
List<CMSDirectoryEntry> cmsEntries = CMSDirectoryParser.parse(text);
|
||||||
currentEntries.addAll(cmsEntries);
|
allEntries.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 {
|
} else {
|
||||||
List<TSODirectoryEntry> tsoEntries = client.getXfer().getTsoDirectory(screenText);
|
List<TSODirectoryEntry> tsoEntries = TSODirectoryParser.parse(text);
|
||||||
currentEntries.addAll(tsoEntries);
|
allEntries.addAll(tsoEntries);
|
||||||
for (TSODirectoryEntry entry : tsoEntries) {
|
}
|
||||||
|
|
||||||
|
applyFilter();
|
||||||
|
if (allEntries.isEmpty()) {
|
||||||
|
statusLabel.setText("0 entries found from " + source + ". Use 'Query Host' or 'Paste Clipboard'.");
|
||||||
|
} else {
|
||||||
|
statusLabel.setText("Parsed " + allEntries.size() + " entries from " + source + ".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyFilter() {
|
||||||
|
tableModel.setRowCount(0);
|
||||||
|
currentEntries.clear();
|
||||||
|
String filter = queryField.getText().trim();
|
||||||
|
FTConfig.HostType hostType = (FTConfig.HostType) hostTypeCombo.getSelectedItem();
|
||||||
|
|
||||||
|
for (HostDirectoryEntry entry : allEntries) {
|
||||||
|
if (!matchesFilter(entry, filter)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
currentEntries.add(entry);
|
||||||
|
if (hostType == FTConfig.HostType.CMS && entry instanceof CMSDirectoryEntry) {
|
||||||
|
CMSDirectoryEntry cms = (CMSDirectoryEntry) entry;
|
||||||
tableModel.addRow(new Object[]{
|
tableModel.addRow(new Object[]{
|
||||||
entry.getDatasetName(), entry.getVolume(), entry.getRecfm(),
|
cms.getFilename() != null ? cms.getFilename() : "",
|
||||||
entry.getLrecl(), entry.getBlksize(), entry.getTracksAllocated(), entry.getExtents()
|
cms.getFiletype() != null ? cms.getFiletype() : "",
|
||||||
|
cms.getFilemode() != null ? cms.getFilemode() : "",
|
||||||
|
cms.getRecfm() != null ? cms.getRecfm() : "",
|
||||||
|
cms.getLrecl() > 0 ? String.valueOf(cms.getLrecl()) : "",
|
||||||
|
cms.getNumRecords() > 0 ? String.valueOf(cms.getNumRecords()) : "",
|
||||||
|
(cms.getDate() != null ? cms.getDate() : "") + " " + (cms.getTime() != null ? cms.getTime() : "")
|
||||||
|
});
|
||||||
|
} else if (entry instanceof TSODirectoryEntry) {
|
||||||
|
TSODirectoryEntry tso = (TSODirectoryEntry) entry;
|
||||||
|
tableModel.addRow(new Object[]{
|
||||||
|
tso.getDatasetName() != null ? tso.getDatasetName() : "",
|
||||||
|
tso.getVolume() != null ? tso.getVolume() : ""
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
statusLabel.setText("Parsed " + currentEntries.size() + " entries from presentation space.");
|
if (!allEntries.isEmpty()) {
|
||||||
|
statusLabel.setText(currentEntries.size() + " entries shown (" + allEntries.size() + " total).");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean matchesFilter(HostDirectoryEntry entry, String filter) {
|
||||||
|
if (filter == null || filter.trim().isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
String cleanFilter = filter.replace("'", "").replace("\"", "").trim();
|
||||||
|
if (cleanFilter.isEmpty() || cleanFilter.equals("*") || cleanFilter.equals("* * *") || cleanFilter.equals("*.*")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry instanceof CMSDirectoryEntry) {
|
||||||
|
return matchesCmsFilter((CMSDirectoryEntry) entry, cleanFilter);
|
||||||
|
} else if (entry instanceof TSODirectoryEntry) {
|
||||||
|
return matchesTsoFilter((TSODirectoryEntry) entry, cleanFilter);
|
||||||
|
} else {
|
||||||
|
return matchesWildcard(entry.getName(), cleanFilter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean matchesCmsFilter(CMSDirectoryEntry cms, String filter) {
|
||||||
|
String fn = cms.getFilename() != null ? cms.getFilename().trim() : "";
|
||||||
|
String ft = cms.getFiletype() != null ? cms.getFiletype().trim() : "";
|
||||||
|
String fm = cms.getFilemode() != null ? cms.getFilemode().trim() : "";
|
||||||
|
|
||||||
|
String[] tokens = filter.split("\\s+");
|
||||||
|
if (tokens.length >= 3) {
|
||||||
|
String fnPat = tokens[0];
|
||||||
|
String ftPat = tokens[1];
|
||||||
|
String fmPat = tokens[2];
|
||||||
|
return matchesWildcard(fn, fnPat)
|
||||||
|
&& matchesWildcard(ft, ftPat)
|
||||||
|
&& matchesCmsFilemode(fm, fmPat);
|
||||||
|
} else if (tokens.length == 2) {
|
||||||
|
String fnPat = tokens[0];
|
||||||
|
String ftPat = tokens[1];
|
||||||
|
return matchesWildcard(fn, fnPat) && matchesWildcard(ft, ftPat);
|
||||||
|
} else {
|
||||||
|
String tok = tokens[0];
|
||||||
|
if (tok.contains(".")) {
|
||||||
|
int dot = tok.indexOf('.');
|
||||||
|
String fnPat = tok.substring(0, dot);
|
||||||
|
String ftPat = tok.substring(dot + 1);
|
||||||
|
return matchesWildcard(fn, fnPat) && matchesWildcard(ft, ftPat);
|
||||||
|
}
|
||||||
|
if (matchesWildcard(fn, tok) || matchesWildcard(ft, tok) || matchesCmsFilemode(fm, tok)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return matchesWildcard(cms.getName(), tok);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean matchesCmsFilemode(String actualFm, String fmPattern) {
|
||||||
|
if (fmPattern == null || fmPattern.isEmpty() || fmPattern.equals("*")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (actualFm == null || actualFm.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (fmPattern.length() == 1 && Character.isLetter(fmPattern.charAt(0))) {
|
||||||
|
return actualFm.toUpperCase().startsWith(fmPattern.toUpperCase());
|
||||||
|
}
|
||||||
|
return matchesWildcard(actualFm, fmPattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean matchesTsoFilter(TSODirectoryEntry tso, String filter) {
|
||||||
|
String dsn = tso.getDatasetName() != null ? tso.getDatasetName().trim() : "";
|
||||||
|
if (filter.equals("*") || filter.equals("* * *") || filter.equals("*.*")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (filter.contains("*") || filter.contains("?")) {
|
||||||
|
return matchesWildcard(dsn, filter);
|
||||||
|
}
|
||||||
|
return dsn.toUpperCase().contains(filter.toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean matchesWildcard(String text, String pattern) {
|
||||||
|
if (text == null) text = "";
|
||||||
|
if (pattern == null || pattern.isEmpty() || pattern.equals("*")) return true;
|
||||||
|
|
||||||
|
StringBuilder regex = new StringBuilder("^");
|
||||||
|
for (int i = 0; i < pattern.length(); i++) {
|
||||||
|
char c = pattern.charAt(i);
|
||||||
|
if (c == '*') {
|
||||||
|
regex.append(".*");
|
||||||
|
} else if (c == '?') {
|
||||||
|
regex.append(".");
|
||||||
|
} else if ("\\.[]{}()+^$|".indexOf(c) >= 0) {
|
||||||
|
regex.append("\\").append(c);
|
||||||
|
} else {
|
||||||
|
regex.append(Character.toUpperCase(c));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
regex.append("$");
|
||||||
|
return text.toUpperCase().matches(regex.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onConfirmSelection() {
|
private void onConfirmSelection() {
|
||||||
@@ -256,6 +909,7 @@ public class HostDirectoryDialog extends JDialog {
|
|||||||
|
|
||||||
HostDirectoryEntry entry = currentEntries.get(row);
|
HostDirectoryEntry entry = currentEntries.get(row);
|
||||||
selectedHostFile = entry.getName();
|
selectedHostFile = entry.getName();
|
||||||
|
selectedEntry = entry;
|
||||||
confirmed = true;
|
confirmed = true;
|
||||||
dispose();
|
dispose();
|
||||||
}
|
}
|
||||||
@@ -267,4 +921,50 @@ public class HostDirectoryDialog extends JDialog {
|
|||||||
public String getSelectedHostFile() {
|
public String getSelectedHostFile() {
|
||||||
return selectedHostFile;
|
return selectedHostFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HostDirectoryEntry getSelectedEntry() {
|
||||||
|
return selectedEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<HostDirectoryEntry> getAllEntries() {
|
||||||
|
return allEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<HostDirectoryEntry> getCurrentEntries() {
|
||||||
|
return currentEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JTextField getQueryField() {
|
||||||
|
return queryField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String resolveDefaultQuery(FTConfig.HostType hostType, String initialQuery) {
|
||||||
|
String defaultQuery = initialQuery != null ? initialQuery.trim() : "";
|
||||||
|
if (hostType == FTConfig.HostType.CMS && (defaultQuery.isEmpty() || defaultQuery.equals("* * *") || defaultQuery.equals("*"))) {
|
||||||
|
defaultQuery = "* * A";
|
||||||
|
}
|
||||||
|
return defaultQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatCMSFilter(String pattern) {
|
||||||
|
if (pattern == null || pattern.trim().isEmpty()) {
|
||||||
|
return "* * A";
|
||||||
|
}
|
||||||
|
String clean = pattern.replace("'", "").replace("\"", "").trim();
|
||||||
|
if (clean.equals("* * *") || clean.equals("*")) {
|
||||||
|
return "* * A";
|
||||||
|
} else if (clean.contains(".")) {
|
||||||
|
int dot = clean.indexOf('.');
|
||||||
|
return clean.substring(0, dot).trim() + " " + clean.substring(dot + 1).trim() + " A";
|
||||||
|
} else {
|
||||||
|
String[] parts = clean.split("\\s+");
|
||||||
|
if (parts.length == 1) {
|
||||||
|
return parts[0] + " * A";
|
||||||
|
} else if (parts.length == 2) {
|
||||||
|
return parts[0] + " " + parts[1] + " A";
|
||||||
|
} else {
|
||||||
|
return parts[0] + " " + parts[1] + " " + parts[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,391 @@
|
|||||||
|
package haus.nightmare.j3270.ui;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
|
||||||
|
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Real-time hotspot and URL auto-detection engine matching IBM Host On-Demand
|
||||||
|
* ShowURLOptions and ScreenText parity.
|
||||||
|
*
|
||||||
|
* Scans screen text using regular expressions for:
|
||||||
|
* 1. URLs (http://, https://, ftp://, file://, mailto:, www.)
|
||||||
|
* 2. Function keys (PF1..PF24, F1..F24, PA1..PA3, [ENTER], ENTER)
|
||||||
|
* 3. Menu choices ("1.", "2.", "===> 3", "=> 4")
|
||||||
|
*
|
||||||
|
* Provides hit-testing for mouse hover/clicks and generates keystroke actions.
|
||||||
|
*/
|
||||||
|
public class HotspotEngine {
|
||||||
|
|
||||||
|
public enum HotspotType {
|
||||||
|
URL,
|
||||||
|
PF_KEY,
|
||||||
|
ENTER,
|
||||||
|
MENU_CHOICE
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an active interactive hotspot region on the terminal screen.
|
||||||
|
*/
|
||||||
|
public static class Hotspot {
|
||||||
|
private final HotspotType type;
|
||||||
|
private final int startRow;
|
||||||
|
private final int startCol;
|
||||||
|
private final int endRow;
|
||||||
|
private final int endCol;
|
||||||
|
private final String displayText;
|
||||||
|
private final String action;
|
||||||
|
private final String tooltip;
|
||||||
|
|
||||||
|
public Hotspot(HotspotType type, int startRow, int startCol, int endRow, int endCol,
|
||||||
|
String displayText, String action, String tooltip) {
|
||||||
|
this.type = type;
|
||||||
|
this.startRow = startRow;
|
||||||
|
this.startCol = startCol;
|
||||||
|
this.endRow = endRow;
|
||||||
|
this.endCol = endCol;
|
||||||
|
this.displayText = displayText;
|
||||||
|
this.action = action;
|
||||||
|
this.tooltip = tooltip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HotspotType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStartRow() {
|
||||||
|
return startRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStartCol() {
|
||||||
|
return startCol;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEndRow() {
|
||||||
|
return endRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEndCol() {
|
||||||
|
return endCol;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayText() {
|
||||||
|
return displayText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAction() {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTooltip() {
|
||||||
|
return tooltip;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test whether the given row/col coordinate falls within this hotspot.
|
||||||
|
*/
|
||||||
|
public boolean contains(int row, int col) {
|
||||||
|
if (row < startRow || row > endRow) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (startRow == endRow) {
|
||||||
|
return col >= startCol && col <= endCol;
|
||||||
|
}
|
||||||
|
if (row == startRow) {
|
||||||
|
return col >= startCol;
|
||||||
|
}
|
||||||
|
if (row == endRow) {
|
||||||
|
return col <= endCol;
|
||||||
|
}
|
||||||
|
return true; // Intermediate rows
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Hotspot{" + type + " [" + startRow + "," + startCol + "]-[" + endRow + "," + endCol + "] '" + displayText + "'}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configurable scan enablement
|
||||||
|
private boolean urlEnabled = true;
|
||||||
|
private boolean pfKeyEnabled = true;
|
||||||
|
private boolean menuChoiceEnabled = true;
|
||||||
|
|
||||||
|
// Regular expression patterns
|
||||||
|
// 1. URLs
|
||||||
|
private static final Pattern URL_PATTERN = Pattern.compile(
|
||||||
|
"(?:https?|ftp|file)://[\\w\\-._~:/?#\\[\\]@!$&'*+,;=%]+|www\\.[\\w\\-._~:/?#\\[\\]@!$&'*+,;=%]+|mailto:[\\w\\-._~:/?#\\[\\]@!$&'*+,;=%]+",
|
||||||
|
Pattern.CASE_INSENSITIVE
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. Function keys (PF1..PF24, F1..F24, PA1..PA3, [ENTER], ENTER)
|
||||||
|
private static final Pattern PF_KEY_PATTERN = Pattern.compile(
|
||||||
|
"(?:(?<=\\W)|^)(PF(?:[1-9]|1[0-9]|2[0-4])|F(?:[1-9]|1[0-9]|2[0-4])|PA[1-3]|\\[ENTER\\]|ENTER)(?:(?=\\W)|$)",
|
||||||
|
Pattern.CASE_INSENSITIVE
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. Menu choices: "1.", "2.", "===> 3", "=> 4", "--> 5"
|
||||||
|
private static final Pattern MENU_NUMBER_PATTERN = Pattern.compile(
|
||||||
|
"(?:(?<=\\s)|^)(\\d{1,2})\\.(?:(?=\\s)|$)"
|
||||||
|
);
|
||||||
|
|
||||||
|
private static final Pattern MENU_PROMPT_PATTERN = Pattern.compile(
|
||||||
|
"(?:===>|=>|-->)\\s*(\\d{1,2})(?:(?=\\s)|$)"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Caching state to avoid rescanning unchanged screens
|
||||||
|
private String cachedScreenText = "";
|
||||||
|
private int cachedRows = 0;
|
||||||
|
private int cachedCols = 0;
|
||||||
|
private List<Hotspot> cachedHotspots = Collections.emptyList();
|
||||||
|
|
||||||
|
public HotspotEngine() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUrlEnabled() {
|
||||||
|
return urlEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUrlEnabled(boolean urlEnabled) {
|
||||||
|
this.urlEnabled = urlEnabled;
|
||||||
|
invalidateCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPfKeyEnabled() {
|
||||||
|
return pfKeyEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPfKeyEnabled(boolean pfKeyEnabled) {
|
||||||
|
this.pfKeyEnabled = pfKeyEnabled;
|
||||||
|
invalidateCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMenuChoiceEnabled() {
|
||||||
|
return menuChoiceEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMenuChoiceEnabled(boolean menuChoiceEnabled) {
|
||||||
|
this.menuChoiceEnabled = menuChoiceEnabled;
|
||||||
|
invalidateCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void invalidateCache() {
|
||||||
|
cachedScreenText = "";
|
||||||
|
cachedHotspots = Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan the given ScreenBuffer for interactive hotspots.
|
||||||
|
*/
|
||||||
|
public synchronized List<Hotspot> scan(ScreenBuffer sb) {
|
||||||
|
if (sb == null) return Collections.emptyList();
|
||||||
|
int rows = sb.getDisplayRows();
|
||||||
|
int cols = sb.getDisplayCols();
|
||||||
|
|
||||||
|
// Build plain text representation
|
||||||
|
StringBuilder fullText = new StringBuilder(rows * (cols + 1));
|
||||||
|
for (int r = 0; r < rows; r++) {
|
||||||
|
for (int c = 0; c < cols; c++) {
|
||||||
|
int baddr = r * cols + c;
|
||||||
|
ExtendedAttribute ea = sb.getDisplayCell(baddr);
|
||||||
|
if (ea == null || ea.isFieldAttribute() || ea.ucs4 <= 0x20 || ea.ucs4 == 0xFF) {
|
||||||
|
fullText.append(' ');
|
||||||
|
} else {
|
||||||
|
fullText.append((char) ea.ucs4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (r < rows - 1) {
|
||||||
|
fullText.append('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return scanText(fullText.toString(), rows, cols);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan formatted screen text (lines separated by '\n') for hotspots.
|
||||||
|
*/
|
||||||
|
public synchronized List<Hotspot> scanText(String screenText, int rows, int cols) {
|
||||||
|
if (screenText == null || rows <= 0 || cols <= 0) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (screenText.equals(cachedScreenText) && rows == cachedRows && cols == cachedCols) {
|
||||||
|
return cachedHotspots;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Hotspot> hotspots = new ArrayList<>();
|
||||||
|
String[] lines = screenText.split("\n", -1);
|
||||||
|
int actualRows = Math.min(rows, lines.length);
|
||||||
|
|
||||||
|
// Scan line by line for precise row/column tracking
|
||||||
|
for (int r = 0; r < actualRows; r++) {
|
||||||
|
String line = lines[r];
|
||||||
|
boolean[] occupied = new boolean[line.length() + 1];
|
||||||
|
|
||||||
|
// 1. Scan URLs
|
||||||
|
if (urlEnabled) {
|
||||||
|
Matcher mUrl = URL_PATTERN.matcher(line);
|
||||||
|
while (mUrl.find()) {
|
||||||
|
int startCol = mUrl.start();
|
||||||
|
int endCol = mUrl.end();
|
||||||
|
String match = mUrl.group();
|
||||||
|
|
||||||
|
// Trim trailing punctuation like . , ; : ) ]
|
||||||
|
while (match.length() > 1) {
|
||||||
|
char last = match.charAt(match.length() - 1);
|
||||||
|
if (last == '.' || last == ',' || last == ';' || last == ':' || last == ')' || last == ']' || last == '>') {
|
||||||
|
match = match.substring(0, match.length() - 1);
|
||||||
|
endCol--;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match.isEmpty()) continue;
|
||||||
|
|
||||||
|
String actionUrl = match;
|
||||||
|
if (match.toLowerCase().startsWith("www.")) {
|
||||||
|
actionUrl = "http://" + match;
|
||||||
|
}
|
||||||
|
|
||||||
|
Hotspot h = new Hotspot(
|
||||||
|
HotspotType.URL,
|
||||||
|
r, startCol, r, endCol - 1,
|
||||||
|
match, actionUrl, "Open URL: " + actionUrl
|
||||||
|
);
|
||||||
|
hotspots.add(h);
|
||||||
|
markOccupied(occupied, startCol, endCol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Scan PF / Function keys
|
||||||
|
if (pfKeyEnabled) {
|
||||||
|
Matcher mPf = PF_KEY_PATTERN.matcher(line);
|
||||||
|
while (mPf.find()) {
|
||||||
|
int startCol = mPf.start(1);
|
||||||
|
int endCol = mPf.end(1);
|
||||||
|
if (isRangeOccupied(occupied, startCol, endCol)) continue;
|
||||||
|
|
||||||
|
String keyText = mPf.group(1).toUpperCase();
|
||||||
|
String action;
|
||||||
|
String tooltip;
|
||||||
|
|
||||||
|
if (keyText.equals("ENTER") || keyText.equals("[ENTER]")) {
|
||||||
|
action = "[enter]";
|
||||||
|
tooltip = "Send [ENTER]";
|
||||||
|
} else if (keyText.startsWith("PF")) {
|
||||||
|
String numStr = keyText.substring(2);
|
||||||
|
action = "[pf" + numStr + "]";
|
||||||
|
tooltip = "Send " + action.toUpperCase();
|
||||||
|
} else if (keyText.startsWith("F")) {
|
||||||
|
String numStr = keyText.substring(1);
|
||||||
|
action = "[pf" + numStr + "]";
|
||||||
|
tooltip = "Send " + action.toUpperCase();
|
||||||
|
} else if (keyText.startsWith("PA")) {
|
||||||
|
String numStr = keyText.substring(2);
|
||||||
|
action = "[pa" + numStr + "]";
|
||||||
|
tooltip = "Send " + action.toUpperCase();
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Hotspot h = new Hotspot(
|
||||||
|
keyText.contains("ENTER") ? HotspotType.ENTER : HotspotType.PF_KEY,
|
||||||
|
r, startCol, r, endCol - 1,
|
||||||
|
mPf.group(1), action, tooltip
|
||||||
|
);
|
||||||
|
hotspots.add(h);
|
||||||
|
markOccupied(occupied, startCol, endCol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Scan Menu choices ("1.", "2.", "===> 3", "=> 4")
|
||||||
|
if (menuChoiceEnabled) {
|
||||||
|
// Pattern 3a: Number followed by dot (e.g. " 1. ")
|
||||||
|
Matcher mNum = MENU_NUMBER_PATTERN.matcher(line);
|
||||||
|
while (mNum.find()) {
|
||||||
|
int startCol = mNum.start(1);
|
||||||
|
int endCol = mNum.end();
|
||||||
|
if (isRangeOccupied(occupied, startCol, endCol)) continue;
|
||||||
|
|
||||||
|
String choiceNum = mNum.group(1);
|
||||||
|
String action = choiceNum + "[enter]";
|
||||||
|
String tooltip = "Select option " + choiceNum;
|
||||||
|
|
||||||
|
Hotspot h = new Hotspot(
|
||||||
|
HotspotType.MENU_CHOICE,
|
||||||
|
r, startCol, r, endCol - 1,
|
||||||
|
choiceNum + ".", action, tooltip
|
||||||
|
);
|
||||||
|
hotspots.add(h);
|
||||||
|
markOccupied(occupied, startCol, endCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern 3b: Prompt followed by number (e.g. "===> 3")
|
||||||
|
Matcher mPrompt = MENU_PROMPT_PATTERN.matcher(line);
|
||||||
|
while (mPrompt.find()) {
|
||||||
|
int startCol = mPrompt.start();
|
||||||
|
int endCol = mPrompt.end();
|
||||||
|
if (isRangeOccupied(occupied, startCol, endCol)) continue;
|
||||||
|
|
||||||
|
String choiceNum = mPrompt.group(1);
|
||||||
|
String action = choiceNum + "[enter]";
|
||||||
|
String tooltip = "Execute choice " + choiceNum;
|
||||||
|
|
||||||
|
Hotspot h = new Hotspot(
|
||||||
|
HotspotType.MENU_CHOICE,
|
||||||
|
r, startCol, r, endCol - 1,
|
||||||
|
mPrompt.group(), action, tooltip
|
||||||
|
);
|
||||||
|
hotspots.add(h);
|
||||||
|
markOccupied(occupied, startCol, endCol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cachedScreenText = screenText;
|
||||||
|
this.cachedRows = rows;
|
||||||
|
this.cachedCols = cols;
|
||||||
|
this.cachedHotspots = Collections.unmodifiableList(hotspots);
|
||||||
|
|
||||||
|
return cachedHotspots;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hit-test for a hotspot at the specified terminal row and column.
|
||||||
|
*/
|
||||||
|
public Hotspot findHotspot(int row, int col) {
|
||||||
|
for (Hotspot h : cachedHotspots) {
|
||||||
|
if (h.contains(row, col)) {
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query currently active cached hotspots.
|
||||||
|
*/
|
||||||
|
public List<Hotspot> getHotspots() {
|
||||||
|
return cachedHotspots;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void markOccupied(boolean[] arr, int start, int end) {
|
||||||
|
for (int i = Math.max(0, start); i < Math.min(arr.length, end); i++) {
|
||||||
|
arr[i] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isRangeOccupied(boolean[] arr, int start, int end) {
|
||||||
|
for (int i = Math.max(0, start); i < Math.min(arr.length, end); i++) {
|
||||||
|
if (arr[i]) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -206,7 +206,8 @@ public class KeyBindings {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a .kmp file from a Reader.
|
* Parse a .kmp file from a Reader.
|
||||||
* Supports both INI-style KEY<n>=action formats and bind statements.
|
* Supports both IBM Host On-Demand (HoD) .kmp format (hashKey prefixes)
|
||||||
|
* and IBM Personal Communications (PCOMM) INI-style and bind statements.
|
||||||
*
|
*
|
||||||
* @param reader the reader to parse from
|
* @param reader the reader to parse from
|
||||||
* @return Map of j3270 action names to key stroke bindings
|
* @return Map of j3270 action names to key stroke bindings
|
||||||
@@ -214,11 +215,38 @@ public class KeyBindings {
|
|||||||
*/
|
*/
|
||||||
public static Map<String, String> parseKmp(Reader reader) throws IOException {
|
public static Map<String, String> parseKmp(Reader reader) throws IOException {
|
||||||
BufferedReader br = (reader instanceof BufferedReader) ? (BufferedReader) reader : new BufferedReader(reader);
|
BufferedReader br = (reader instanceof BufferedReader) ? (BufferedReader) reader : new BufferedReader(reader);
|
||||||
Map<String, List<String>> actionToBindings = new LinkedHashMap<>();
|
List<String> rawLines = new ArrayList<>();
|
||||||
|
boolean hasHodKeyRemap = false;
|
||||||
|
boolean hasHodHashKey = false;
|
||||||
String line;
|
String line;
|
||||||
while ((line = br.readLine()) != null) {
|
while ((line = br.readLine()) != null) {
|
||||||
line = line.trim();
|
rawLines.add(line);
|
||||||
|
String trimmed = line.trim();
|
||||||
|
if (trimmed.equalsIgnoreCase("[KeyRemap]")) {
|
||||||
|
hasHodKeyRemap = true;
|
||||||
|
}
|
||||||
|
int eq = trimmed.indexOf('=');
|
||||||
|
if (eq > 0) {
|
||||||
|
String left = trimmed.substring(0, eq).trim();
|
||||||
|
if (left.matches("^[A-Za-z][0-9]+$")) {
|
||||||
|
hasHodHashKey = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasHodKeyRemap || hasHodHashKey) {
|
||||||
|
Map<String, String> hodMap = haus.nightmare.lib3270j.input.HodKeyMap.parseHodKmp(
|
||||||
|
new StringReader(String.join("\n", rawLines))
|
||||||
|
);
|
||||||
|
if (hodMap != null && !hodMap.isEmpty()) {
|
||||||
|
return hodMap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, List<String>> actionToBindings = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
for (String raw : rawLines) {
|
||||||
|
line = raw.trim();
|
||||||
|
|
||||||
// Strip comments
|
// Strip comments
|
||||||
int commentIdx = line.indexOf(';');
|
int commentIdx = line.indexOf(';');
|
||||||
@@ -262,6 +290,22 @@ public class KeyBindings {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export key bindings to a .kmp file using IBM HoD format.
|
||||||
|
*/
|
||||||
|
public static void exportKmp(File file, Map<String, String> keyBindings) throws IOException {
|
||||||
|
try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) {
|
||||||
|
exportKmp(bw, keyBindings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export key bindings to a Writer using IBM HoD format.
|
||||||
|
*/
|
||||||
|
public static void exportKmp(Writer writer, Map<String, String> keyBindings) throws IOException {
|
||||||
|
haus.nightmare.lib3270j.input.HodKeyMap.exportHodKmp(writer, keyBindings);
|
||||||
|
}
|
||||||
|
|
||||||
private static void parseBindStatement(String stmt, Map<String, List<String>> actionToBindings) {
|
private static void parseBindStatement(String stmt, Map<String, List<String>> actionToBindings) {
|
||||||
String[] parts = stmt.split("\\s+", 2);
|
String[] parts = stmt.split("\\s+", 2);
|
||||||
if (parts.length < 2) return;
|
if (parts.length < 2) return;
|
||||||
@@ -288,6 +332,19 @@ public class KeyBindings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void parseKeyAssignment(String left, String right, Map<String, List<String>> actionToBindings) {
|
private static void parseKeyAssignment(String left, String right, Map<String, List<String>> actionToBindings) {
|
||||||
|
if (left.matches("^[A-Za-z][0-9]+$")) {
|
||||||
|
haus.nightmare.lib3270j.input.HodKeyMap.KeyEntry entry =
|
||||||
|
haus.nightmare.lib3270j.input.HodKeyMap.parseHashKey(left, right);
|
||||||
|
if (entry != null) {
|
||||||
|
String stroke = entry.toKeyStrokeString();
|
||||||
|
String act = haus.nightmare.lib3270j.input.HodKeyMap.resolveMnemonicToAction(right);
|
||||||
|
if (act != null && stroke != null && !stroke.isEmpty()) {
|
||||||
|
addBinding(actionToBindings, act, stroke);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Case A: left is KEY<n> or Modifier-KEY<n>, right is mnemonic
|
// Case A: left is KEY<n> or Modifier-KEY<n>, right is mnemonic
|
||||||
String action = resolveMnemonic(right);
|
String action = resolveMnemonic(right);
|
||||||
String keyStroke = null;
|
String keyStroke = null;
|
||||||
|
|||||||
@@ -0,0 +1,392 @@
|
|||||||
|
package haus.nightmare.j3270.ui;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||||
|
import haus.nightmare.lib3270j.ecl.ECLSession;
|
||||||
|
import haus.nightmare.lib3270j.macro.Macro;
|
||||||
|
import haus.nightmare.lib3270j.macro.MacroScreen;
|
||||||
|
import haus.nightmare.lib3270j.macro.MacroScreens;
|
||||||
|
import haus.nightmare.lib3270j.macro.MacroXmlParser;
|
||||||
|
import haus.nightmare.lib3270j.macro.action.MacroAction;
|
||||||
|
import haus.nightmare.lib3270j.macro.action.MacroActionPrompt;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interactive Swing dialog for loading, editing, running, and debugging
|
||||||
|
* IBM Host On-Demand XML Macros (<HASCRIPT> / .mac files).
|
||||||
|
*/
|
||||||
|
public class MacroDialog extends JDialog implements Macro.MacroListener, Macro.MacroPromptHandler {
|
||||||
|
|
||||||
|
private final Telnet3270Client client;
|
||||||
|
private final TerminalPanel terminalPanel;
|
||||||
|
|
||||||
|
private JTextArea xmlArea;
|
||||||
|
private JTextArea logArea;
|
||||||
|
private JLabel statusLabel;
|
||||||
|
private JButton playBtn;
|
||||||
|
private JButton pauseBtn;
|
||||||
|
private JButton stopBtn;
|
||||||
|
|
||||||
|
private Macro macro;
|
||||||
|
|
||||||
|
public MacroDialog(Frame parent, Telnet3270Client client, TerminalPanel terminalPanel) {
|
||||||
|
super(parent, "IBM HoD XML Macro Runtime (<HASCRIPT>)", false);
|
||||||
|
this.client = client;
|
||||||
|
this.terminalPanel = terminalPanel;
|
||||||
|
AppIcon.applyTo(this);
|
||||||
|
|
||||||
|
this.macro = new Macro(client);
|
||||||
|
this.macro.addMacroListener(this);
|
||||||
|
this.macro.setPromptHandler(this);
|
||||||
|
|
||||||
|
buildUI();
|
||||||
|
setSize(780, 580);
|
||||||
|
setLocationRelativeTo(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildUI() {
|
||||||
|
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
|
||||||
|
mainPanel.setBorder(new EmptyBorder(12, 14, 12, 14));
|
||||||
|
|
||||||
|
Font labelFont = ThemeManager.getUiFont();
|
||||||
|
|
||||||
|
// Top info label
|
||||||
|
JPanel topPanel = new JPanel(new BorderLayout(5, 5));
|
||||||
|
topPanel.setOpaque(false);
|
||||||
|
JLabel descLabel = new JLabel("<html>Load, edit, and execute IBM Host On-Demand XML Macro files (<code>.mac</code> / <code><HASCRIPT></code>).<br>"
|
||||||
|
+ "Automates host flows via screen descriptors, actions, variables, and state transitions.</html>");
|
||||||
|
descLabel.setFont(labelFont);
|
||||||
|
topPanel.add(descLabel, BorderLayout.CENTER);
|
||||||
|
mainPanel.add(topPanel, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
// Center split: XML editor and Execution Log
|
||||||
|
xmlArea = new JTextArea();
|
||||||
|
xmlArea.setFont(ThemeManager.getMonospacedUiFont());
|
||||||
|
xmlArea.setLineWrap(false);
|
||||||
|
xmlArea.setText(getDefaultMacroXml());
|
||||||
|
ThemeManager.styleTextArea(xmlArea);
|
||||||
|
|
||||||
|
JScrollPane xmlScroll = new JScrollPane(xmlArea);
|
||||||
|
ThemeManager.styleScrollPane(xmlScroll);
|
||||||
|
xmlScroll.setBorder(ThemeManager.createTitledBorder("Macro XML (<HASCRIPT>)"));
|
||||||
|
|
||||||
|
logArea = new JTextArea();
|
||||||
|
logArea.setFont(ThemeManager.getMonospacedUiFont(Font.PLAIN, -1));
|
||||||
|
logArea.setEditable(false);
|
||||||
|
logArea.setLineWrap(true);
|
||||||
|
ThemeManager.styleTextArea(logArea);
|
||||||
|
|
||||||
|
JScrollPane logScroll = new JScrollPane(logArea);
|
||||||
|
ThemeManager.styleScrollPane(logScroll);
|
||||||
|
logScroll.setBorder(ThemeManager.createTitledBorder("Execution Log"));
|
||||||
|
|
||||||
|
JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, xmlScroll, logScroll);
|
||||||
|
splitPane.setResizeWeight(0.65);
|
||||||
|
splitPane.setOpaque(false);
|
||||||
|
mainPanel.add(splitPane, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
// Bottom control panel
|
||||||
|
JPanel bottomPanel = new JPanel(new BorderLayout(5, 5));
|
||||||
|
bottomPanel.setOpaque(false);
|
||||||
|
|
||||||
|
statusLabel = new JLabel("Status: Stopped");
|
||||||
|
statusLabel.setForeground(ThemeManager.getFgMuted());
|
||||||
|
statusLabel.setFont(labelFont);
|
||||||
|
bottomPanel.add(statusLabel, BorderLayout.WEST);
|
||||||
|
|
||||||
|
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 6, 0));
|
||||||
|
buttonPanel.setOpaque(false);
|
||||||
|
|
||||||
|
JButton loadBtn = new JButton("Load .mac...");
|
||||||
|
ThemeManager.styleButton(loadBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
loadBtn.setFont(labelFont);
|
||||||
|
loadBtn.addActionListener(e -> loadMacroFile());
|
||||||
|
|
||||||
|
JButton saveBtn = new JButton("Save .mac...");
|
||||||
|
ThemeManager.styleButton(saveBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
saveBtn.setFont(labelFont);
|
||||||
|
saveBtn.addActionListener(e -> saveMacroFile());
|
||||||
|
|
||||||
|
playBtn = new JButton("Play");
|
||||||
|
ThemeManager.styleButton(playBtn, ThemeManager.ButtonVariant.PRIMARY);
|
||||||
|
playBtn.setFont(labelFont);
|
||||||
|
playBtn.addActionListener(e -> playMacro());
|
||||||
|
|
||||||
|
pauseBtn = new JButton("Pause");
|
||||||
|
ThemeManager.styleButton(pauseBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
pauseBtn.setFont(labelFont);
|
||||||
|
pauseBtn.setEnabled(false);
|
||||||
|
pauseBtn.addActionListener(e -> togglePause());
|
||||||
|
|
||||||
|
stopBtn = new JButton("Stop");
|
||||||
|
ThemeManager.styleButton(stopBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
stopBtn.setFont(labelFont);
|
||||||
|
stopBtn.setEnabled(false);
|
||||||
|
stopBtn.addActionListener(e -> stopMacro());
|
||||||
|
|
||||||
|
JButton clearLogBtn = new JButton("Clear Log");
|
||||||
|
ThemeManager.styleButton(clearLogBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
clearLogBtn.setFont(labelFont);
|
||||||
|
clearLogBtn.addActionListener(e -> logArea.setText(""));
|
||||||
|
|
||||||
|
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(8));
|
||||||
|
buttonPanel.add(playBtn);
|
||||||
|
buttonPanel.add(pauseBtn);
|
||||||
|
buttonPanel.add(stopBtn);
|
||||||
|
buttonPanel.add(clearLogBtn);
|
||||||
|
buttonPanel.add(Box.createHorizontalStrut(8));
|
||||||
|
buttonPanel.add(closeBtn);
|
||||||
|
|
||||||
|
bottomPanel.add(buttonPanel, BorderLayout.EAST);
|
||||||
|
mainPanel.add(bottomPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
setContentPane(mainPanel);
|
||||||
|
ThemeManager.applyThemeToWindow(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadMacroFile() {
|
||||||
|
JFileChooser chooser = new JFileChooser();
|
||||||
|
chooser.setDialogTitle("Open IBM HoD Macro");
|
||||||
|
chooser.setFileFilter(new FileNameExtensionFilter("IBM Macro (*.mac, *.xml)", "mac", "xml"));
|
||||||
|
if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
|
||||||
|
File f = chooser.getSelectedFile();
|
||||||
|
try (BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(f), StandardCharsets.UTF_8))) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
String line;
|
||||||
|
while ((line = r.readLine()) != null) {
|
||||||
|
sb.append(line).append("\n");
|
||||||
|
}
|
||||||
|
xmlArea.setText(sb.toString());
|
||||||
|
log("Loaded macro file: " + f.getName());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
JOptionPane.showMessageDialog(this, "Failed to load macro file: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveMacroFile() {
|
||||||
|
JFileChooser chooser = new JFileChooser();
|
||||||
|
chooser.setDialogTitle("Save IBM HoD Macro");
|
||||||
|
chooser.setFileFilter(new FileNameExtensionFilter("IBM Macro (*.mac, *.xml)", "mac", "xml"));
|
||||||
|
if (chooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
|
||||||
|
File f = chooser.getSelectedFile();
|
||||||
|
if (!f.getName().contains(".")) {
|
||||||
|
f = new File(f.getParentFile(), f.getName() + ".mac");
|
||||||
|
}
|
||||||
|
try (Writer w = new OutputStreamWriter(new FileOutputStream(f), StandardCharsets.UTF_8)) {
|
||||||
|
w.write(xmlArea.getText());
|
||||||
|
log("Saved macro file: " + f.getName());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
JOptionPane.showMessageDialog(this, "Failed to save macro file: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void playMacro() {
|
||||||
|
String xml = xmlArea.getText();
|
||||||
|
if (xml == null || xml.trim().isEmpty()) {
|
||||||
|
JOptionPane.showMessageDialog(this, "Macro XML is empty.", "Warning", JOptionPane.WARNING_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client == null || !client.getConnectionState().isFullSession()) {
|
||||||
|
int opt = JOptionPane.showConfirmDialog(this,
|
||||||
|
"Session is not currently connected to a host.\nDo you want to run macro anyway (descriptors will wait for connection)?",
|
||||||
|
"Not Connected", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
|
||||||
|
if (opt != JOptionPane.YES_OPTION) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
MacroScreens screens = MacroXmlParser.parse(xml);
|
||||||
|
macro.setScreens(screens);
|
||||||
|
macro.setClient(client);
|
||||||
|
|
||||||
|
log("Starting macro: " + (screens.getName().isEmpty() ? "Untitled" : screens.getName()));
|
||||||
|
macro.play();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log("Parse / Launch Error: " + ex.getMessage());
|
||||||
|
JOptionPane.showMessageDialog(this, "Macro parsing error: " + ex.getMessage(), "Macro Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void togglePause() {
|
||||||
|
if (macro.getState() == Macro.STATE_PLAYING) {
|
||||||
|
macro.pause();
|
||||||
|
pauseBtn.setText("Resume");
|
||||||
|
log("Macro paused.");
|
||||||
|
} else if (macro.getState() == Macro.STATE_PAUSED) {
|
||||||
|
macro.resume();
|
||||||
|
pauseBtn.setText("Pause");
|
||||||
|
log("Macro resumed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopMacro() {
|
||||||
|
macro.stop();
|
||||||
|
log("Macro stopped by user.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void log(String msg) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
logArea.append(msg + "\n");
|
||||||
|
logArea.setCaretPosition(logArea.getDocument().getLength());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== MacroListener Callbacks ==========
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void macroStateChanged(Macro macro, int oldState, int newState) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
switch (newState) {
|
||||||
|
case Macro.STATE_PLAYING:
|
||||||
|
statusLabel.setText("Status: Playing");
|
||||||
|
statusLabel.setForeground(ThemeManager.getFgMain());
|
||||||
|
playBtn.setEnabled(false);
|
||||||
|
pauseBtn.setEnabled(true);
|
||||||
|
pauseBtn.setText("Pause");
|
||||||
|
stopBtn.setEnabled(true);
|
||||||
|
break;
|
||||||
|
case Macro.STATE_PAUSED:
|
||||||
|
statusLabel.setText("Status: Paused");
|
||||||
|
statusLabel.setForeground(ThemeManager.getFgMuted());
|
||||||
|
pauseBtn.setText("Resume");
|
||||||
|
break;
|
||||||
|
case Macro.STATE_ERROR:
|
||||||
|
statusLabel.setText("Status: Error");
|
||||||
|
statusLabel.setForeground(Color.RED);
|
||||||
|
playBtn.setEnabled(true);
|
||||||
|
pauseBtn.setEnabled(false);
|
||||||
|
stopBtn.setEnabled(false);
|
||||||
|
break;
|
||||||
|
case Macro.STATE_STOPPED:
|
||||||
|
default:
|
||||||
|
statusLabel.setText("Status: Stopped");
|
||||||
|
statusLabel.setForeground(ThemeManager.getFgMuted());
|
||||||
|
playBtn.setEnabled(true);
|
||||||
|
pauseBtn.setEnabled(false);
|
||||||
|
pauseBtn.setText("Pause");
|
||||||
|
stopBtn.setEnabled(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void macroScreenMatching(MacroScreen screen) {
|
||||||
|
log("Matched screen: [" + screen.getName() + "]");
|
||||||
|
if (terminalPanel != null) {
|
||||||
|
terminalPanel.repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void macroActionExecuting(MacroAction action) {
|
||||||
|
log(" -> Executing: " + action.getClass().getSimpleName());
|
||||||
|
if (terminalPanel != null) {
|
||||||
|
terminalPanel.repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void macroMessage(String title, String message) {
|
||||||
|
log("Message [" + title + "]: " + message);
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
JOptionPane.showMessageDialog(this, message, title.isEmpty() ? "Macro Message" : title, JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void macroError(String error) {
|
||||||
|
log("ERROR: " + error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== MacroPromptHandler Callback ==========
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String prompt(MacroActionPrompt prompt) {
|
||||||
|
AtomicReference<String> result = new AtomicReference<>(null);
|
||||||
|
|
||||||
|
if (SwingUtilities.isEventDispatchThread()) {
|
||||||
|
result.set(showPromptModal(prompt));
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
SwingUtilities.invokeAndWait(() -> {
|
||||||
|
result.set(showPromptModal(prompt));
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String showPromptModal(MacroActionPrompt prompt) {
|
||||||
|
JPanel panel = new JPanel(new BorderLayout(5, 5));
|
||||||
|
JLabel msgLabel = new JLabel(prompt.getDescription().isEmpty() ? "Enter parameter:" : prompt.getDescription());
|
||||||
|
panel.add(msgLabel, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
JTextField textField;
|
||||||
|
if (prompt.isEncrypted()) {
|
||||||
|
textField = new JPasswordField(prompt.getDefaultValue(), prompt.getLen() > 0 ? Math.min(prompt.getLen(), 25) : 15);
|
||||||
|
} else {
|
||||||
|
textField = new JTextField(prompt.getDefaultValue(), prompt.getLen() > 0 ? Math.min(prompt.getLen(), 25) : 15);
|
||||||
|
}
|
||||||
|
panel.add(textField, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
int opt = JOptionPane.showConfirmDialog(this, panel,
|
||||||
|
prompt.getTitle().isEmpty() ? "Macro Input Prompt" : prompt.getTitle(),
|
||||||
|
JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
|
||||||
|
|
||||||
|
if (opt == JOptionPane.OK_OPTION) {
|
||||||
|
if (textField instanceof JPasswordField) {
|
||||||
|
return new String(((JPasswordField) textField).getPassword());
|
||||||
|
}
|
||||||
|
return textField.getText();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getDefaultMacroXml() {
|
||||||
|
return "<HASCRIPT name=\"SampleLogon\" timeout=\"15000\" usevars=\"true\">\n"
|
||||||
|
+ " <vars>\n"
|
||||||
|
+ " <create name=\"$userId\" type=\"string\" value=\"USER01\" />\n"
|
||||||
|
+ " <create name=\"$count\" type=\"integer\" value=\"0\" />\n"
|
||||||
|
+ " </vars>\n\n"
|
||||||
|
+ " <screen name=\"LogonScreen\" entryscreen=\"true\">\n"
|
||||||
|
+ " <description>\n"
|
||||||
|
+ " <oia status=\"NOTINHIBITED\" />\n"
|
||||||
|
+ " </description>\n"
|
||||||
|
+ " <actions>\n"
|
||||||
|
+ " <input value=\"$userId + '[enter]'\" movecursor=\"true\" xlatehostkeys=\"true\" />\n"
|
||||||
|
+ " </actions>\n"
|
||||||
|
+ " <nextscreens timeout=\"10000\">\n"
|
||||||
|
+ " <nextscreen name=\"ReadyScreen\" />\n"
|
||||||
|
+ " </nextscreens>\n"
|
||||||
|
+ " </screen>\n\n"
|
||||||
|
+ " <screen name=\"ReadyScreen\" exitscreen=\"true\">\n"
|
||||||
|
+ " <description>\n"
|
||||||
|
+ " <oia status=\"NOTINHIBITED\" />\n"
|
||||||
|
+ " </description>\n"
|
||||||
|
+ " <actions>\n"
|
||||||
|
+ " <message title=\"Success\" value=\"Logon macro completed successfully!\" />\n"
|
||||||
|
+ " </actions>\n"
|
||||||
|
+ " </screen>\n"
|
||||||
|
+ "</HASCRIPT>";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,323 @@
|
|||||||
|
package haus.nightmare.j3270.ui;
|
||||||
|
|
||||||
|
import haus.nightmare.j3270.J3270App;
|
||||||
|
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||||
|
import haus.nightmare.lib3270j.screen.ScreenHistoryBuffer;
|
||||||
|
import haus.nightmare.lib3270j.screen.ScreenHistoryBuffer.ScreenHistoryListener;
|
||||||
|
import haus.nightmare.lib3270j.screen.ScreenHistoryBuffer.ScreenSnapshot;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.datatransfer.Clipboard;
|
||||||
|
import java.awt.datatransfer.StringSelection;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Screen History viewer dialog matching IBM Host On-Demand ScreenHistory UI parity.
|
||||||
|
*
|
||||||
|
* Provides:
|
||||||
|
* - Interactive JSlider scrubber to review past 64 screen buffer frames
|
||||||
|
* - Previous / Next navigation buttons
|
||||||
|
* - Exact monospace terminal frame preview with selection capability
|
||||||
|
* - Copy Screen, Copy Selection, and Copy as Table actions
|
||||||
|
* - Live update synchronization as new host screens arrive
|
||||||
|
*/
|
||||||
|
public class ScreenHistoryDialog extends JDialog implements ScreenHistoryListener {
|
||||||
|
|
||||||
|
private final J3270App parentApp;
|
||||||
|
private final Telnet3270Client client;
|
||||||
|
private final ScreenHistoryBuffer historyBuffer;
|
||||||
|
|
||||||
|
private JSlider scrubSlider;
|
||||||
|
private JButton btnPrev;
|
||||||
|
private JButton btnNext;
|
||||||
|
private JLabel infoLabel;
|
||||||
|
private JTextArea previewArea;
|
||||||
|
private JButton btnCopyScreen;
|
||||||
|
private JButton btnCopySelection;
|
||||||
|
private JButton btnCopyTable;
|
||||||
|
private JButton btnClose;
|
||||||
|
|
||||||
|
private int currentIndex = -1; // 0-based index into historyBuffer
|
||||||
|
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
|
||||||
|
public ScreenHistoryDialog(J3270App parent, Telnet3270Client client) {
|
||||||
|
super(parent, "Screen History", false);
|
||||||
|
this.parentApp = parent;
|
||||||
|
this.client = client;
|
||||||
|
this.historyBuffer = client != null ? client.getScreenHistoryBuffer() : null;
|
||||||
|
|
||||||
|
AppIcon.applyTo(this);
|
||||||
|
initComponents();
|
||||||
|
setupKeyNavigation();
|
||||||
|
|
||||||
|
if (historyBuffer != null) {
|
||||||
|
historyBuffer.addListener(this);
|
||||||
|
refreshHistoryView(historyBuffer.size() - 1);
|
||||||
|
} else {
|
||||||
|
updateEmptyState();
|
||||||
|
}
|
||||||
|
|
||||||
|
addWindowListener(new WindowAdapter() {
|
||||||
|
@Override
|
||||||
|
public void windowClosed(WindowEvent e) {
|
||||||
|
if (historyBuffer != null) {
|
||||||
|
historyBuffer.removeListener(ScreenHistoryDialog.this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setSize(780, 560);
|
||||||
|
setLocationRelativeTo(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initComponents() {
|
||||||
|
setLayout(new BorderLayout(6, 6));
|
||||||
|
JPanel contentPane = new JPanel(new BorderLayout(8, 8));
|
||||||
|
contentPane.setBorder(new EmptyBorder(10, 10, 10, 10));
|
||||||
|
setContentPane(contentPane);
|
||||||
|
|
||||||
|
// Top Navigation Bar
|
||||||
|
JPanel topPanel = new JPanel(new BorderLayout(8, 8));
|
||||||
|
topPanel.setBorder(BorderFactory.createTitledBorder("History Scrub Navigation"));
|
||||||
|
|
||||||
|
JPanel sliderPanel = new JPanel(new BorderLayout(6, 6));
|
||||||
|
btnPrev = new JButton("◄ Prev");
|
||||||
|
ThemeManager.styleButton(btnPrev, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
btnPrev.addActionListener(e -> stepHistory(-1));
|
||||||
|
|
||||||
|
btnNext = new JButton("Next ►");
|
||||||
|
ThemeManager.styleButton(btnNext, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
btnNext.addActionListener(e -> stepHistory(1));
|
||||||
|
|
||||||
|
scrubSlider = new JSlider(1, 1, 1);
|
||||||
|
scrubSlider.setMajorTickSpacing(10);
|
||||||
|
scrubSlider.setMinorTickSpacing(1);
|
||||||
|
scrubSlider.setPaintTicks(true);
|
||||||
|
scrubSlider.setSnapToTicks(true);
|
||||||
|
scrubSlider.addChangeListener(e -> {
|
||||||
|
if (!scrubSlider.getValueIsAdjusting()) {
|
||||||
|
int selected = scrubSlider.getValue() - 1;
|
||||||
|
displayScreen(selected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sliderPanel.add(btnPrev, BorderLayout.WEST);
|
||||||
|
sliderPanel.add(scrubSlider, BorderLayout.CENTER);
|
||||||
|
sliderPanel.add(btnNext, BorderLayout.EAST);
|
||||||
|
|
||||||
|
infoLabel = new JLabel("Screen 0 of 0", SwingConstants.CENTER);
|
||||||
|
infoLabel.setFont(infoLabel.getFont().deriveFont(Font.BOLD, 13f));
|
||||||
|
topPanel.add(sliderPanel, BorderLayout.CENTER);
|
||||||
|
topPanel.add(infoLabel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
contentPane.add(topPanel, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
// Center Terminal Preview Pane
|
||||||
|
previewArea = new JTextArea();
|
||||||
|
previewArea.setEditable(false);
|
||||||
|
previewArea.setFont(new Font("Monospaced", Font.PLAIN, 14));
|
||||||
|
previewArea.setBackground(Color.BLACK);
|
||||||
|
previewArea.setForeground(new Color(0, 255, 65)); // 3270 Green Phosphor
|
||||||
|
previewArea.setCaretColor(Color.WHITE);
|
||||||
|
previewArea.setLineWrap(false);
|
||||||
|
previewArea.setMargin(new Insets(6, 6, 6, 6));
|
||||||
|
|
||||||
|
JScrollPane scrollPane = new JScrollPane(previewArea);
|
||||||
|
scrollPane.setBorder(BorderFactory.createLineBorder(new Color(60, 60, 60)));
|
||||||
|
contentPane.add(scrollPane, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
// Bottom Action Bar
|
||||||
|
JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 8, 4));
|
||||||
|
|
||||||
|
btnCopyScreen = new JButton("Copy Screen");
|
||||||
|
ThemeManager.styleButton(btnCopyScreen, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
btnCopyScreen.setToolTipText("Copy entire historical screen to clipboard");
|
||||||
|
btnCopyScreen.addActionListener(e -> copyCurrentScreen());
|
||||||
|
|
||||||
|
btnCopySelection = new JButton("Copy Selection");
|
||||||
|
ThemeManager.styleButton(btnCopySelection, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
btnCopySelection.setToolTipText("Copy selected text to clipboard");
|
||||||
|
btnCopySelection.addActionListener(e -> copySelectedText());
|
||||||
|
|
||||||
|
btnCopyTable = new JButton("Copy as Table");
|
||||||
|
ThemeManager.styleButton(btnCopyTable, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
btnCopyTable.setToolTipText("Copy screen text with trimmed trailing spaces per row");
|
||||||
|
btnCopyTable.addActionListener(e -> copyAsTable());
|
||||||
|
|
||||||
|
btnClose = new JButton("Close");
|
||||||
|
ThemeManager.styleButton(btnClose, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
btnClose.addActionListener(e -> setVisible(false));
|
||||||
|
|
||||||
|
bottomPanel.add(btnCopyScreen);
|
||||||
|
bottomPanel.add(btnCopySelection);
|
||||||
|
bottomPanel.add(btnCopyTable);
|
||||||
|
bottomPanel.add(Box.createHorizontalStrut(12));
|
||||||
|
bottomPanel.add(btnClose);
|
||||||
|
|
||||||
|
contentPane.add(bottomPanel, BorderLayout.SOUTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupKeyNavigation() {
|
||||||
|
// Keyboard shortcuts for rapid scrub navigation
|
||||||
|
Action prevAction = new AbstractAction() {
|
||||||
|
@Override public void actionPerformed(ActionEvent e) { stepHistory(-1); }
|
||||||
|
};
|
||||||
|
Action nextAction = new AbstractAction() {
|
||||||
|
@Override public void actionPerformed(ActionEvent e) { stepHistory(1); }
|
||||||
|
};
|
||||||
|
Action homeAction = new AbstractAction() {
|
||||||
|
@Override public void actionPerformed(ActionEvent e) { displayScreen(0); }
|
||||||
|
};
|
||||||
|
Action endAction = new AbstractAction() {
|
||||||
|
@Override public void actionPerformed(ActionEvent e) {
|
||||||
|
if (historyBuffer != null) displayScreen(historyBuffer.size() - 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Action closeAction = new AbstractAction() {
|
||||||
|
@Override public void actionPerformed(ActionEvent e) { setVisible(false); }
|
||||||
|
};
|
||||||
|
|
||||||
|
JRootPane root = getRootPane();
|
||||||
|
InputMap im = root.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
|
||||||
|
ActionMap am = root.getActionMap();
|
||||||
|
|
||||||
|
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.ALT_DOWN_MASK), "prevScreen");
|
||||||
|
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0), "prevScreen");
|
||||||
|
am.put("prevScreen", prevAction);
|
||||||
|
|
||||||
|
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.ALT_DOWN_MASK), "nextScreen");
|
||||||
|
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0), "nextScreen");
|
||||||
|
am.put("nextScreen", nextAction);
|
||||||
|
|
||||||
|
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, InputEvent.ALT_DOWN_MASK), "homeScreen");
|
||||||
|
am.put("homeScreen", homeAction);
|
||||||
|
|
||||||
|
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_END, InputEvent.ALT_DOWN_MASK), "endScreen");
|
||||||
|
am.put("endScreen", endAction);
|
||||||
|
|
||||||
|
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "closeDialog");
|
||||||
|
am.put("closeDialog", closeAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stepHistory(int delta) {
|
||||||
|
if (historyBuffer == null || historyBuffer.isEmpty()) return;
|
||||||
|
int target = currentIndex + delta;
|
||||||
|
target = Math.max(0, Math.min(target, historyBuffer.size() - 1));
|
||||||
|
displayScreen(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void displayScreen(int index) {
|
||||||
|
if (historyBuffer == null || historyBuffer.isEmpty()) {
|
||||||
|
updateEmptyState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int total = historyBuffer.size();
|
||||||
|
index = Math.max(0, Math.min(index, total - 1));
|
||||||
|
currentIndex = index;
|
||||||
|
|
||||||
|
ScreenSnapshot snap = historyBuffer.get(index);
|
||||||
|
if (snap != null) {
|
||||||
|
previewArea.setText(snap.getText());
|
||||||
|
previewArea.setCaretPosition(0);
|
||||||
|
|
||||||
|
String timeStr = dateFormat.format(new Date(snap.getTimestamp()));
|
||||||
|
infoLabel.setText("Screen " + (index + 1) + " of " + total + " (" + timeStr + ") — "
|
||||||
|
+ snap.getRows() + "x" + snap.getCols() + ", Cursor: ["
|
||||||
|
+ (snap.getCursorRow() + 1) + "," + (snap.getCursorCol() + 1) + "]");
|
||||||
|
|
||||||
|
scrubSlider.setValue(index + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
btnPrev.setEnabled(index > 0);
|
||||||
|
btnNext.setEnabled(index < total - 1);
|
||||||
|
btnCopyScreen.setEnabled(true);
|
||||||
|
btnCopySelection.setEnabled(true);
|
||||||
|
btnCopyTable.setEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateEmptyState() {
|
||||||
|
currentIndex = -1;
|
||||||
|
previewArea.setText("No screen history captured yet.\n\nScreens are automatically recorded as the host session receives updates or unlocks the keyboard.");
|
||||||
|
infoLabel.setText("Screen 0 of 0 — Empty History");
|
||||||
|
scrubSlider.setEnabled(false);
|
||||||
|
btnPrev.setEnabled(false);
|
||||||
|
btnNext.setEnabled(false);
|
||||||
|
btnCopyScreen.setEnabled(false);
|
||||||
|
btnCopySelection.setEnabled(false);
|
||||||
|
btnCopyTable.setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh slider bounds and select target index.
|
||||||
|
*/
|
||||||
|
public synchronized void refreshHistoryView(int targetIndex) {
|
||||||
|
if (historyBuffer == null || historyBuffer.isEmpty()) {
|
||||||
|
updateEmptyState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int total = historyBuffer.size();
|
||||||
|
scrubSlider.setEnabled(true);
|
||||||
|
scrubSlider.setMinimum(1);
|
||||||
|
scrubSlider.setMaximum(total);
|
||||||
|
|
||||||
|
int select = targetIndex >= 0 ? Math.min(targetIndex, total - 1) : total - 1;
|
||||||
|
displayScreen(select);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHistoryUpdated(ScreenHistoryBuffer buffer, ScreenSnapshot newSnapshot) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
boolean wasAtLatest = (currentIndex >= 0 && currentIndex == buffer.size() - 2);
|
||||||
|
int newTotal = buffer.size();
|
||||||
|
scrubSlider.setMaximum(newTotal);
|
||||||
|
|
||||||
|
if (wasAtLatest || currentIndex < 0) {
|
||||||
|
// Keep tracked with latest frame
|
||||||
|
displayScreen(newTotal - 1);
|
||||||
|
} else {
|
||||||
|
// Keep operator's active scrub position, update label
|
||||||
|
displayScreen(currentIndex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyCurrentScreen() {
|
||||||
|
if (historyBuffer == null || currentIndex < 0) return;
|
||||||
|
ScreenSnapshot snap = historyBuffer.get(currentIndex);
|
||||||
|
if (snap != null) {
|
||||||
|
setClipboardText(snap.getText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copySelectedText() {
|
||||||
|
String selected = previewArea.getSelectedText();
|
||||||
|
if (selected != null && !selected.isEmpty()) {
|
||||||
|
setClipboardText(selected);
|
||||||
|
} else {
|
||||||
|
copyCurrentScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyAsTable() {
|
||||||
|
if (historyBuffer == null || currentIndex < 0) return;
|
||||||
|
ScreenSnapshot snap = historyBuffer.get(currentIndex);
|
||||||
|
if (snap != null) {
|
||||||
|
setClipboardText(snap.getFormattedTable());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setClipboardText(String text) {
|
||||||
|
try {
|
||||||
|
Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||||
|
cb.setContents(new StringSelection(text), null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Toolkit.getDefaultToolkit().beep();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,12 +11,16 @@ import java.io.File;
|
|||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import javax.swing.table.DefaultTableModel;
|
import javax.swing.table.DefaultTableModel;
|
||||||
import javax.swing.table.DefaultTableCellRenderer;
|
import javax.swing.table.DefaultTableCellRenderer;
|
||||||
import javax.swing.table.JTableHeader;
|
import javax.swing.table.JTableHeader;
|
||||||
import javax.swing.border.LineBorder;
|
import javax.swing.border.LineBorder;
|
||||||
import javax.swing.border.TitledBorder;
|
import javax.swing.border.TitledBorder;
|
||||||
|
import haus.nightmare.j3270.storage.HostStorage;
|
||||||
|
import haus.nightmare.j3270.storage.SavedHost;
|
||||||
|
|
||||||
public class SettingsDialog extends JDialog {
|
public class SettingsDialog extends JDialog {
|
||||||
|
|
||||||
@@ -42,12 +46,23 @@ public class SettingsDialog extends JDialog {
|
|||||||
private JCheckBox blockPasteCheck;
|
private JCheckBox blockPasteCheck;
|
||||||
private JCheckBox enablePasteFromExcelCheck;
|
private JCheckBox enablePasteFromExcelCheck;
|
||||||
private JCheckBox pasteStopAtProtectedCheck;
|
private JCheckBox pasteStopAtProtectedCheck;
|
||||||
|
private JCheckBox pasteWordBreakCheck;
|
||||||
|
private JCheckBox trimRectSizingHandlesCheck;
|
||||||
|
private JCheckBox trimRectRemainAfterEditCheck;
|
||||||
|
|
||||||
|
// Phase 25: Hotspots & Mouse Wheel controls
|
||||||
|
private JCheckBox hotspotsEnabledCheck;
|
||||||
|
private JCheckBox hotspotsUrlCheck;
|
||||||
|
private JCheckBox hotspotsPfCheck;
|
||||||
|
private JCheckBox hotspotsMenuCheck;
|
||||||
|
private JCheckBox mouseWheelEnabledCheck;
|
||||||
|
private JTextField mouseWheelUpField;
|
||||||
|
private JTextField mouseWheelDownField;
|
||||||
|
|
||||||
// Connection tab
|
// Connection tab
|
||||||
private JComboBox<Settings.StartupBehavior> startupBehaviorBox;
|
private JComboBox<Settings.StartupBehavior> startupBehaviorBox;
|
||||||
private JPanel autoConnectPanel;
|
private JPanel autoConnectPanel;
|
||||||
private JTextField hostField;
|
private JComboBox<SavedHost> savedHostBox;
|
||||||
private JTextField portField;
|
|
||||||
private JCheckBox autoReconnectCheck;
|
private JCheckBox autoReconnectCheck;
|
||||||
private JSpinner dynamicRowsSpinner;
|
private JSpinner dynamicRowsSpinner;
|
||||||
private JSpinner dynamicColsSpinner;
|
private JSpinner dynamicColsSpinner;
|
||||||
@@ -71,6 +86,8 @@ public class SettingsDialog extends JDialog {
|
|||||||
// Bottom action buttons
|
// Bottom action buttons
|
||||||
private JButton btnImport;
|
private JButton btnImport;
|
||||||
private JButton btnExport;
|
private JButton btnExport;
|
||||||
|
private JButton btnImportConnections;
|
||||||
|
private JButton btnExportConnections;
|
||||||
|
|
||||||
// Colors tab component tracking
|
// Colors tab component tracking
|
||||||
private final JPanel[] hostColorSwatches = new JPanel[16];
|
private final JPanel[] hostColorSwatches = new JPanel[16];
|
||||||
@@ -82,7 +99,7 @@ public class SettingsDialog extends JDialog {
|
|||||||
AppIcon.applyTo(this);
|
AppIcon.applyTo(this);
|
||||||
|
|
||||||
initComponents();
|
initComponents();
|
||||||
setSize(660, 540);
|
setSize(680, 650);
|
||||||
setLocationRelativeTo(parent);
|
setLocationRelativeTo(parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,13 +112,18 @@ public class SettingsDialog extends JDialog {
|
|||||||
tabbedPane.addTab("Connection", createConnectionPanel());
|
tabbedPane.addTab("Connection", createConnectionPanel());
|
||||||
tabbedPane.addTab("Entry Assist", createEntryAssistPanel());
|
tabbedPane.addTab("Entry Assist", createEntryAssistPanel());
|
||||||
|
|
||||||
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 8, 8));
|
|
||||||
btnImport = new JButton("Import Config...");
|
btnImport = new JButton("Import Config...");
|
||||||
ThemeManager.styleButton(btnImport, ThemeManager.ButtonVariant.DEFAULT);
|
ThemeManager.styleButton(btnImport, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
|
||||||
btnExport = new JButton("Export Config...");
|
btnExport = new JButton("Export Config...");
|
||||||
ThemeManager.styleButton(btnExport, ThemeManager.ButtonVariant.DEFAULT);
|
ThemeManager.styleButton(btnExport, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
|
||||||
|
btnImportConnections = new JButton("Import Connections...");
|
||||||
|
ThemeManager.styleButton(btnImportConnections, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
|
||||||
|
btnExportConnections = new JButton("Export Connections...");
|
||||||
|
ThemeManager.styleButton(btnExportConnections, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
|
||||||
JButton btnApply = new JButton("Apply");
|
JButton btnApply = new JButton("Apply");
|
||||||
ThemeManager.styleButton(btnApply, ThemeManager.ButtonVariant.DEFAULT);
|
ThemeManager.styleButton(btnApply, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
|
||||||
@@ -119,6 +141,14 @@ public class SettingsDialog extends JDialog {
|
|||||||
exportConfig();
|
exportConfig();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
btnImportConnections.addActionListener((ActionEvent e) -> {
|
||||||
|
importConnections();
|
||||||
|
});
|
||||||
|
|
||||||
|
btnExportConnections.addActionListener((ActionEvent e) -> {
|
||||||
|
exportConnections();
|
||||||
|
});
|
||||||
|
|
||||||
btnOk.addActionListener((ActionEvent e) -> {
|
btnOk.addActionListener((ActionEvent e) -> {
|
||||||
boolean success = applySettings();
|
boolean success = applySettings();
|
||||||
if (success) {
|
if (success) {
|
||||||
@@ -134,12 +164,28 @@ public class SettingsDialog extends JDialog {
|
|||||||
dispose();
|
dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
buttonPanel.add(btnImport);
|
JPanel buttonPanel = new JPanel(new GridLayout(3, 1, 0, 4));
|
||||||
buttonPanel.add(btnExport);
|
buttonPanel.setBorder(BorderFactory.createEmptyBorder(4, 8, 8, 8));
|
||||||
buttonPanel.add(Box.createHorizontalStrut(20));
|
|
||||||
buttonPanel.add(btnApply);
|
JPanel row1 = new JPanel(new FlowLayout(FlowLayout.RIGHT, 6, 2));
|
||||||
buttonPanel.add(btnCancel);
|
row1.setOpaque(false);
|
||||||
buttonPanel.add(btnOk);
|
row1.add(btnImport);
|
||||||
|
row1.add(btnExport);
|
||||||
|
|
||||||
|
JPanel row2 = new JPanel(new FlowLayout(FlowLayout.RIGHT, 6, 2));
|
||||||
|
row2.setOpaque(false);
|
||||||
|
row2.add(btnImportConnections);
|
||||||
|
row2.add(btnExportConnections);
|
||||||
|
|
||||||
|
JPanel row3 = new JPanel(new FlowLayout(FlowLayout.RIGHT, 6, 2));
|
||||||
|
row3.setOpaque(false);
|
||||||
|
row3.add(btnApply);
|
||||||
|
row3.add(btnCancel);
|
||||||
|
row3.add(btnOk);
|
||||||
|
|
||||||
|
buttonPanel.add(row1);
|
||||||
|
buttonPanel.add(row2);
|
||||||
|
buttonPanel.add(row3);
|
||||||
|
|
||||||
getContentPane().add(tabbedPane, BorderLayout.CENTER);
|
getContentPane().add(tabbedPane, BorderLayout.CENTER);
|
||||||
getContentPane().add(buttonPanel, BorderLayout.SOUTH);
|
getContentPane().add(buttonPanel, BorderLayout.SOUTH);
|
||||||
@@ -338,7 +384,17 @@ public class SettingsDialog extends JDialog {
|
|||||||
JTabbedPane behaviorTabs = new JTabbedPane();
|
JTabbedPane behaviorTabs = new JTabbedPane();
|
||||||
ThemeManager.styleTabbedPane(behaviorTabs);
|
ThemeManager.styleTabbedPane(behaviorTabs);
|
||||||
|
|
||||||
behaviorTabs.addTab("General", createGeneralBehaviorPanel());
|
JPanel generalContent = createGeneralBehaviorPanel();
|
||||||
|
JScrollPane generalScroll = new JScrollPane(generalContent);
|
||||||
|
ThemeManager.styleScrollPane(generalScroll);
|
||||||
|
generalScroll.setBorder(BorderFactory.createEmptyBorder());
|
||||||
|
if (generalScroll.getViewport() != null) {
|
||||||
|
generalScroll.getViewport().setBackground(generalContent.getBackground());
|
||||||
|
}
|
||||||
|
generalScroll.getVerticalScrollBar().setUnitIncrement(16);
|
||||||
|
generalScroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
||||||
|
|
||||||
|
behaviorTabs.addTab("General", generalScroll);
|
||||||
behaviorTabs.addTab("Keymap", createKeymapPanel());
|
behaviorTabs.addTab("Keymap", createKeymapPanel());
|
||||||
|
|
||||||
panel.add(behaviorTabs, BorderLayout.CENTER);
|
panel.add(behaviorTabs, BorderLayout.CENTER);
|
||||||
@@ -346,12 +402,13 @@ public class SettingsDialog extends JDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private JPanel createGeneralBehaviorPanel() {
|
private JPanel createGeneralBehaviorPanel() {
|
||||||
JPanel panel = new JPanel();
|
ScrollablePanel panel = new ScrollablePanel();
|
||||||
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
||||||
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||||
|
|
||||||
// Group 1: Typing & Operational Modes
|
// Group 1: Typing & Operational Modes
|
||||||
JPanel typingGroup = new JPanel(new GridBagLayout());
|
JPanel typingGroup = new JPanel(new GridBagLayout());
|
||||||
|
typingGroup.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||||
typingGroup.setBorder(BorderFactory.createTitledBorder("Typing & Operational Modes"));
|
typingGroup.setBorder(BorderFactory.createTitledBorder("Typing & Operational Modes"));
|
||||||
GridBagConstraints gbc = new GridBagConstraints();
|
GridBagConstraints gbc = new GridBagConstraints();
|
||||||
gbc.insets = new Insets(4, 6, 4, 6);
|
gbc.insets = new Insets(4, 6, 4, 6);
|
||||||
@@ -402,6 +459,7 @@ public class SettingsDialog extends JDialog {
|
|||||||
|
|
||||||
// Group 2: Selection & Clipboard
|
// Group 2: Selection & Clipboard
|
||||||
JPanel selGroup = new JPanel(new GridBagLayout());
|
JPanel selGroup = new JPanel(new GridBagLayout());
|
||||||
|
selGroup.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||||
selGroup.setBorder(BorderFactory.createTitledBorder("Selection & Clipboard"));
|
selGroup.setBorder(BorderFactory.createTitledBorder("Selection & Clipboard"));
|
||||||
GridBagConstraints gbcS = new GridBagConstraints();
|
GridBagConstraints gbcS = new GridBagConstraints();
|
||||||
gbcS.insets = new Insets(4, 6, 4, 6);
|
gbcS.insets = new Insets(4, 6, 4, 6);
|
||||||
@@ -430,7 +488,106 @@ public class SettingsDialog extends JDialog {
|
|||||||
gbcS.gridy = 3;
|
gbcS.gridy = 3;
|
||||||
selGroup.add(pasteStopAtProtectedCheck, gbcS);
|
selGroup.add(pasteStopAtProtectedCheck, gbcS);
|
||||||
|
|
||||||
|
pasteWordBreakCheck = new JCheckBox("Paste without splitting words across fields", Settings.getPasteWordBreak());
|
||||||
|
ThemeManager.styleCheckBox(pasteWordBreakCheck);
|
||||||
|
gbcS.gridy = 4;
|
||||||
|
selGroup.add(pasteWordBreakCheck, gbcS);
|
||||||
|
|
||||||
|
trimRectSizingHandlesCheck = new JCheckBox("Trim rectangle sizing handles", Settings.getTrimRectSizingHandles());
|
||||||
|
ThemeManager.styleCheckBox(trimRectSizingHandlesCheck);
|
||||||
|
gbcS.gridy = 5;
|
||||||
|
selGroup.add(trimRectSizingHandlesCheck, gbcS);
|
||||||
|
|
||||||
|
trimRectRemainAfterEditCheck = new JCheckBox("Trim rectangle remains after cut/copy/paste", Settings.getTrimRectRemainAfterEdit());
|
||||||
|
ThemeManager.styleCheckBox(trimRectRemainAfterEditCheck);
|
||||||
|
gbcS.gridy = 6;
|
||||||
|
selGroup.add(trimRectRemainAfterEditCheck, gbcS);
|
||||||
|
|
||||||
panel.add(selGroup);
|
panel.add(selGroup);
|
||||||
|
panel.add(Box.createVerticalStrut(10));
|
||||||
|
|
||||||
|
// Group 3: Hotspots & Mouse Wheel Navigation (Phase 25)
|
||||||
|
JPanel hotGroup = new JPanel(new GridBagLayout());
|
||||||
|
hotGroup.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||||
|
hotGroup.setBorder(BorderFactory.createTitledBorder("Hotspots & Mouse Wheel Navigation"));
|
||||||
|
GridBagConstraints gbcH = new GridBagConstraints();
|
||||||
|
gbcH.insets = new Insets(3, 6, 3, 6);
|
||||||
|
gbcH.anchor = GridBagConstraints.WEST;
|
||||||
|
gbcH.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
gbcH.gridx = 0;
|
||||||
|
gbcH.weightx = 1.0;
|
||||||
|
|
||||||
|
hotspotsEnabledCheck = new JCheckBox("Enable Interactive Hotspots (links, function keys, choices)", Settings.getHotspotsEnabled());
|
||||||
|
ThemeManager.styleCheckBox(hotspotsEnabledCheck);
|
||||||
|
gbcH.gridy = 0;
|
||||||
|
hotGroup.add(hotspotsEnabledCheck, gbcH);
|
||||||
|
|
||||||
|
hotspotsUrlCheck = new JCheckBox("Auto-detect URLs (http://, https://, ftp://, www)", Settings.getHotspotsUrlEnabled());
|
||||||
|
ThemeManager.styleCheckBox(hotspotsUrlCheck);
|
||||||
|
gbcH.gridy = 1;
|
||||||
|
gbcH.insets = new Insets(2, 24, 2, 6);
|
||||||
|
hotGroup.add(hotspotsUrlCheck, gbcH);
|
||||||
|
|
||||||
|
hotspotsPfCheck = new JCheckBox("Auto-detect Function keys (PF1..PF24, F1..F24, PA1..PA3, [ENTER])", Settings.getHotspotsPfEnabled());
|
||||||
|
ThemeManager.styleCheckBox(hotspotsPfCheck);
|
||||||
|
gbcH.gridy = 2;
|
||||||
|
hotGroup.add(hotspotsPfCheck, gbcH);
|
||||||
|
|
||||||
|
hotspotsMenuCheck = new JCheckBox("Auto-detect Menu choices (\"1.\", \"2.\", \"===> 3\")", Settings.getHotspotsMenuChoicesEnabled());
|
||||||
|
ThemeManager.styleCheckBox(hotspotsMenuCheck);
|
||||||
|
gbcH.gridy = 3;
|
||||||
|
hotGroup.add(hotspotsMenuCheck, gbcH);
|
||||||
|
|
||||||
|
hotspotsEnabledCheck.addActionListener(e -> {
|
||||||
|
boolean en = hotspotsEnabledCheck.isSelected();
|
||||||
|
hotspotsUrlCheck.setEnabled(en);
|
||||||
|
hotspotsPfCheck.setEnabled(en);
|
||||||
|
hotspotsMenuCheck.setEnabled(en);
|
||||||
|
});
|
||||||
|
boolean initHotEn = hotspotsEnabledCheck.isSelected();
|
||||||
|
hotspotsUrlCheck.setEnabled(initHotEn);
|
||||||
|
hotspotsPfCheck.setEnabled(initHotEn);
|
||||||
|
hotspotsMenuCheck.setEnabled(initHotEn);
|
||||||
|
|
||||||
|
mouseWheelEnabledCheck = new JCheckBox("Enable Mouse Wheel Navigation (scroll up/down)", Settings.getMouseWheelEnabled());
|
||||||
|
ThemeManager.styleCheckBox(mouseWheelEnabledCheck);
|
||||||
|
gbcH.gridy = 4;
|
||||||
|
gbcH.insets = new Insets(6, 6, 3, 6);
|
||||||
|
hotGroup.add(mouseWheelEnabledCheck, gbcH);
|
||||||
|
|
||||||
|
JPanel wheelKeysPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0));
|
||||||
|
wheelKeysPanel.setOpaque(false);
|
||||||
|
JLabel lblUp = new JLabel("Wheel Up:");
|
||||||
|
mouseWheelUpField = new JTextField(Settings.getMouseWheelUpKey(), 6);
|
||||||
|
ThemeManager.styleTextField(mouseWheelUpField);
|
||||||
|
JLabel lblDown = new JLabel("Wheel Down:");
|
||||||
|
mouseWheelDownField = new JTextField(Settings.getMouseWheelDownKey(), 6);
|
||||||
|
ThemeManager.styleTextField(mouseWheelDownField);
|
||||||
|
|
||||||
|
wheelKeysPanel.add(lblUp);
|
||||||
|
wheelKeysPanel.add(mouseWheelUpField);
|
||||||
|
wheelKeysPanel.add(Box.createHorizontalStrut(12));
|
||||||
|
wheelKeysPanel.add(lblDown);
|
||||||
|
wheelKeysPanel.add(mouseWheelDownField);
|
||||||
|
|
||||||
|
mouseWheelEnabledCheck.addActionListener(e -> {
|
||||||
|
boolean mwEn = mouseWheelEnabledCheck.isSelected();
|
||||||
|
lblUp.setEnabled(mwEn);
|
||||||
|
mouseWheelUpField.setEnabled(mwEn);
|
||||||
|
lblDown.setEnabled(mwEn);
|
||||||
|
mouseWheelDownField.setEnabled(mwEn);
|
||||||
|
});
|
||||||
|
boolean initMwEn = mouseWheelEnabledCheck.isSelected();
|
||||||
|
lblUp.setEnabled(initMwEn);
|
||||||
|
mouseWheelUpField.setEnabled(initMwEn);
|
||||||
|
lblDown.setEnabled(initMwEn);
|
||||||
|
mouseWheelDownField.setEnabled(initMwEn);
|
||||||
|
|
||||||
|
gbcH.gridy = 5;
|
||||||
|
gbcH.insets = new Insets(2, 24, 2, 6);
|
||||||
|
hotGroup.add(wheelKeysPanel, gbcH);
|
||||||
|
|
||||||
|
panel.add(hotGroup);
|
||||||
panel.add(Box.createVerticalGlue());
|
panel.add(Box.createVerticalGlue());
|
||||||
|
|
||||||
return panel;
|
return panel;
|
||||||
@@ -465,22 +622,33 @@ public class SettingsDialog extends JDialog {
|
|||||||
acGbc.insets = new Insets(5, 5, 5, 5);
|
acGbc.insets = new Insets(5, 5, 5, 5);
|
||||||
acGbc.anchor = GridBagConstraints.WEST;
|
acGbc.anchor = GridBagConstraints.WEST;
|
||||||
|
|
||||||
JLabel hostLabel = new JLabel("Host:");
|
JLabel savedConnLabel = new JLabel("Saved Connection:");
|
||||||
acGbc.gridx = 0;
|
acGbc.gridx = 0;
|
||||||
acGbc.gridy = 0;
|
acGbc.gridy = 0;
|
||||||
autoConnectPanel.add(hostLabel, acGbc);
|
autoConnectPanel.add(savedConnLabel, acGbc);
|
||||||
|
|
||||||
hostField = new JTextField(Settings.getAutoConnectHost(), 15);
|
savedHostBox = new JComboBox<>();
|
||||||
|
savedHostBox.setRenderer(new DefaultListCellRenderer() {
|
||||||
|
@Override
|
||||||
|
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||||
|
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||||
|
if (value instanceof SavedHost) {
|
||||||
|
SavedHost sh = (SavedHost) value;
|
||||||
|
if (sh.getName() != null && !sh.getName().trim().isEmpty()) {
|
||||||
|
setText(sh.getName() + " (" + sh.getHost() + ":" + sh.getPort() + ")");
|
||||||
|
} else {
|
||||||
|
setText(sh.getHost() + ":" + sh.getPort());
|
||||||
|
}
|
||||||
|
} else if (value == null) {
|
||||||
|
setText("(No saved connections)");
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
refreshSavedHostsBox();
|
||||||
acGbc.gridx = 1;
|
acGbc.gridx = 1;
|
||||||
autoConnectPanel.add(hostField, acGbc);
|
acGbc.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
autoConnectPanel.add(savedHostBox, acGbc);
|
||||||
JLabel portLabel = new JLabel("Port:");
|
|
||||||
acGbc.gridx = 2;
|
|
||||||
autoConnectPanel.add(portLabel, acGbc);
|
|
||||||
|
|
||||||
portField = new JTextField(String.valueOf(Settings.getAutoConnectPort()), 4);
|
|
||||||
acGbc.gridx = 3;
|
|
||||||
autoConnectPanel.add(portField, acGbc);
|
|
||||||
|
|
||||||
gbc.gridx = 0;
|
gbc.gridx = 0;
|
||||||
gbc.gridy = 1;
|
gbc.gridy = 1;
|
||||||
@@ -535,6 +703,30 @@ public class SettingsDialog extends JDialog {
|
|||||||
return panel;
|
return panel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void refreshSavedHostsBox() {
|
||||||
|
if (savedHostBox == null) return;
|
||||||
|
savedHostBox.removeAllItems();
|
||||||
|
List<SavedHost> savedHosts = HostStorage.getSavedHosts();
|
||||||
|
SavedHost currentAutoHost = HostStorage.getAutoConnectHost();
|
||||||
|
SavedHost toSelect = null;
|
||||||
|
for (SavedHost sh : savedHosts) {
|
||||||
|
savedHostBox.addItem(sh);
|
||||||
|
if (currentAutoHost != null && Objects.equals(sh.getId(), currentAutoHost.getId())) {
|
||||||
|
toSelect = sh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (toSelect != null) {
|
||||||
|
savedHostBox.setSelectedItem(toSelect);
|
||||||
|
savedHostBox.setEnabled(true);
|
||||||
|
} else if (!savedHosts.isEmpty()) {
|
||||||
|
savedHostBox.setSelectedIndex(0);
|
||||||
|
savedHostBox.setEnabled(true);
|
||||||
|
} else {
|
||||||
|
savedHostBox.addItem(null);
|
||||||
|
savedHostBox.setEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private JPanel createEntryAssistPanel() {
|
private JPanel createEntryAssistPanel() {
|
||||||
JPanel panel = new JPanel();
|
JPanel panel = new JPanel();
|
||||||
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
||||||
@@ -676,14 +868,23 @@ public class SettingsDialog extends JDialog {
|
|||||||
monoPanel.add(p);
|
monoPanel.add(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
JPanel bottomPanel = new JPanel(new BorderLayout(8, 0));
|
JPanel bottomPanel = new JPanel();
|
||||||
|
bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.Y_AXIS));
|
||||||
bottomPanel.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6));
|
bottomPanel.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6));
|
||||||
|
|
||||||
fourColorOverrideCheck = new JCheckBox("Base 4-Color Override mode (3279 green/white/red/turquoise)", Settings.getFourColorOverride());
|
fourColorOverrideCheck = new JCheckBox("Base 4 Override Mode (3279)", Settings.getFourColorOverride());
|
||||||
|
fourColorOverrideCheck.setToolTipText("Base 4-Color Override mode (3279 green/white/red/turquoise)");
|
||||||
ThemeManager.styleCheckBox(fourColorOverrideCheck);
|
ThemeManager.styleCheckBox(fourColorOverrideCheck);
|
||||||
bottomPanel.add(fourColorOverrideCheck, BorderLayout.WEST);
|
fourColorOverrideCheck.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||||
|
bottomPanel.add(fourColorOverrideCheck);
|
||||||
|
|
||||||
JButton btnResetColors = new JButton("Reset to Defaults");
|
bottomPanel.add(Box.createVerticalStrut(4));
|
||||||
|
|
||||||
|
JPanel btnRow = new JPanel(new BorderLayout());
|
||||||
|
btnRow.setOpaque(false);
|
||||||
|
btnRow.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||||
|
|
||||||
|
JButton btnResetColors = new JButton("Restore Defaults");
|
||||||
ThemeManager.styleButton(btnResetColors, ThemeManager.ButtonVariant.DEFAULT);
|
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++) {
|
||||||
@@ -701,7 +902,8 @@ public class SettingsDialog extends JDialog {
|
|||||||
}
|
}
|
||||||
JOptionPane.showMessageDialog(main, "Colors reset. Click Apply to save.");
|
JOptionPane.showMessageDialog(main, "Colors reset. Click Apply to save.");
|
||||||
});
|
});
|
||||||
bottomPanel.add(btnResetColors, BorderLayout.EAST);
|
btnRow.add(btnResetColors, BorderLayout.EAST);
|
||||||
|
bottomPanel.add(btnRow);
|
||||||
|
|
||||||
main.add(hostPanel, BorderLayout.NORTH);
|
main.add(hostPanel, BorderLayout.NORTH);
|
||||||
main.add(monoPanel, BorderLayout.CENTER);
|
main.add(monoPanel, BorderLayout.CENTER);
|
||||||
@@ -870,7 +1072,7 @@ public class SettingsDialog extends JDialog {
|
|||||||
btnPanel.add(btnReset);
|
btnPanel.add(btnReset);
|
||||||
|
|
||||||
// Import Keymap — import from .kmp file
|
// Import Keymap — import from .kmp file
|
||||||
JButton btnImportKmp = new JButton("Import Keymap...");
|
JButton btnImportKmp = new JButton("Import .kmp...");
|
||||||
ThemeManager.styleButton(btnImportKmp, ThemeManager.ButtonVariant.DEFAULT);
|
ThemeManager.styleButton(btnImportKmp, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
btnImportKmp.addActionListener(e -> {
|
btnImportKmp.addActionListener(e -> {
|
||||||
JFileChooser chooser = new JFileChooser();
|
JFileChooser chooser = new JFileChooser();
|
||||||
@@ -908,6 +1110,36 @@ public class SettingsDialog extends JDialog {
|
|||||||
});
|
});
|
||||||
btnPanel.add(btnImportKmp);
|
btnPanel.add(btnImportKmp);
|
||||||
|
|
||||||
|
// Export Keymap — export to .kmp file
|
||||||
|
JButton btnExportKmp = new JButton("Export .kmp...");
|
||||||
|
ThemeManager.styleButton(btnExportKmp, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
btnExportKmp.addActionListener(e -> {
|
||||||
|
JFileChooser chooser = new JFileChooser();
|
||||||
|
chooser.setDialogTitle("Export Keymap Profile");
|
||||||
|
chooser.setFileFilter(new javax.swing.filechooser.FileNameExtensionFilter("Keyboard Map Profile (*.kmp)", "kmp"));
|
||||||
|
chooser.setSelectedFile(new File("User.kmp"));
|
||||||
|
int ret = chooser.showSaveDialog(this);
|
||||||
|
if (ret == JFileChooser.APPROVE_OPTION) {
|
||||||
|
File f = chooser.getSelectedFile();
|
||||||
|
if (!f.getName().toLowerCase().endsWith(".kmp")) {
|
||||||
|
f = new File(f.getParentFile(), f.getName() + ".kmp");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
KeyBindings.exportKmp(f, tempKeyBindings);
|
||||||
|
JOptionPane.showMessageDialog(this,
|
||||||
|
"Successfully exported key bindings to " + f.getName(),
|
||||||
|
"Keymap Exported",
|
||||||
|
JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
JOptionPane.showMessageDialog(this,
|
||||||
|
"Failed to export keymap: " + ex.getMessage(),
|
||||||
|
"Export Error",
|
||||||
|
JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
btnPanel.add(btnExportKmp);
|
||||||
|
|
||||||
main.add(new JScrollPane(table), BorderLayout.CENTER);
|
main.add(new JScrollPane(table), BorderLayout.CENTER);
|
||||||
main.add(btnPanel, BorderLayout.SOUTH);
|
main.add(btnPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
@@ -987,6 +1219,36 @@ public class SettingsDialog extends JDialog {
|
|||||||
if (pasteStopAtProtectedCheck != null) {
|
if (pasteStopAtProtectedCheck != null) {
|
||||||
Settings.setPasteStopAtProtectedLine(pasteStopAtProtectedCheck.isSelected());
|
Settings.setPasteStopAtProtectedLine(pasteStopAtProtectedCheck.isSelected());
|
||||||
}
|
}
|
||||||
|
if (pasteWordBreakCheck != null) {
|
||||||
|
Settings.setPasteWordBreak(pasteWordBreakCheck.isSelected());
|
||||||
|
}
|
||||||
|
if (trimRectSizingHandlesCheck != null) {
|
||||||
|
Settings.setTrimRectSizingHandles(trimRectSizingHandlesCheck.isSelected());
|
||||||
|
}
|
||||||
|
if (trimRectRemainAfterEditCheck != null) {
|
||||||
|
Settings.setTrimRectRemainAfterEdit(trimRectRemainAfterEditCheck.isSelected());
|
||||||
|
}
|
||||||
|
if (hotspotsEnabledCheck != null) {
|
||||||
|
Settings.setHotspotsEnabled(hotspotsEnabledCheck.isSelected());
|
||||||
|
}
|
||||||
|
if (hotspotsUrlCheck != null) {
|
||||||
|
Settings.setHotspotsUrlEnabled(hotspotsUrlCheck.isSelected());
|
||||||
|
}
|
||||||
|
if (hotspotsPfCheck != null) {
|
||||||
|
Settings.setHotspotsPfEnabled(hotspotsPfCheck.isSelected());
|
||||||
|
}
|
||||||
|
if (hotspotsMenuCheck != null) {
|
||||||
|
Settings.setHotspotsMenuChoicesEnabled(hotspotsMenuCheck.isSelected());
|
||||||
|
}
|
||||||
|
if (mouseWheelEnabledCheck != null) {
|
||||||
|
Settings.setMouseWheelEnabled(mouseWheelEnabledCheck.isSelected());
|
||||||
|
}
|
||||||
|
if (mouseWheelUpField != null) {
|
||||||
|
Settings.setMouseWheelUpKey(mouseWheelUpField.getText().trim());
|
||||||
|
}
|
||||||
|
if (mouseWheelDownField != null) {
|
||||||
|
Settings.setMouseWheelDownKey(mouseWheelDownField.getText().trim());
|
||||||
|
}
|
||||||
|
|
||||||
// Apply Connection
|
// Apply Connection
|
||||||
if (startupBehaviorBox != null) {
|
if (startupBehaviorBox != null) {
|
||||||
@@ -994,11 +1256,32 @@ public class SettingsDialog extends JDialog {
|
|||||||
if (behavior != null) {
|
if (behavior != null) {
|
||||||
Settings.setStartupBehavior(behavior);
|
Settings.setStartupBehavior(behavior);
|
||||||
}
|
}
|
||||||
if (behavior == Settings.StartupBehavior.AUTO_CONNECT && hostField != null && portField != null) {
|
if (behavior == Settings.StartupBehavior.AUTO_CONNECT && savedHostBox != null) {
|
||||||
Settings.setAutoConnectHost(hostField.getText().trim());
|
Object sel = savedHostBox.getSelectedItem();
|
||||||
try {
|
if (sel instanceof SavedHost) {
|
||||||
Settings.setAutoConnectPort(Integer.parseInt(portField.getText().trim()));
|
SavedHost selected = (SavedHost) sel;
|
||||||
} catch (NumberFormatException ignored) {
|
List<SavedHost> hosts = HostStorage.getSavedHosts();
|
||||||
|
for (SavedHost h : hosts) {
|
||||||
|
h.setAutoConnect(Objects.equals(h.getId(), selected.getId()));
|
||||||
|
}
|
||||||
|
HostStorage.saveAll(hosts);
|
||||||
|
Settings.setAutoConnectHost(selected.getHost());
|
||||||
|
Settings.setAutoConnectPort(selected.getPort());
|
||||||
|
Settings.setAutoConnectTls(selected.isUseTls());
|
||||||
|
Settings.setAutoConnectVerifyCert(selected.isTlsVerifyCert());
|
||||||
|
Settings.setAutoConnectTn3270e(selected.isTn3270e());
|
||||||
|
}
|
||||||
|
} else if (behavior != Settings.StartupBehavior.AUTO_CONNECT) {
|
||||||
|
List<SavedHost> hosts = HostStorage.getSavedHosts();
|
||||||
|
boolean modified = false;
|
||||||
|
for (SavedHost h : hosts) {
|
||||||
|
if (h.isAutoConnect()) {
|
||||||
|
h.setAutoConnect(false);
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (modified) {
|
||||||
|
HostStorage.saveAll(hosts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1133,6 +1416,43 @@ public class SettingsDialog extends JDialog {
|
|||||||
if (pasteStopAtProtectedCheck != null) {
|
if (pasteStopAtProtectedCheck != null) {
|
||||||
pasteStopAtProtectedCheck.setSelected(Settings.getPasteStopAtProtectedLine());
|
pasteStopAtProtectedCheck.setSelected(Settings.getPasteStopAtProtectedLine());
|
||||||
}
|
}
|
||||||
|
if (pasteWordBreakCheck != null) {
|
||||||
|
pasteWordBreakCheck.setSelected(Settings.getPasteWordBreak());
|
||||||
|
}
|
||||||
|
if (trimRectSizingHandlesCheck != null) {
|
||||||
|
trimRectSizingHandlesCheck.setSelected(Settings.getTrimRectSizingHandles());
|
||||||
|
}
|
||||||
|
if (trimRectRemainAfterEditCheck != null) {
|
||||||
|
trimRectRemainAfterEditCheck.setSelected(Settings.getTrimRectRemainAfterEdit());
|
||||||
|
}
|
||||||
|
if (hotspotsEnabledCheck != null) {
|
||||||
|
hotspotsEnabledCheck.setSelected(Settings.getHotspotsEnabled());
|
||||||
|
boolean en = Settings.getHotspotsEnabled();
|
||||||
|
if (hotspotsUrlCheck != null) {
|
||||||
|
hotspotsUrlCheck.setSelected(Settings.getHotspotsUrlEnabled());
|
||||||
|
hotspotsUrlCheck.setEnabled(en);
|
||||||
|
}
|
||||||
|
if (hotspotsPfCheck != null) {
|
||||||
|
hotspotsPfCheck.setSelected(Settings.getHotspotsPfEnabled());
|
||||||
|
hotspotsPfCheck.setEnabled(en);
|
||||||
|
}
|
||||||
|
if (hotspotsMenuCheck != null) {
|
||||||
|
hotspotsMenuCheck.setSelected(Settings.getHotspotsMenuChoicesEnabled());
|
||||||
|
hotspotsMenuCheck.setEnabled(en);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mouseWheelEnabledCheck != null) {
|
||||||
|
boolean mwEn = Settings.getMouseWheelEnabled();
|
||||||
|
mouseWheelEnabledCheck.setSelected(mwEn);
|
||||||
|
if (mouseWheelUpField != null) {
|
||||||
|
mouseWheelUpField.setText(Settings.getMouseWheelUpKey());
|
||||||
|
mouseWheelUpField.setEnabled(mwEn);
|
||||||
|
}
|
||||||
|
if (mouseWheelDownField != null) {
|
||||||
|
mouseWheelDownField.setText(Settings.getMouseWheelDownKey());
|
||||||
|
mouseWheelDownField.setEnabled(mwEn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Connection
|
// Connection
|
||||||
if (startupBehaviorBox != null) {
|
if (startupBehaviorBox != null) {
|
||||||
@@ -1141,12 +1461,7 @@ public class SettingsDialog extends JDialog {
|
|||||||
autoConnectPanel.setVisible(startupBehaviorBox.getSelectedItem() == Settings.StartupBehavior.AUTO_CONNECT);
|
autoConnectPanel.setVisible(startupBehaviorBox.getSelectedItem() == Settings.StartupBehavior.AUTO_CONNECT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hostField != null) {
|
refreshSavedHostsBox();
|
||||||
hostField.setText(Settings.getAutoConnectHost());
|
|
||||||
}
|
|
||||||
if (portField != null) {
|
|
||||||
portField.setText(String.valueOf(Settings.getAutoConnectPort()));
|
|
||||||
}
|
|
||||||
if (autoReconnectCheck != null) {
|
if (autoReconnectCheck != null) {
|
||||||
autoReconnectCheck.setSelected(Settings.getAutoConnectAutoReconnect());
|
autoReconnectCheck.setSelected(Settings.getAutoConnectAutoReconnect());
|
||||||
}
|
}
|
||||||
@@ -1226,7 +1541,7 @@ public class SettingsDialog extends JDialog {
|
|||||||
|
|
||||||
// ========== Import Config ==========
|
// ========== Import Config ==========
|
||||||
|
|
||||||
void importConfigFile(File file) throws IOException {
|
public void importConfigFile(File file) throws IOException {
|
||||||
if (file == null || !file.exists()) {
|
if (file == null || !file.exists()) {
|
||||||
throw new FileNotFoundException("Config file not found: " + (file != null ? file.getAbsolutePath() : "null"));
|
throw new FileNotFoundException("Config file not found: " + (file != null ? file.getAbsolutePath() : "null"));
|
||||||
}
|
}
|
||||||
@@ -1264,6 +1579,102 @@ public class SettingsDialog extends JDialog {
|
|||||||
return btnExport;
|
return btnExport;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JButton getBtnImportConnections() {
|
||||||
|
return btnImportConnections;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JButton getBtnExportConnections() {
|
||||||
|
return btnExportConnections;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JComboBox<SavedHost> getSavedHostBox() {
|
||||||
|
return savedHostBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Import / Export Connections ==========
|
||||||
|
|
||||||
|
public void importConnectionsFile(File file) throws IOException {
|
||||||
|
if (file == null || !file.exists()) {
|
||||||
|
throw new FileNotFoundException("Connections file not found: " + (file != null ? file.getAbsolutePath() : "null"));
|
||||||
|
}
|
||||||
|
HostStorage.importFromIniFile(file);
|
||||||
|
refreshSavedHostsBox();
|
||||||
|
SavedHost autoHost = HostStorage.getAutoConnectHost();
|
||||||
|
if (autoHost != null) {
|
||||||
|
Settings.setAutoConnectHost(autoHost.getHost());
|
||||||
|
Settings.setAutoConnectPort(autoHost.getPort());
|
||||||
|
Settings.setAutoConnectTls(autoHost.isUseTls());
|
||||||
|
Settings.setAutoConnectVerifyCert(autoHost.isTlsVerifyCert());
|
||||||
|
Settings.setAutoConnectTn3270e(autoHost.isTn3270e());
|
||||||
|
}
|
||||||
|
refreshFromSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void exportConnectionsFile(File file) throws IOException {
|
||||||
|
if (file == null) {
|
||||||
|
throw new IllegalArgumentException("Target file cannot be null");
|
||||||
|
}
|
||||||
|
HostStorage.exportToIniFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void importConnections() {
|
||||||
|
JFileChooser fc = new JFileChooser();
|
||||||
|
fc.setDialogTitle("Import Connections");
|
||||||
|
fc.setSelectedFile(new java.io.File("connections.ini"));
|
||||||
|
fc.setFileFilter(new javax.swing.filechooser.FileNameExtensionFilter("INI Files (*.ini)", "ini"));
|
||||||
|
|
||||||
|
int result = fc.showOpenDialog(this);
|
||||||
|
if (result == JFileChooser.APPROVE_OPTION) {
|
||||||
|
java.io.File file = fc.getSelectedFile();
|
||||||
|
try {
|
||||||
|
importConnectionsFile(file);
|
||||||
|
int count = HostStorage.getSavedHosts().size();
|
||||||
|
JOptionPane.showMessageDialog(this,
|
||||||
|
"Connections imported successfully from:\n" + file.getAbsolutePath() + "\nTotal saved connections: " + count,
|
||||||
|
"Import Successful", JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
JOptionPane.showMessageDialog(this,
|
||||||
|
"Import connections failed: " + ex.getMessage(),
|
||||||
|
"Import Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exportConnections() {
|
||||||
|
JFileChooser fc = new JFileChooser();
|
||||||
|
fc.setDialogTitle("Export Connections");
|
||||||
|
fc.setSelectedFile(new java.io.File("connections.ini"));
|
||||||
|
fc.setFileFilter(new javax.swing.filechooser.FileNameExtensionFilter("INI Files (*.ini)", "ini"));
|
||||||
|
|
||||||
|
int result = fc.showSaveDialog(this);
|
||||||
|
if (result == JFileChooser.APPROVE_OPTION) {
|
||||||
|
java.io.File file = fc.getSelectedFile();
|
||||||
|
// Ensure .ini extension
|
||||||
|
if (!file.getName().toLowerCase().endsWith(".ini")) {
|
||||||
|
file = new java.io.File(file.getAbsolutePath() + ".ini");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirm overwrite
|
||||||
|
if (file.exists()) {
|
||||||
|
int confirm = JOptionPane.showConfirmDialog(this,
|
||||||
|
"File already exists. Overwrite?", "Confirm Overwrite",
|
||||||
|
JOptionPane.YES_NO_OPTION);
|
||||||
|
if (confirm != JOptionPane.YES_OPTION) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
exportConnectionsFile(file);
|
||||||
|
JOptionPane.showMessageDialog(this,
|
||||||
|
"Connections exported to:\n" + file.getAbsolutePath(),
|
||||||
|
"Export Successful", JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
JOptionPane.showMessageDialog(this,
|
||||||
|
"Export connections failed: " + ex.getMessage(),
|
||||||
|
"Export Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ========== Export Config ==========
|
// ========== Export Config ==========
|
||||||
|
|
||||||
private void exportConfig() {
|
private void exportConfig() {
|
||||||
@@ -1302,4 +1713,39 @@ public class SettingsDialog extends JDialog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class ScrollablePanel extends JPanel implements Scrollable {
|
||||||
|
public ScrollablePanel() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScrollablePanel(LayoutManager layout) {
|
||||||
|
super(layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Dimension getPreferredScrollableViewportSize() {
|
||||||
|
return getPreferredSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
|
||||||
|
return 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
|
||||||
|
return 48;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getScrollableTracksViewportWidth() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getScrollableTracksViewportHeight() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ public class StatusBar extends JPanel {
|
|||||||
|
|
||||||
private Telnet3270Client client;
|
private Telnet3270Client client;
|
||||||
private TerminalPanel terminalPanel;
|
private TerminalPanel terminalPanel;
|
||||||
|
private haus.nightmare.lib3270j.ecl.ECLOIANotify oiaNotifyListener;
|
||||||
|
|
||||||
public StatusBar() {
|
public StatusBar() {
|
||||||
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
|
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
|
||||||
@@ -131,8 +132,15 @@ public class StatusBar extends JPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setClient(Telnet3270Client client, TerminalPanel terminalPanel) {
|
public void setClient(Telnet3270Client client, TerminalPanel terminalPanel) {
|
||||||
|
if (this.client != null && this.client.getOIA() != null && this.oiaNotifyListener != null) {
|
||||||
|
this.client.getOIA().unregisterOIAEvent(this.oiaNotifyListener);
|
||||||
|
}
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.terminalPanel = terminalPanel;
|
this.terminalPanel = terminalPanel;
|
||||||
|
if (this.client != null && this.client.getOIA() != null) {
|
||||||
|
this.oiaNotifyListener = event -> updateStatus();
|
||||||
|
this.client.getOIA().registerOIAEvent(this.oiaNotifyListener);
|
||||||
|
}
|
||||||
updateStatus();
|
updateStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,9 +357,12 @@ public class StatusBar extends JPanel {
|
|||||||
wordWrapStatus.setText("");
|
wordWrapStatus.setText("");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Field status: Numeric vs Alphanumeric
|
// Field status: Numeric vs Alphanumeric vs DBCS
|
||||||
if (state.isFullSession() && client.getScreenBuffer().isFormatted()) {
|
if (state.isFullSession() && client.getScreenBuffer().isFormatted()) {
|
||||||
if (client.getOIA().isNumeric()) {
|
if (client.getOIA().isDBCS()) {
|
||||||
|
fieldTypeStatus.setText("DBCS");
|
||||||
|
fieldTypeStatus.setForeground(ThemeManager.getOiaStatusSysAvail(theme));
|
||||||
|
} else if (client.getOIA().isNumeric()) {
|
||||||
fieldTypeStatus.setText("NUM");
|
fieldTypeStatus.setText("NUM");
|
||||||
fieldTypeStatus.setForeground(ThemeManager.getOiaFgWarn(theme));
|
fieldTypeStatus.setForeground(ThemeManager.getOiaFgWarn(theme));
|
||||||
} else {
|
} else {
|
||||||
@@ -388,4 +399,8 @@ public class StatusBar extends JPanel {
|
|||||||
cursorPosition.setText(String.format("%03d/%03d [%04d]", row, col, curAddr));
|
cursorPosition.setText(String.format("%03d/%03d [%04d]", row, col, curAddr));
|
||||||
cursorPosition.setForeground(ThemeManager.getOiaFgNormal(theme));
|
cursorPosition.setForeground(ThemeManager.getOiaFgNormal(theme));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getLockStatusText() {
|
||||||
|
return lockStatus != null ? lockStatus.getText() : "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,297 @@
|
|||||||
|
package haus.nightmare.j3270;
|
||||||
|
|
||||||
|
import haus.nightmare.j3270.ui.ConnectChoiceDialog;
|
||||||
|
import haus.nightmare.j3270.ui.ThemeManager;
|
||||||
|
import haus.nightmare.lib3270j.ConnectionConfig;
|
||||||
|
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||||
|
import haus.nightmare.lib3270j.TerminalModel;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class ConnectionConflictTest {
|
||||||
|
|
||||||
|
private final List<J3270App> createdApps = new ArrayList<>();
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() {
|
||||||
|
for (J3270App app : new ArrayList<>(J3270App.getOpenWindows())) {
|
||||||
|
try {
|
||||||
|
app.dispose();
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void tearDown() {
|
||||||
|
for (J3270App app : createdApps) {
|
||||||
|
try {
|
||||||
|
app.dispose();
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
for (J3270App app : new ArrayList<>(J3270App.getOpenWindows())) {
|
||||||
|
try {
|
||||||
|
app.dispose();
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private J3270App createApp() {
|
||||||
|
try {
|
||||||
|
J3270App app = new J3270App();
|
||||||
|
createdApps.add(app);
|
||||||
|
return app;
|
||||||
|
} catch (HeadlessException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("ConnectChoiceDialog components, styling, and action results")
|
||||||
|
public void testConnectChoiceDialogComponents() {
|
||||||
|
try {
|
||||||
|
ConnectChoiceDialog dialog = new ConnectChoiceDialog(null, "mvs.corp.local:23");
|
||||||
|
assertEquals("Active Connection", dialog.getTitle());
|
||||||
|
assertNotNull(dialog.getNewWindowButton());
|
||||||
|
assertNotNull(dialog.getAbortButton());
|
||||||
|
assertNotNull(dialog.getCancelButton());
|
||||||
|
assertEquals("New Window", dialog.getNewWindowButton().getText());
|
||||||
|
assertEquals("Abort Current Connection", dialog.getAbortButton().getText());
|
||||||
|
assertEquals("Cancel", dialog.getCancelButton().getText());
|
||||||
|
|
||||||
|
assertTrue(dialog.getLine1().getText().contains("mvs.corp.local:23"));
|
||||||
|
assertTrue(dialog.getLine2().getText().contains("new window"));
|
||||||
|
assertTrue(dialog.getLine2().getText().contains("abort"));
|
||||||
|
|
||||||
|
// Test button click choices
|
||||||
|
dialog.getNewWindowButton().doClick();
|
||||||
|
assertEquals(ConnectChoiceDialog.Choice.NEW_WINDOW, dialog.getChoice());
|
||||||
|
|
||||||
|
ConnectChoiceDialog dialog2 = new ConnectChoiceDialog(null, "cms.test:992");
|
||||||
|
dialog2.getAbortButton().doClick();
|
||||||
|
assertEquals(ConnectChoiceDialog.Choice.ABORT_CURRENT, dialog2.getChoice());
|
||||||
|
|
||||||
|
ConnectChoiceDialog dialog3 = new ConnectChoiceDialog(null, "cics.test:23");
|
||||||
|
dialog3.getCancelButton().doClick();
|
||||||
|
assertEquals(ConnectChoiceDialog.Choice.CANCEL, dialog3.getChoice());
|
||||||
|
} catch (HeadlessException ignored) {
|
||||||
|
// Expected in headless CI environments without X11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("When connected, Connect with NEW_WINDOW choice opens in a new window and preserves current connection")
|
||||||
|
public void testConnectChoiceNewWindow() {
|
||||||
|
J3270App app = createApp();
|
||||||
|
if (app == null) return;
|
||||||
|
|
||||||
|
ConnectionConfig config1 = new ConnectionConfig("host1.test", 23, TerminalModel.IBM_3278_2);
|
||||||
|
config1.setSavedProfileName("Host 1");
|
||||||
|
app.connect(config1, false);
|
||||||
|
|
||||||
|
assertTrue(app.getConnectedTarget().contains("host1.test") || app.getConnectedTarget().contains("Host 1"));
|
||||||
|
|
||||||
|
// Configure handler to return NEW_WINDOW
|
||||||
|
boolean[] handlerCalled = new boolean[]{false};
|
||||||
|
app.setConnectChoiceHandler((owner, target) -> {
|
||||||
|
handlerCalled[0] = true;
|
||||||
|
assertTrue(target.contains("host1.test") || target.contains("Host 1"));
|
||||||
|
return ConnectChoiceDialog.Choice.NEW_WINDOW;
|
||||||
|
});
|
||||||
|
|
||||||
|
int initialWindows = J3270App.getOpenWindows().size();
|
||||||
|
|
||||||
|
ConnectionConfig config2 = new ConnectionConfig("host2.test", 23, TerminalModel.IBM_3278_2);
|
||||||
|
config2.setSavedProfileName("Host 2");
|
||||||
|
app.connect(config2, true);
|
||||||
|
|
||||||
|
// Handler was invoked
|
||||||
|
assertTrue(handlerCalled[0], "Connect choice handler must be called when already connected");
|
||||||
|
|
||||||
|
// A new window was opened
|
||||||
|
assertEquals(initialWindows + 1, J3270App.getOpenWindows().size(),
|
||||||
|
"A new window should have been added to openWindows");
|
||||||
|
|
||||||
|
// Original app remains on config1
|
||||||
|
assertEquals("host1.test", app.getClient().getConfig().getHost());
|
||||||
|
|
||||||
|
// Find the new window
|
||||||
|
J3270App newWindow = null;
|
||||||
|
for (J3270App w : J3270App.getOpenWindows()) {
|
||||||
|
if (w != app) {
|
||||||
|
newWindow = w;
|
||||||
|
createdApps.add(w);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertNotNull(newWindow, "New window must exist");
|
||||||
|
assertNotNull(newWindow.getClient(), "New window should have a client");
|
||||||
|
assertEquals("host2.test", newWindow.getClient().getConfig().getHost(),
|
||||||
|
"New window must be connected to host2.test");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("When connected, Connect with ABORT_CURRENT choice aborts existing connection and connects in same window")
|
||||||
|
public void testConnectChoiceAbortCurrent() {
|
||||||
|
J3270App app = createApp();
|
||||||
|
if (app == null) return;
|
||||||
|
|
||||||
|
ConnectionConfig config1 = new ConnectionConfig("host1.test", 23, TerminalModel.IBM_3278_2);
|
||||||
|
app.connect(config1, false);
|
||||||
|
|
||||||
|
int initialWindows = J3270App.getOpenWindows().size();
|
||||||
|
|
||||||
|
boolean[] handlerCalled = new boolean[]{false};
|
||||||
|
app.setConnectChoiceHandler((owner, target) -> {
|
||||||
|
handlerCalled[0] = true;
|
||||||
|
return ConnectChoiceDialog.Choice.ABORT_CURRENT;
|
||||||
|
});
|
||||||
|
|
||||||
|
ConnectionConfig config2 = new ConnectionConfig("host2.test", 23, TerminalModel.IBM_3278_2);
|
||||||
|
app.connect(config2, true);
|
||||||
|
|
||||||
|
assertTrue(handlerCalled[0], "Connect choice handler must be called");
|
||||||
|
assertEquals(initialWindows, J3270App.getOpenWindows().size(),
|
||||||
|
"No new window should be created when choosing ABORT_CURRENT");
|
||||||
|
assertEquals("host2.test", app.getClient().getConfig().getHost(),
|
||||||
|
"Same window should now be connected to host2.test");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("When connected, Connect with CANCEL choice keeps existing connection and creates no new window")
|
||||||
|
public void testConnectChoiceCancel() {
|
||||||
|
J3270App app = createApp();
|
||||||
|
if (app == null) return;
|
||||||
|
|
||||||
|
ConnectionConfig config1 = new ConnectionConfig("host1.test", 23, TerminalModel.IBM_3278_2);
|
||||||
|
app.connect(config1, false);
|
||||||
|
|
||||||
|
int initialWindows = J3270App.getOpenWindows().size();
|
||||||
|
|
||||||
|
boolean[] handlerCalled = new boolean[]{false};
|
||||||
|
app.setConnectChoiceHandler((owner, target) -> {
|
||||||
|
handlerCalled[0] = true;
|
||||||
|
return ConnectChoiceDialog.Choice.CANCEL;
|
||||||
|
});
|
||||||
|
|
||||||
|
ConnectionConfig config2 = new ConnectionConfig("host2.test", 23, TerminalModel.IBM_3278_2);
|
||||||
|
app.connect(config2, true);
|
||||||
|
|
||||||
|
assertTrue(handlerCalled[0], "Connect choice handler must be called");
|
||||||
|
assertEquals(initialWindows, J3270App.getOpenWindows().size(),
|
||||||
|
"No new window should be created when choosing CANCEL");
|
||||||
|
assertEquals("host1.test", app.getClient().getConfig().getHost(),
|
||||||
|
"Existing window must remain connected to host1.test");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("File menu contains Close Window item with VK_W accelerator")
|
||||||
|
public void testCloseWindowMenuItem() {
|
||||||
|
J3270App app = createApp();
|
||||||
|
if (app == null) return;
|
||||||
|
|
||||||
|
JMenuBar mb = app.getJMenuBar();
|
||||||
|
assertNotNull(mb);
|
||||||
|
JMenu fileMenu = null;
|
||||||
|
for (int i = 0; i < mb.getMenuCount(); i++) {
|
||||||
|
if ("File".equals(mb.getMenu(i).getText())) {
|
||||||
|
fileMenu = mb.getMenu(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertNotNull(fileMenu, "File menu must exist");
|
||||||
|
|
||||||
|
JMenuItem closeWindowItem = null;
|
||||||
|
for (int i = 0; i < fileMenu.getItemCount(); i++) {
|
||||||
|
JMenuItem item = fileMenu.getItem(i);
|
||||||
|
if (item != null && "Close Window".equals(item.getText())) {
|
||||||
|
closeWindowItem = item;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertNotNull(closeWindowItem, "File menu must contain Close Window item");
|
||||||
|
KeyStroke ks = closeWindowItem.getAccelerator();
|
||||||
|
assertNotNull(ks, "Close Window item must have an accelerator");
|
||||||
|
assertEquals(KeyEvent.VK_W, ks.getKeyCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Closing one window when multiple windows are open leaves remaining window open")
|
||||||
|
public void testMultiWindowCloseIndependent() {
|
||||||
|
J3270App app1 = createApp();
|
||||||
|
if (app1 == null) return;
|
||||||
|
|
||||||
|
J3270App app2 = createApp();
|
||||||
|
if (app2 == null) return;
|
||||||
|
|
||||||
|
assertTrue(J3270App.getOpenWindows().contains(app1));
|
||||||
|
assertTrue(J3270App.getOpenWindows().contains(app2));
|
||||||
|
assertEquals(2, J3270App.getOpenWindows().size());
|
||||||
|
|
||||||
|
// Close app1
|
||||||
|
app1.closeWindow();
|
||||||
|
|
||||||
|
assertFalse(J3270App.getOpenWindows().contains(app1), "app1 should be removed from open windows");
|
||||||
|
assertTrue(J3270App.getOpenWindows().contains(app2), "app2 should remain open");
|
||||||
|
assertEquals(1, J3270App.getOpenWindows().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Abort current connection cleanly tears down old client and connects new client in same window")
|
||||||
|
public void testAbortCurrentConnectionGracefullyDisconnects() throws Exception {
|
||||||
|
J3270App app = createApp();
|
||||||
|
if (app == null) return;
|
||||||
|
|
||||||
|
ConnectionConfig config1 = new ConnectionConfig("host1.test", 23, TerminalModel.IBM_3278_2);
|
||||||
|
app.connect(config1, false);
|
||||||
|
|
||||||
|
Telnet3270Client oldClient = app.getClient();
|
||||||
|
assertNotNull(oldClient);
|
||||||
|
|
||||||
|
app.setConnectChoiceHandler((owner, target) -> ConnectChoiceDialog.Choice.ABORT_CURRENT);
|
||||||
|
|
||||||
|
ConnectionConfig config2 = new ConnectionConfig("host2.test", 23, TerminalModel.IBM_3278_2);
|
||||||
|
app.connect(config2, true);
|
||||||
|
|
||||||
|
// Client on app is immediately replaced with new client
|
||||||
|
Telnet3270Client newClient = app.getClient();
|
||||||
|
assertNotNull(newClient);
|
||||||
|
assertNotSame(oldClient, newClient);
|
||||||
|
assertEquals("host2.test", newClient.getConfig().getHost());
|
||||||
|
|
||||||
|
// Old client should be disconnected
|
||||||
|
Thread.sleep(100);
|
||||||
|
assertEquals(haus.nightmare.lib3270j.ConnectionState.NOT_CONNECTED, oldClient.getConnectionState());
|
||||||
|
assertFalse(oldClient.isConnected());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Explicit app.disconnect() tears down client and clears UI state")
|
||||||
|
public void testExplicitDisconnectResetsUI() throws Exception {
|
||||||
|
J3270App app = createApp();
|
||||||
|
if (app == null) return;
|
||||||
|
|
||||||
|
ConnectionConfig config = new ConnectionConfig("host1.test", 23, TerminalModel.IBM_3278_2);
|
||||||
|
app.connect(config, false);
|
||||||
|
|
||||||
|
Telnet3270Client client = app.getClient();
|
||||||
|
assertNotNull(client);
|
||||||
|
|
||||||
|
app.disconnect();
|
||||||
|
|
||||||
|
assertNull(app.getClient());
|
||||||
|
assertFalse(app.isConnected());
|
||||||
|
Thread.sleep(100);
|
||||||
|
assertEquals(haus.nightmare.lib3270j.ConnectionState.NOT_CONNECTED, client.getConnectionState());
|
||||||
|
assertFalse(client.isConnected());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -85,7 +85,8 @@ public class LoggingConfigurationTest {
|
|||||||
assertEquals(Level.ALL, Logger.getLogger("haus.nightmare.lib3270j").getLevel());
|
assertEquals(Level.ALL, Logger.getLogger("haus.nightmare.lib3270j").getLevel());
|
||||||
|
|
||||||
File logFile = new File("j3270.log");
|
File logFile = new File("j3270.log");
|
||||||
assertTrue(logFile.exists(), "j3270.log should be created when debug is enabled");
|
File logFile1 = new File("j3270.log.1");
|
||||||
|
assertTrue(logFile.exists() || logFile1.exists(), "j3270.log should be created when debug is enabled");
|
||||||
|
|
||||||
// Clean up
|
// Clean up
|
||||||
J3270App.configureLogging(false);
|
J3270App.configureLogging(false);
|
||||||
|
|||||||
@@ -0,0 +1,271 @@
|
|||||||
|
package haus.nightmare.j3270.ft;
|
||||||
|
|
||||||
|
import haus.nightmare.j3270.J3270App;
|
||||||
|
import haus.nightmare.j3270.config.Settings;
|
||||||
|
import haus.nightmare.j3270.storage.HostStorage;
|
||||||
|
import haus.nightmare.j3270.storage.SavedHost;
|
||||||
|
import haus.nightmare.j3270.ui.ConnectDialog;
|
||||||
|
import haus.nightmare.lib3270j.ConnectionConfig;
|
||||||
|
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||||
|
import haus.nightmare.lib3270j.ft.FTConfig;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.Frame;
|
||||||
|
import java.awt.HeadlessException;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class ConnectionHostTypeConfigTest {
|
||||||
|
|
||||||
|
private List<SavedHost> originalSavedHosts;
|
||||||
|
private String originalDefaultHostType;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() {
|
||||||
|
FileTransferDialog.resetSessionState();
|
||||||
|
originalSavedHosts = HostStorage.getSavedHosts();
|
||||||
|
originalDefaultHostType = Settings.getDefaultHostType();
|
||||||
|
HostStorage.clearAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void tearDown() {
|
||||||
|
FileTransferDialog.resetSessionState();
|
||||||
|
HostStorage.clearAll();
|
||||||
|
if (originalSavedHosts != null) {
|
||||||
|
HostStorage.saveAll(originalSavedHosts);
|
||||||
|
}
|
||||||
|
Settings.setDefaultHostType(originalDefaultHostType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("SavedHost converts to ConnectionConfig preserving hostType and saved connection metadata")
|
||||||
|
public void testSavedHostToConnectionConfigPreservesHostType() {
|
||||||
|
SavedHost cmsHost = new SavedHost(
|
||||||
|
"cms-uuid-1", "VM/CMS System", "vm370.corp.local", 23, 4, 62, 160, "",
|
||||||
|
false, "CMS", false, true, true, "BOTH", "037",
|
||||||
|
"NONE", "", 0, "", ""
|
||||||
|
);
|
||||||
|
|
||||||
|
ConnectionConfig cfgCms = cmsHost.toConnectionConfig();
|
||||||
|
assertTrue(cfgCms.isSavedConnection(), "Config from SavedHost must be marked as saved connection");
|
||||||
|
assertEquals("cms-uuid-1", cfgCms.getSavedProfileId());
|
||||||
|
assertEquals("VM/CMS System", cfgCms.getSavedProfileName());
|
||||||
|
assertEquals("CMS", cfgCms.getHostType());
|
||||||
|
assertEquals(FTConfig.HostType.CMS, cfgCms.getFtHostType());
|
||||||
|
|
||||||
|
SavedHost cicsHost = new SavedHost(
|
||||||
|
"cics-uuid-2", "CICS Gateway", "cics.corp.local", 23, 2, 24, 80, "LU1",
|
||||||
|
false, "CICS", false, true, true, "BOTH", "037",
|
||||||
|
"NONE", "", 0, "", ""
|
||||||
|
);
|
||||||
|
|
||||||
|
ConnectionConfig cfgCics = cicsHost.toConnectionConfig();
|
||||||
|
assertTrue(cfgCics.isSavedConnection());
|
||||||
|
assertEquals("CICS", cfgCics.getHostType());
|
||||||
|
assertEquals(FTConfig.HostType.CICS, cfgCics.getFtHostType());
|
||||||
|
|
||||||
|
SavedHost tsoHost = new SavedHost(
|
||||||
|
"tso-uuid-3", "MVS TSO", "tso.corp.local", 23, 4, 43, 80, "",
|
||||||
|
false, "TSO", false, true, true, "BOTH", "037",
|
||||||
|
"NONE", "", 0, "", ""
|
||||||
|
);
|
||||||
|
|
||||||
|
ConnectionConfig cfgTso = tsoHost.toConnectionConfig();
|
||||||
|
assertTrue(cfgTso.isSavedConnection());
|
||||||
|
assertEquals("TSO", cfgTso.getHostType());
|
||||||
|
assertEquals(FTConfig.HostType.TSO, cfgTso.getFtHostType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("FileTransferDialog preselects CMS when using a saved connection configured as CMS")
|
||||||
|
public void testFileTransferDialogPrefillsFromSavedConnectionCms() {
|
||||||
|
ConnectionConfig config = new ConnectionConfig("vm.example.com", 23);
|
||||||
|
config.setSavedConnection(true);
|
||||||
|
config.setSavedProfileId("vm-1");
|
||||||
|
config.setSavedProfileName("VM370");
|
||||||
|
config.setHostType("CMS");
|
||||||
|
|
||||||
|
Telnet3270Client client = new Telnet3270Client(config);
|
||||||
|
FileTransfer ft = new FileTransfer(client, null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
FileTransferDialog dialog = new FileTransferDialog(null, ft);
|
||||||
|
assertEquals(FTConfig.HostType.CMS, dialog.getHostTypeCombo().getSelectedItem(),
|
||||||
|
"FileTransferDialog must preselect CMS when saved connection is configured as CMS");
|
||||||
|
dialog.dispose();
|
||||||
|
} catch (HeadlessException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("FileTransferDialog preselects CICS when using a saved connection configured as CICS")
|
||||||
|
public void testFileTransferDialogPrefillsFromSavedConnectionCics() {
|
||||||
|
ConnectionConfig config = new ConnectionConfig("cics.example.com", 23);
|
||||||
|
config.setSavedConnection(true);
|
||||||
|
config.setSavedProfileId("cics-1");
|
||||||
|
config.setSavedProfileName("CICS Region");
|
||||||
|
config.setHostType("CICS");
|
||||||
|
|
||||||
|
Telnet3270Client client = new Telnet3270Client(config);
|
||||||
|
FileTransfer ft = new FileTransfer(client, null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
FileTransferDialog dialog = new FileTransferDialog(null, ft);
|
||||||
|
assertEquals(FTConfig.HostType.CICS, dialog.getHostTypeCombo().getSelectedItem(),
|
||||||
|
"FileTransferDialog must preselect CICS when saved connection is configured as CICS");
|
||||||
|
dialog.dispose();
|
||||||
|
} catch (HeadlessException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("FileTransferDialog overrides stale prior transfer state with configured host type from saved connection")
|
||||||
|
public void testFileTransferDialogOverridesOldTransferStateWhenSavedConnectionUsed() {
|
||||||
|
// Simulate a prior transfer performed on a TSO system
|
||||||
|
FileTransferDialog.TransferSessionState oldState = new FileTransferDialog.TransferSessionState();
|
||||||
|
oldState.hostType = FTConfig.HostType.TSO;
|
||||||
|
oldState.localFile = "test.txt";
|
||||||
|
oldState.hostFile = "USER.DATA";
|
||||||
|
FileTransferDialog.setLastTransferState(oldState);
|
||||||
|
|
||||||
|
// Now connect to a CMS saved connection
|
||||||
|
ConnectionConfig cmsConfig = new ConnectionConfig("vm.corp.internal", 23);
|
||||||
|
cmsConfig.setSavedConnection(true);
|
||||||
|
cmsConfig.setSavedProfileName("VM Prod");
|
||||||
|
cmsConfig.setHostType("CMS");
|
||||||
|
|
||||||
|
Telnet3270Client client = new Telnet3270Client(cmsConfig);
|
||||||
|
FileTransfer ft = new FileTransfer(client, null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
FileTransferDialog dialog = new FileTransferDialog(null, ft);
|
||||||
|
assertEquals(FTConfig.HostType.CMS, dialog.getHostTypeCombo().getSelectedItem(),
|
||||||
|
"FileTransferDialog must use the configured host type (CMS) of the saved connection rather than prior session's TSO");
|
||||||
|
dialog.dispose();
|
||||||
|
} catch (HeadlessException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("ConnectDialog allows host type configuration and saves it to profile")
|
||||||
|
public void testConnectDialogHostTypeConfigurationAndProfile() {
|
||||||
|
try {
|
||||||
|
ConnectDialog dialog = new ConnectDialog((Frame) null);
|
||||||
|
assertNotNull(dialog.getHostTypeCombo(), "ConnectDialog must provide a Host Type combo box");
|
||||||
|
assertEquals("TSO", dialog.getSelectedHostType());
|
||||||
|
|
||||||
|
// Switch to CMS
|
||||||
|
dialog.setInitialHostType("CMS");
|
||||||
|
assertEquals("CMS", dialog.getSelectedHostType());
|
||||||
|
assertEquals(FTConfig.HostType.CMS, dialog.getSelectedFtHostType());
|
||||||
|
|
||||||
|
// Switch to CICS
|
||||||
|
dialog.setInitialHostType("CICS");
|
||||||
|
assertEquals("CICS", dialog.getSelectedHostType());
|
||||||
|
assertEquals(FTConfig.HostType.CICS, dialog.getSelectedFtHostType());
|
||||||
|
|
||||||
|
// Loading a SavedHost profile updates host type in ConnectDialog
|
||||||
|
SavedHost cmsProfile = new SavedHost(
|
||||||
|
"cms-prof-1", "CMS Dev", "cms.dev.local", 23, 4, 62, 160, "",
|
||||||
|
false, "CMS", false, true, true, "BOTH", "037",
|
||||||
|
"NONE", "", 0, "", ""
|
||||||
|
);
|
||||||
|
dialog.loadProfile(cmsProfile);
|
||||||
|
assertEquals("CMS", dialog.getSelectedHostType());
|
||||||
|
|
||||||
|
dialog.dispose();
|
||||||
|
} catch (HeadlessException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Settings persists and exports defaultHostType")
|
||||||
|
public void testSettingsDefaultHostTypePersistenceAndIni() throws Exception {
|
||||||
|
Settings.setDefaultHostType("CMS");
|
||||||
|
assertEquals("CMS", Settings.getDefaultHostType());
|
||||||
|
|
||||||
|
File tmpIni = File.createTempFile("j3270_test_cfg", ".ini");
|
||||||
|
tmpIni.deleteOnExit();
|
||||||
|
|
||||||
|
try (PrintWriter writer = new PrintWriter(tmpIni)) {
|
||||||
|
writer.println("[connection]");
|
||||||
|
writer.println("hostType = CICS");
|
||||||
|
}
|
||||||
|
|
||||||
|
Settings.loadFromIniFile(tmpIni.getAbsolutePath());
|
||||||
|
assertEquals("CICS", Settings.getDefaultHostType());
|
||||||
|
|
||||||
|
// Test export
|
||||||
|
File exportFile = File.createTempFile("j3270_export", ".ini");
|
||||||
|
exportFile.deleteOnExit();
|
||||||
|
Settings.exportToIniFile(exportFile.getAbsolutePath());
|
||||||
|
|
||||||
|
String content = new String(java.nio.file.Files.readAllBytes(exportFile.toPath()));
|
||||||
|
assertTrue(content.contains("hostType = CICS"), "Exported INI must contain hostType = CICS");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("J3270App setConnectionHostType updates connection config and syncs with profile in HostStorage")
|
||||||
|
public void testSetConnectionHostTypeUpdatesActiveConnectionAndStorage() {
|
||||||
|
SavedHost saved = new SavedHost(
|
||||||
|
"saved-prof-1", "Prod CMS", "vm.prod", 23, 4, 62, 160, "",
|
||||||
|
false, "TSO", false, true, true, "BOTH", "037",
|
||||||
|
"NONE", "", 0, "", ""
|
||||||
|
);
|
||||||
|
HostStorage.saveHost(saved);
|
||||||
|
|
||||||
|
ConnectionConfig config = saved.toConnectionConfig();
|
||||||
|
assertEquals("TSO", config.getHostType());
|
||||||
|
|
||||||
|
try {
|
||||||
|
J3270App app = new J3270App();
|
||||||
|
try {
|
||||||
|
// Verify Connection menu exists in JMenuBar
|
||||||
|
JMenuBar mb = app.getJMenuBar();
|
||||||
|
assertNotNull(mb);
|
||||||
|
boolean hasConnectionMenu = false;
|
||||||
|
for (int i = 0; i < mb.getMenuCount(); i++) {
|
||||||
|
JMenu m = mb.getMenu(i);
|
||||||
|
if (m != null && "Connection".equals(m.getText())) {
|
||||||
|
hasConnectionMenu = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(hasConnectionMenu, "JMenuBar must include a Connection menu");
|
||||||
|
|
||||||
|
// Simulate connecting with this config
|
||||||
|
app.connect(config);
|
||||||
|
|
||||||
|
// Change host type via setConnectionHostType
|
||||||
|
app.setConnectionHostType(FTConfig.HostType.CMS);
|
||||||
|
assertEquals(FTConfig.HostType.CMS, app.getClient().getConfig().getFtHostType());
|
||||||
|
|
||||||
|
// Profile in storage should also be updated
|
||||||
|
SavedHost reloaded = HostStorage.findById("saved-prof-1");
|
||||||
|
assertNotNull(reloaded);
|
||||||
|
assertEquals("CMS", reloaded.getHostType());
|
||||||
|
} finally {
|
||||||
|
app.dispose();
|
||||||
|
}
|
||||||
|
} catch (HeadlessException ignored) {
|
||||||
|
// In headless environments where JFrame cannot be initialized, test model updates directly
|
||||||
|
Telnet3270Client client = new Telnet3270Client(config);
|
||||||
|
client.getConfig().setFtHostType(FTConfig.HostType.CMS);
|
||||||
|
saved.setHostType(FTConfig.HostType.CMS.name());
|
||||||
|
HostStorage.saveHost(saved);
|
||||||
|
|
||||||
|
SavedHost reloaded = HostStorage.findById("saved-prof-1");
|
||||||
|
assertNotNull(reloaded);
|
||||||
|
assertEquals("CMS", reloaded.getHostType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
package haus.nightmare.j3270.ft;
|
package haus.nightmare.j3270.ft;
|
||||||
|
|
||||||
|
import haus.nightmare.j3270.ui.HostDirectoryDialog;
|
||||||
import haus.nightmare.lib3270j.ft.FTConfig;
|
import haus.nightmare.lib3270j.ft.FTConfig;
|
||||||
|
import haus.nightmare.lib3270j.ft.dir.CMSDirectoryEntry;
|
||||||
|
import haus.nightmare.lib3270j.ft.dir.TSODirectoryEntry;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@@ -99,4 +102,114 @@ public class FileTransferSessionAndCmsTest {
|
|||||||
// Headless environment; verified through state retention
|
// Headless environment; verified through state retention
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPdsSessionStatePersistence() {
|
||||||
|
FileTransferDialog.TransferSessionState state = new FileTransferDialog.TransferSessionState();
|
||||||
|
state.hostType = FTConfig.HostType.TSO;
|
||||||
|
state.isSend = true;
|
||||||
|
state.hostFile = "'USER.PDS.DATA'";
|
||||||
|
state.units = FTConfig.AllocationUnit.CYLINDERS;
|
||||||
|
state.space = "10,5";
|
||||||
|
state.dirBlocks = "20";
|
||||||
|
|
||||||
|
FileTransferDialog.setLastTransferState(state);
|
||||||
|
|
||||||
|
FileTransferDialog.TransferSessionState retrieved = FileTransferDialog.getLastTransferState();
|
||||||
|
assertNotNull(retrieved);
|
||||||
|
assertEquals(FTConfig.AllocationUnit.CYLINDERS, retrieved.units);
|
||||||
|
assertEquals("10,5", retrieved.space);
|
||||||
|
assertEquals("20", retrieved.dirBlocks);
|
||||||
|
|
||||||
|
try {
|
||||||
|
FileTransferDialog dialog = new FileTransferDialog(null, null);
|
||||||
|
assertEquals(FTConfig.AllocationUnit.CYLINDERS, dialog.getUnitsCombo().getSelectedItem());
|
||||||
|
assertEquals("10,5", dialog.getSpaceField().getText());
|
||||||
|
assertEquals("20", dialog.getDirField().getText());
|
||||||
|
dialog.dispose();
|
||||||
|
} catch (HeadlessException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHostDirectoryDialogStructureAndFilter() {
|
||||||
|
// Test default query resolution
|
||||||
|
assertEquals("* * A", haus.nightmare.j3270.ui.HostDirectoryDialog.resolveDefaultQuery(FTConfig.HostType.CMS, null));
|
||||||
|
assertEquals("* * A", haus.nightmare.j3270.ui.HostDirectoryDialog.resolveDefaultQuery(FTConfig.HostType.CMS, ""));
|
||||||
|
assertEquals("* * A", haus.nightmare.j3270.ui.HostDirectoryDialog.resolveDefaultQuery(FTConfig.HostType.CMS, " "));
|
||||||
|
assertEquals("* * A", haus.nightmare.j3270.ui.HostDirectoryDialog.resolveDefaultQuery(FTConfig.HostType.CMS, "* * *"));
|
||||||
|
assertEquals("* * A", haus.nightmare.j3270.ui.HostDirectoryDialog.resolveDefaultQuery(FTConfig.HostType.CMS, "*"));
|
||||||
|
assertEquals("TEST EXEC", haus.nightmare.j3270.ui.HostDirectoryDialog.resolveDefaultQuery(FTConfig.HostType.CMS, "TEST EXEC"));
|
||||||
|
|
||||||
|
assertEquals("", haus.nightmare.j3270.ui.HostDirectoryDialog.resolveDefaultQuery(FTConfig.HostType.TSO, null));
|
||||||
|
assertEquals("", haus.nightmare.j3270.ui.HostDirectoryDialog.resolveDefaultQuery(FTConfig.HostType.TSO, ""));
|
||||||
|
assertEquals("'MY.DATASET'", haus.nightmare.j3270.ui.HostDirectoryDialog.resolveDefaultQuery(FTConfig.HostType.TSO, "'MY.DATASET'"));
|
||||||
|
|
||||||
|
// Test CMS filter formatting
|
||||||
|
assertEquals("* * A", haus.nightmare.j3270.ui.HostDirectoryDialog.formatCMSFilter(null));
|
||||||
|
assertEquals("* * A", haus.nightmare.j3270.ui.HostDirectoryDialog.formatCMSFilter(""));
|
||||||
|
assertEquals("* * A", haus.nightmare.j3270.ui.HostDirectoryDialog.formatCMSFilter("* * *"));
|
||||||
|
assertEquals("* * A", haus.nightmare.j3270.ui.HostDirectoryDialog.formatCMSFilter("*"));
|
||||||
|
assertEquals("TEST * A", haus.nightmare.j3270.ui.HostDirectoryDialog.formatCMSFilter("TEST"));
|
||||||
|
assertEquals("TEST EXEC A", haus.nightmare.j3270.ui.HostDirectoryDialog.formatCMSFilter("TEST EXEC"));
|
||||||
|
assertEquals("TEST EXEC B", haus.nightmare.j3270.ui.HostDirectoryDialog.formatCMSFilter("TEST EXEC B"));
|
||||||
|
assertEquals("TEST EXEC A", haus.nightmare.j3270.ui.HostDirectoryDialog.formatCMSFilter("TEST.EXEC"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
haus.nightmare.j3270.ui.HostDirectoryDialog dialog =
|
||||||
|
new haus.nightmare.j3270.ui.HostDirectoryDialog(null, null, FTConfig.HostType.CMS, "");
|
||||||
|
assertNotNull(dialog);
|
||||||
|
assertFalse(dialog.isConfirmed());
|
||||||
|
assertNull(dialog.getSelectedHostFile());
|
||||||
|
assertEquals("* * A", dialog.getQueryField().getText());
|
||||||
|
dialog.dispose();
|
||||||
|
|
||||||
|
haus.nightmare.j3270.ui.HostDirectoryDialog dialog2 =
|
||||||
|
new haus.nightmare.j3270.ui.HostDirectoryDialog(null, null, FTConfig.HostType.CMS, "* * *");
|
||||||
|
assertEquals("* * A", dialog2.getQueryField().getText());
|
||||||
|
dialog2.dispose();
|
||||||
|
|
||||||
|
haus.nightmare.j3270.ui.HostDirectoryDialog dialog3 =
|
||||||
|
new haus.nightmare.j3270.ui.HostDirectoryDialog(null, null, FTConfig.HostType.CMS, "TEST EXEC");
|
||||||
|
assertEquals("TEST EXEC", dialog3.getQueryField().getText());
|
||||||
|
dialog3.dispose();
|
||||||
|
} catch (HeadlessException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMatchesFilterWildcards() {
|
||||||
|
CMSDirectoryEntry cmsA = new CMSDirectoryEntry("PROFILE", "EXEC", "A1");
|
||||||
|
CMSDirectoryEntry cmsB = new CMSDirectoryEntry("PROFILE", "EXEC", "B1");
|
||||||
|
CMSDirectoryEntry cmsSys = new CMSDirectoryEntry("SYSTEM", "CONFIG", "S2");
|
||||||
|
|
||||||
|
// * * A matches A disk files (A1, A0, etc.), excludes B and S
|
||||||
|
assertTrue(HostDirectoryDialog.matchesFilter(cmsA, "* * A"));
|
||||||
|
assertFalse(HostDirectoryDialog.matchesFilter(cmsB, "* * A"));
|
||||||
|
assertFalse(HostDirectoryDialog.matchesFilter(cmsSys, "* * A"));
|
||||||
|
|
||||||
|
// Blank, *, * * * matches all
|
||||||
|
assertTrue(HostDirectoryDialog.matchesFilter(cmsA, ""));
|
||||||
|
assertTrue(HostDirectoryDialog.matchesFilter(cmsA, "*"));
|
||||||
|
assertTrue(HostDirectoryDialog.matchesFilter(cmsB, "* * *"));
|
||||||
|
assertTrue(HostDirectoryDialog.matchesFilter(cmsSys, "* * *"));
|
||||||
|
|
||||||
|
// Two tokens (fn ft) and dot tokens
|
||||||
|
assertTrue(HostDirectoryDialog.matchesFilter(cmsA, "* EXEC"));
|
||||||
|
assertTrue(HostDirectoryDialog.matchesFilter(cmsA, "*.EXEC"));
|
||||||
|
assertTrue(HostDirectoryDialog.matchesFilter(cmsA, "PROFILE *"));
|
||||||
|
assertTrue(HostDirectoryDialog.matchesFilter(cmsA, "PROF*.*"));
|
||||||
|
assertFalse(HostDirectoryDialog.matchesFilter(cmsSys, "* EXEC"));
|
||||||
|
|
||||||
|
// TSO Wildcards
|
||||||
|
TSODirectoryEntry tso = new TSODirectoryEntry("TSOUSER.TEST.DATA");
|
||||||
|
assertTrue(HostDirectoryDialog.matchesFilter(tso, ""));
|
||||||
|
assertTrue(HostDirectoryDialog.matchesFilter(tso, "*"));
|
||||||
|
assertTrue(HostDirectoryDialog.matchesFilter(tso, "* * *"));
|
||||||
|
assertTrue(HostDirectoryDialog.matchesFilter(tso, "*.DATA"));
|
||||||
|
assertTrue(HostDirectoryDialog.matchesFilter(tso, "TEST"));
|
||||||
|
assertTrue(HostDirectoryDialog.matchesFilter(tso, "TSOUSER.*"));
|
||||||
|
assertTrue(HostDirectoryDialog.matchesFilter(tso, "'TSOUSER.TEST.*'"));
|
||||||
|
assertFalse(HostDirectoryDialog.matchesFilter(tso, "*.CNTL"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,342 @@
|
|||||||
|
package haus.nightmare.j3270.storage;
|
||||||
|
|
||||||
|
import haus.nightmare.j3270.J3270App;
|
||||||
|
import haus.nightmare.j3270.config.Settings;
|
||||||
|
import haus.nightmare.j3270.ui.SettingsDialog;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class ConnectionIniStorageTest {
|
||||||
|
|
||||||
|
private List<SavedHost> originalSavedHosts;
|
||||||
|
private String origHost;
|
||||||
|
private int origPort;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() {
|
||||||
|
originalSavedHosts = HostStorage.getSavedHosts();
|
||||||
|
origHost = Settings.getAutoConnectHost();
|
||||||
|
origPort = Settings.getAutoConnectPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void tearDown() {
|
||||||
|
if (originalSavedHosts != null) {
|
||||||
|
HostStorage.saveAll(originalSavedHosts);
|
||||||
|
} else {
|
||||||
|
HostStorage.clearAll();
|
||||||
|
}
|
||||||
|
Settings.setAutoConnectHost(origHost);
|
||||||
|
Settings.setAutoConnectPort(origPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("HostStorage exports to and loads from connections.ini with full fidelity")
|
||||||
|
public void testExportAndLoadConnectionsIniRoundtrip() throws IOException {
|
||||||
|
List<SavedHost> hosts = new ArrayList<>();
|
||||||
|
SavedHost h1 = new SavedHost("id-mainframe-1", "Production TSO", "mainframe.corp.com", 23, 4, 62, 160,
|
||||||
|
"LU01", true, "TSO", false, true, true, "BOTH", "037", "NONE", "", 0, "", "");
|
||||||
|
SavedHost h2 = new SavedHost("id-vm-2", "VM CMS Test", "vmcms.lab.net", 992, 2, 24, 80,
|
||||||
|
"LUCMS", false, "CMS", true, false, true, "GDDM", "1047", "SOCKS5", "10.0.0.1", 1080, "proxyuser", "proxypass");
|
||||||
|
hosts.add(h1);
|
||||||
|
hosts.add(h2);
|
||||||
|
|
||||||
|
File tmpIni = File.createTempFile("connections_test", ".ini");
|
||||||
|
tmpIni.deleteOnExit();
|
||||||
|
|
||||||
|
try {
|
||||||
|
HostStorage.exportToIniFile(tmpIni, hosts);
|
||||||
|
|
||||||
|
assertTrue(tmpIni.exists());
|
||||||
|
String content = new String(Files.readAllBytes(tmpIni.toPath()));
|
||||||
|
assertTrue(content.contains("[Production TSO]"), "Must contain section header [Production TSO]");
|
||||||
|
assertTrue(content.contains("[VM CMS Test]"), "Must contain section header [VM CMS Test]");
|
||||||
|
assertTrue(content.contains("host = mainframe.corp.com"));
|
||||||
|
assertTrue(content.contains("proxyHost = 10.0.0.1"));
|
||||||
|
assertTrue(content.contains("proxyType = SOCKS5"));
|
||||||
|
|
||||||
|
List<SavedHost> loaded = HostStorage.loadFromIniFile(tmpIni);
|
||||||
|
assertEquals(2, loaded.size(), "Should load 2 connections from INI");
|
||||||
|
|
||||||
|
SavedHost loaded1 = loaded.get(0);
|
||||||
|
assertEquals("id-mainframe-1", loaded1.getId());
|
||||||
|
assertEquals("Production TSO", loaded1.getName());
|
||||||
|
assertEquals("mainframe.corp.com", loaded1.getHost());
|
||||||
|
assertEquals(23, loaded1.getPort());
|
||||||
|
assertEquals(4, loaded1.getModel());
|
||||||
|
assertEquals(62, loaded1.getDynamicRows());
|
||||||
|
assertEquals(160, loaded1.getDynamicCols());
|
||||||
|
assertEquals("LU01", loaded1.getLuName());
|
||||||
|
assertTrue(loaded1.isAutoConnect());
|
||||||
|
assertEquals("TSO", loaded1.getHostType());
|
||||||
|
assertFalse(loaded1.isUseTls());
|
||||||
|
assertTrue(loaded1.isTlsVerifyCert());
|
||||||
|
|
||||||
|
SavedHost loaded2 = loaded.get(1);
|
||||||
|
assertEquals("id-vm-2", loaded2.getId());
|
||||||
|
assertEquals("VM CMS Test", loaded2.getName());
|
||||||
|
assertEquals("vmcms.lab.net", loaded2.getHost());
|
||||||
|
assertEquals(992, loaded2.getPort());
|
||||||
|
assertEquals(2, loaded2.getModel());
|
||||||
|
assertEquals(24, loaded2.getDynamicRows());
|
||||||
|
assertEquals(80, loaded2.getDynamicCols());
|
||||||
|
assertEquals("LUCMS", loaded2.getLuName());
|
||||||
|
assertFalse(loaded2.isAutoConnect());
|
||||||
|
assertEquals("CMS", loaded2.getHostType());
|
||||||
|
assertTrue(loaded2.isUseTls());
|
||||||
|
assertFalse(loaded2.isTlsVerifyCert());
|
||||||
|
assertEquals("SOCKS5", loaded2.getProxyType());
|
||||||
|
assertEquals("10.0.0.1", loaded2.getProxyHost());
|
||||||
|
assertEquals(1080, loaded2.getProxyPort());
|
||||||
|
assertEquals("proxyuser", loaded2.getProxyUsername());
|
||||||
|
assertEquals("proxypass", loaded2.getProxyPassword());
|
||||||
|
} finally {
|
||||||
|
if (tmpIni.exists()) tmpIni.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("HostStorage importFromIniFile merges and updates existing hosts by name or id")
|
||||||
|
public void testImportFromIniFileMergesAndUpdates() throws IOException {
|
||||||
|
SavedHost initial = new SavedHost("id-100", "Mainframe Prod", "old.host.org", 23, 4, 62, 160,
|
||||||
|
"", false, "TSO", false, true, true, "BOTH", "037", "NONE", "", 0, "", "");
|
||||||
|
List<SavedHost> initList = new ArrayList<>();
|
||||||
|
initList.add(initial);
|
||||||
|
HostStorage.saveAll(initList);
|
||||||
|
|
||||||
|
File tmpIni = File.createTempFile("connections_merge", ".ini");
|
||||||
|
tmpIni.deleteOnExit();
|
||||||
|
|
||||||
|
try {
|
||||||
|
try (FileWriter w = new FileWriter(tmpIni)) {
|
||||||
|
// Update Mainframe Prod with new host address
|
||||||
|
w.write("[Mainframe Prod]\n");
|
||||||
|
w.write("id = id-100\n");
|
||||||
|
w.write("host = new.host.org\n");
|
||||||
|
w.write("port = 2323\n");
|
||||||
|
w.write("\n");
|
||||||
|
// Add a second connection
|
||||||
|
w.write("[Development CICS]\n");
|
||||||
|
w.write("host = cics.dev.org\n");
|
||||||
|
w.write("port = 992\n");
|
||||||
|
w.write("hostType = CICS\n");
|
||||||
|
w.write("useTls = true\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<SavedHost> result = HostStorage.importFromIniFile(tmpIni);
|
||||||
|
assertEquals(2, result.size(), "Import should result in 2 saved hosts (1 updated, 1 added)");
|
||||||
|
|
||||||
|
SavedHost updated = HostStorage.findById("id-100");
|
||||||
|
assertNotNull(updated);
|
||||||
|
assertEquals("new.host.org", updated.getHost());
|
||||||
|
assertEquals(2323, updated.getPort());
|
||||||
|
|
||||||
|
SavedHost added = HostStorage.findByName("Development CICS");
|
||||||
|
assertNotNull(added);
|
||||||
|
assertEquals("cics.dev.org", added.getHost());
|
||||||
|
assertEquals(992, added.getPort());
|
||||||
|
assertEquals("CICS", added.getHostType());
|
||||||
|
assertTrue(added.isUseTls());
|
||||||
|
} finally {
|
||||||
|
if (tmpIni.exists()) tmpIni.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("HostStorage parses INI section prefixes and aliases properly")
|
||||||
|
public void testImportHandlesMissingFieldsAndAliases() throws IOException {
|
||||||
|
File tmpIni = File.createTempFile("connections_alias", ".ini");
|
||||||
|
tmpIni.deleteOnExit();
|
||||||
|
|
||||||
|
try {
|
||||||
|
try (FileWriter w = new FileWriter(tmpIni)) {
|
||||||
|
w.write("[connection:Aliased Host]\n");
|
||||||
|
w.write("hostname = test.alias.net\n");
|
||||||
|
w.write("port = 1023\n");
|
||||||
|
w.write("terminalModel = 2\n");
|
||||||
|
w.write("type = CMS\n");
|
||||||
|
w.write("tls = true\n");
|
||||||
|
w.write("verifyCert = false\n");
|
||||||
|
w.write("charset = 1047\n");
|
||||||
|
w.write("rows = 24\n");
|
||||||
|
w.write("cols = 80\n");
|
||||||
|
w.write("lu = LUVM\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<SavedHost> parsed = HostStorage.loadFromIniFile(tmpIni);
|
||||||
|
assertEquals(1, parsed.size());
|
||||||
|
|
||||||
|
SavedHost h = parsed.get(0);
|
||||||
|
assertEquals("Aliased Host", h.getName());
|
||||||
|
assertEquals("test.alias.net", h.getHost());
|
||||||
|
assertEquals(1023, h.getPort());
|
||||||
|
assertEquals(2, h.getModel());
|
||||||
|
assertEquals("CMS", h.getHostType());
|
||||||
|
assertTrue(h.isUseTls());
|
||||||
|
assertFalse(h.isTlsVerifyCert());
|
||||||
|
assertEquals("1047", h.getCodePage());
|
||||||
|
assertEquals(24, h.getDynamicRows());
|
||||||
|
assertEquals(80, h.getDynamicCols());
|
||||||
|
assertEquals("LUVM", h.getLuName());
|
||||||
|
} finally {
|
||||||
|
if (tmpIni.exists()) tmpIni.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("SettingsDialog contains Import Connections and Export Connections buttons and is taller")
|
||||||
|
public void testSettingsDialogConnectionButtonsAndHeight() {
|
||||||
|
J3270App app;
|
||||||
|
try {
|
||||||
|
app = new J3270App();
|
||||||
|
} catch (HeadlessException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
SettingsDialog dialog = new SettingsDialog(app);
|
||||||
|
|
||||||
|
JButton btnImportConn = dialog.getBtnImportConnections();
|
||||||
|
JButton btnExportConn = dialog.getBtnExportConnections();
|
||||||
|
|
||||||
|
assertNotNull(btnImportConn, "Import Connections button must exist in SettingsDialog");
|
||||||
|
assertNotNull(btnExportConn, "Export Connections button must exist in SettingsDialog");
|
||||||
|
|
||||||
|
assertEquals("Import Connections...", btnImportConn.getText());
|
||||||
|
assertEquals("Export Connections...", btnExportConn.getText());
|
||||||
|
|
||||||
|
// Check window height: must be taller than previous 540 to accommodate extra buttons
|
||||||
|
assertTrue(dialog.getHeight() > 540,
|
||||||
|
"Settings window height (" + dialog.getHeight() + ") must be taller than 540 to accommodate the extra buttons");
|
||||||
|
|
||||||
|
// Verify config buttons share container row1, and connection buttons share container row2
|
||||||
|
JButton btnImport = dialog.getBtnImport();
|
||||||
|
JButton btnExport = dialog.getBtnExport();
|
||||||
|
assertNotNull(btnImport);
|
||||||
|
assertNotNull(btnExport);
|
||||||
|
|
||||||
|
Container configRow = btnImport.getParent();
|
||||||
|
assertNotNull(configRow);
|
||||||
|
assertSame(configRow, btnExport.getParent(), "btnImport and btnExport must share the same parent row");
|
||||||
|
|
||||||
|
Container connRow = btnImportConn.getParent();
|
||||||
|
assertNotNull(connRow);
|
||||||
|
assertSame(connRow, btnExportConn.getParent(), "btnImportConn and btnExportConn must share the same parent row");
|
||||||
|
|
||||||
|
assertNotSame(configRow, connRow, "Connection buttons must be below and NOT in line with config buttons");
|
||||||
|
|
||||||
|
// Verify connRow is positioned below configRow in buttonPanel
|
||||||
|
Container buttonPanel = configRow.getParent();
|
||||||
|
assertNotNull(buttonPanel);
|
||||||
|
assertSame(buttonPanel, connRow.getParent(), "Both rows must be in buttonPanel");
|
||||||
|
|
||||||
|
int configRowIdx = -1;
|
||||||
|
int connRowIdx = -1;
|
||||||
|
Component[] rows = buttonPanel.getComponents();
|
||||||
|
for (int i = 0; i < rows.length; i++) {
|
||||||
|
if (rows[i] == configRow) configRowIdx = i;
|
||||||
|
if (rows[i] == connRow) connRowIdx = i;
|
||||||
|
}
|
||||||
|
assertTrue(configRowIdx >= 0, "configRow must be in buttonPanel");
|
||||||
|
assertTrue(connRowIdx >= 0, "connRow must be in buttonPanel");
|
||||||
|
assertTrue(connRowIdx > configRowIdx, "connRow must be below configRow in buttonPanel (" + connRowIdx + " > " + configRowIdx + ")");
|
||||||
|
|
||||||
|
dialog.dispose();
|
||||||
|
app.dispose();
|
||||||
|
} catch (HeadlessException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("SettingsDialog importConnectionsFile updates savedHostBox and Settings")
|
||||||
|
public void testSettingsDialogImportConnectionsFileUpdatesDropdownAndSettings() throws IOException {
|
||||||
|
J3270App app;
|
||||||
|
try {
|
||||||
|
app = new J3270App();
|
||||||
|
} catch (HeadlessException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File tmpIni = File.createTempFile("settings_dialog_import_test", ".ini");
|
||||||
|
tmpIni.deleteOnExit();
|
||||||
|
|
||||||
|
try {
|
||||||
|
try (FileWriter w = new FileWriter(tmpIni)) {
|
||||||
|
w.write("[Auto Mainframe]\n");
|
||||||
|
w.write("host = auto.mainframe.org\n");
|
||||||
|
w.write("port = 992\n");
|
||||||
|
w.write("autoConnect = true\n");
|
||||||
|
w.write("useTls = true\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsDialog dialog = new SettingsDialog(app);
|
||||||
|
dialog.importConnectionsFile(tmpIni);
|
||||||
|
|
||||||
|
// Check savedHostBox updated
|
||||||
|
JComboBox<SavedHost> box = dialog.getSavedHostBox();
|
||||||
|
assertNotNull(box);
|
||||||
|
assertTrue(box.getItemCount() >= 1);
|
||||||
|
|
||||||
|
SavedHost sel = (SavedHost) box.getSelectedItem();
|
||||||
|
assertNotNull(sel);
|
||||||
|
assertEquals("auto.mainframe.org", sel.getHost());
|
||||||
|
assertTrue(sel.isAutoConnect());
|
||||||
|
|
||||||
|
// Check Settings updated for autoConnect host
|
||||||
|
assertEquals("auto.mainframe.org", Settings.getAutoConnectHost());
|
||||||
|
assertEquals(992, Settings.getAutoConnectPort());
|
||||||
|
assertTrue(Settings.getAutoConnectTls());
|
||||||
|
|
||||||
|
// Test exportConnectionsFile
|
||||||
|
File exportTarget = File.createTempFile("settings_dialog_export_test", ".ini");
|
||||||
|
exportTarget.deleteOnExit();
|
||||||
|
dialog.exportConnectionsFile(exportTarget);
|
||||||
|
assertTrue(exportTarget.exists());
|
||||||
|
String exported = new String(Files.readAllBytes(exportTarget.toPath()));
|
||||||
|
assertTrue(exported.contains("auto.mainframe.org"));
|
||||||
|
|
||||||
|
dialog.dispose();
|
||||||
|
app.dispose();
|
||||||
|
} catch (HeadlessException ignored) {
|
||||||
|
} finally {
|
||||||
|
if (tmpIni.exists()) tmpIni.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("SettingsDialog importConnectionsFile throws FileNotFoundException for nonexistent file")
|
||||||
|
public void testImportNonExistentConnectionsFileThrows() {
|
||||||
|
J3270App app;
|
||||||
|
try {
|
||||||
|
app = new J3270App();
|
||||||
|
} catch (HeadlessException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
SettingsDialog dialog = new SettingsDialog(app);
|
||||||
|
File nonExistent = new File("/path/to/nonexistent/connections_" + System.currentTimeMillis() + ".ini");
|
||||||
|
assertThrows(FileNotFoundException.class, () -> {
|
||||||
|
dialog.importConnectionsFile(nonExistent);
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.dispose();
|
||||||
|
app.dispose();
|
||||||
|
} catch (HeadlessException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -304,4 +304,40 @@ public class HostStorageAndProxyTest {
|
|||||||
dialog.dispose();
|
dialog.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("SavedHost preserves all GraphicsMode settings across JSON serialization without clobbering to BOTH")
|
||||||
|
public void testSavedHostGraphicsModeSerializationRoundtrip() {
|
||||||
|
for (haus.nightmare.lib3270j.graphics.GraphicsMode gm : haus.nightmare.lib3270j.graphics.GraphicsMode.values()) {
|
||||||
|
SavedHost host = new SavedHost(
|
||||||
|
UUID.randomUUID().toString(),
|
||||||
|
"Gfx " + gm.name(),
|
||||||
|
"test.host.com",
|
||||||
|
23,
|
||||||
|
4,
|
||||||
|
24,
|
||||||
|
80,
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
"TSO",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
gm.name(),
|
||||||
|
"037",
|
||||||
|
"NONE",
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
String json = HostStorage.serializeHostsJson(List.of(host));
|
||||||
|
List<SavedHost> parsed = HostStorage.parseHostsJson(json);
|
||||||
|
assertEquals(1, parsed.size());
|
||||||
|
assertEquals(gm.name(), parsed.get(0).getGraphicsMode(),
|
||||||
|
"Graphics mode " + gm.name() + " must be preserved and not coerced to BOTH");
|
||||||
|
assertEquals(gm, parsed.get(0).toConnectionConfig().getGraphicsMode(),
|
||||||
|
"ConnectionConfig must reflect GraphicsMode." + gm.name());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+242
@@ -0,0 +1,242 @@
|
|||||||
|
package haus.nightmare.j3270.ui;
|
||||||
|
|
||||||
|
import haus.nightmare.j3270.J3270App;
|
||||||
|
import haus.nightmare.j3270.config.Settings;
|
||||||
|
import haus.nightmare.j3270.storage.HostStorage;
|
||||||
|
import haus.nightmare.j3270.storage.SavedHost;
|
||||||
|
import haus.nightmare.lib3270j.ConnectionConfig;
|
||||||
|
import haus.nightmare.lib3270j.ConnectionState;
|
||||||
|
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||||
|
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class AutoConnectSettingsAndTerminalCenteringTest {
|
||||||
|
|
||||||
|
private List<SavedHost> originalSavedHosts;
|
||||||
|
private Settings.StartupBehavior originalStartupBehavior;
|
||||||
|
private String originalAutoConnectHost;
|
||||||
|
private int originalAutoConnectPort;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() {
|
||||||
|
originalSavedHosts = new ArrayList<>(HostStorage.getSavedHosts());
|
||||||
|
originalStartupBehavior = Settings.getStartupBehavior();
|
||||||
|
originalAutoConnectHost = Settings.getAutoConnectHost();
|
||||||
|
originalAutoConnectPort = Settings.getAutoConnectPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void tearDown() {
|
||||||
|
HostStorage.saveAll(originalSavedHosts);
|
||||||
|
Settings.setStartupBehavior(originalStartupBehavior);
|
||||||
|
Settings.setAutoConnectHost(originalAutoConnectHost);
|
||||||
|
Settings.setAutoConnectPort(originalAutoConnectPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("TerminalPanel centering: left bound is flush when constrained, centered with left <= right when too big")
|
||||||
|
public void testTerminalPanelCenteringLogic() {
|
||||||
|
TerminalPanel panel = new TerminalPanel();
|
||||||
|
panel.cellWidth = 10;
|
||||||
|
panel.cellHeight = 20;
|
||||||
|
|
||||||
|
Telnet3270Client client = new Telnet3270Client(new ConnectionConfig("localhost", 23));
|
||||||
|
client.getTelnetFSM().setConnectionState(ConnectionState.CONNECTED_3270);
|
||||||
|
ScreenBuffer sb = client.getScreenBuffer();
|
||||||
|
sb.erase(false);
|
||||||
|
panel.setClient(client);
|
||||||
|
|
||||||
|
int cols = sb.getDisplayCols(); // 80
|
||||||
|
int rows = sb.getDisplayRows(); // 24
|
||||||
|
int gridW = cols * panel.cellWidth; // 800
|
||||||
|
int gridH = rows * panel.cellHeight; // 480
|
||||||
|
|
||||||
|
// 1. Panel is smaller than grid: left bound must be 0 (flush left cells), vertical must be 0
|
||||||
|
panel.setSize(gridW - 100, gridH - 50);
|
||||||
|
assertEquals(0, panel.getRenderOffsetX(), "Left bound should be 0 when window width is constrained");
|
||||||
|
assertEquals(0, panel.getRenderOffsetY(), "Top bound should be 0 when window height is constrained");
|
||||||
|
|
||||||
|
// 2. Panel exactly matches grid: offset must be 0
|
||||||
|
panel.setSize(gridW, gridH);
|
||||||
|
assertEquals(0, panel.getRenderOffsetX(), "Offset X must be 0 when width exactly matches grid");
|
||||||
|
assertEquals(0, panel.getRenderOffsetY(), "Offset Y must be 0 when height exactly matches grid");
|
||||||
|
|
||||||
|
// 3. Panel is larger with odd extra (extra = 7)
|
||||||
|
panel.setSize(gridW + 7, gridH + 11);
|
||||||
|
int ox = panel.getRenderOffsetX();
|
||||||
|
int leftSpace = ox;
|
||||||
|
int rightSpace = (gridW + 7) - (ox + gridW);
|
||||||
|
assertTrue(leftSpace <= rightSpace, "Left blank space (" + leftSpace + ") must be <= right space (" + rightSpace + ")");
|
||||||
|
assertEquals(3, leftSpace);
|
||||||
|
assertEquals(4, rightSpace);
|
||||||
|
|
||||||
|
int oy = panel.getRenderOffsetY();
|
||||||
|
int topSpace = oy;
|
||||||
|
int bottomSpace = (gridH + 11) - (oy + gridH);
|
||||||
|
assertTrue(topSpace <= bottomSpace, "Top space must be <= bottom space");
|
||||||
|
assertEquals(5, topSpace);
|
||||||
|
assertEquals(6, bottomSpace);
|
||||||
|
|
||||||
|
// 4. Panel is larger with even extra (extra = 6)
|
||||||
|
panel.setSize(gridW + 6, gridH + 8);
|
||||||
|
assertEquals(3, panel.getRenderOffsetX());
|
||||||
|
assertEquals(4, panel.getRenderOffsetY());
|
||||||
|
|
||||||
|
panel.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("guardedPack does not resize window when window is too small")
|
||||||
|
public void testGuardedPackDoesNotResizeWhenTooSmall() {
|
||||||
|
try {
|
||||||
|
JFrame frame = new JFrame();
|
||||||
|
TerminalPanel panel = new TerminalPanel();
|
||||||
|
panel.cellWidth = 10;
|
||||||
|
panel.cellHeight = 20;
|
||||||
|
|
||||||
|
Telnet3270Client client = new Telnet3270Client(new ConnectionConfig("localhost", 23));
|
||||||
|
client.getTelnetFSM().setConnectionState(ConnectionState.CONNECTED_3270);
|
||||||
|
panel.setClient(client);
|
||||||
|
|
||||||
|
frame.getContentPane().add(panel);
|
||||||
|
frame.setSize(400, 300);
|
||||||
|
frame.addNotify(); // makes frame displayable
|
||||||
|
|
||||||
|
// Set size explicitly smaller than preferred size (800+ x 480+)
|
||||||
|
frame.setSize(400, 300);
|
||||||
|
Dimension sizeBefore = frame.getSize();
|
||||||
|
|
||||||
|
// Run guardedPack
|
||||||
|
panel.guardedPack();
|
||||||
|
|
||||||
|
// Window should not have expanded
|
||||||
|
Dimension sizeAfter = frame.getSize();
|
||||||
|
assertEquals(sizeBefore.width, sizeAfter.width, "Window width should NOT be enlarged when too small");
|
||||||
|
assertEquals(sizeBefore.height, sizeAfter.height, "Window height should NOT be enlarged when too small");
|
||||||
|
|
||||||
|
frame.dispose();
|
||||||
|
panel.dispose();
|
||||||
|
} catch (HeadlessException ignored) {
|
||||||
|
// Environment without X display
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("SettingsDialog Startup & Auto-Connect presents saved connections dropdown and updates auto-connect host")
|
||||||
|
public void testSettingsDialogSavedConnectionsDropdown() throws Exception {
|
||||||
|
SavedHost host1 = new SavedHost("id-test-1", "Host Alpha", "alpha.mainframe.test", 23, 2, 24, 80, "LU1", true, "TSO", false, true, true, "BOTH", "037", "NONE", "", 0, "", "");
|
||||||
|
SavedHost host2 = new SavedHost("id-test-2", "Host Beta", "beta.mainframe.test", 992, 4, 43, 80, "LU2", false, "VM", true, false, true, "BOTH", "037", "NONE", "", 0, "", "");
|
||||||
|
|
||||||
|
List<SavedHost> testHosts = new ArrayList<>();
|
||||||
|
testHosts.add(host1);
|
||||||
|
testHosts.add(host2);
|
||||||
|
HostStorage.saveAll(testHosts);
|
||||||
|
Settings.setStartupBehavior(Settings.StartupBehavior.AUTO_CONNECT);
|
||||||
|
|
||||||
|
J3270App app;
|
||||||
|
try {
|
||||||
|
app = new J3270App();
|
||||||
|
} catch (HeadlessException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
SettingsDialog dialog = new SettingsDialog(app);
|
||||||
|
|
||||||
|
JComboBox<SavedHost> combo = dialog.getSavedHostBox();
|
||||||
|
assertNotNull(combo, "savedHostBox should not be null");
|
||||||
|
assertEquals(2, combo.getItemCount(), "Dropdown should contain all saved hosts");
|
||||||
|
|
||||||
|
// Host Alpha was autoConnect=true, so it should be initially selected
|
||||||
|
SavedHost initialSel = (SavedHost) combo.getSelectedItem();
|
||||||
|
assertNotNull(initialSel);
|
||||||
|
assertEquals("id-test-1", initialSel.getId());
|
||||||
|
|
||||||
|
// Switch to Host Beta
|
||||||
|
combo.setSelectedIndex(1);
|
||||||
|
|
||||||
|
// Trigger applySettings via reflection
|
||||||
|
java.lang.reflect.Method applyMethod = SettingsDialog.class.getDeclaredMethod("applySettings");
|
||||||
|
applyMethod.setAccessible(true);
|
||||||
|
applyMethod.invoke(dialog);
|
||||||
|
|
||||||
|
// Verify Host Beta is now the auto-connect host
|
||||||
|
SavedHost auto = HostStorage.getAutoConnectHost();
|
||||||
|
assertNotNull(auto, "Auto-connect host should exist");
|
||||||
|
assertEquals("id-test-2", auto.getId());
|
||||||
|
assertEquals("Host Beta", auto.getName());
|
||||||
|
assertEquals("beta.mainframe.test", auto.getHost());
|
||||||
|
assertEquals(992, auto.getPort());
|
||||||
|
|
||||||
|
SavedHost loadedAlpha = HostStorage.findById("id-test-1");
|
||||||
|
assertNotNull(loadedAlpha);
|
||||||
|
assertFalse(loadedAlpha.isAutoConnect(), "Host Alpha should no longer have autoConnect = true");
|
||||||
|
|
||||||
|
// Verify Settings autoConnectHost/Port synchronized
|
||||||
|
assertEquals("beta.mainframe.test", Settings.getAutoConnectHost());
|
||||||
|
assertEquals(992, Settings.getAutoConnectPort());
|
||||||
|
|
||||||
|
// Switch startup behavior to SHOW_CONNECT and apply
|
||||||
|
Field sbField = SettingsDialog.class.getDeclaredField("startupBehaviorBox");
|
||||||
|
sbField.setAccessible(true);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
JComboBox<Settings.StartupBehavior> sbBox = (JComboBox<Settings.StartupBehavior>) sbField.get(dialog);
|
||||||
|
sbBox.setSelectedItem(Settings.StartupBehavior.SHOW_CONNECT);
|
||||||
|
|
||||||
|
applyMethod.invoke(dialog);
|
||||||
|
assertNull(HostStorage.getAutoConnectHost(), "When startup behavior is SHOW_CONNECT, auto-connect host should be cleared");
|
||||||
|
|
||||||
|
dialog.dispose();
|
||||||
|
app.dispose();
|
||||||
|
} finally {
|
||||||
|
HostStorage.saveAll(originalSavedHosts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Window size is adjusted once on connect for selected model and left untouched on onScreenSizeChanged")
|
||||||
|
public void testWindowSizedOnceForModelAndNotOnScreenSizeChanged() {
|
||||||
|
try {
|
||||||
|
JFrame frame = new JFrame();
|
||||||
|
TerminalPanel panel = new TerminalPanel();
|
||||||
|
panel.cellWidth = 10;
|
||||||
|
panel.cellHeight = 20;
|
||||||
|
|
||||||
|
frame.getContentPane().add(panel);
|
||||||
|
frame.addNotify();
|
||||||
|
|
||||||
|
// Pack for Model 4 (43 rows, 80 cols)
|
||||||
|
panel.packForModel(43, 80);
|
||||||
|
Dimension model4Size = frame.getSize();
|
||||||
|
assertTrue(model4Size.height >= 43 * 20, "Height should accommodate 43 rows");
|
||||||
|
assertTrue(model4Size.width >= 80 * 10, "Width should accommodate 80 cols");
|
||||||
|
|
||||||
|
// Pack for Model 5 (27 rows, 132 cols)
|
||||||
|
panel.packForModel(27, 132);
|
||||||
|
Dimension model5Size = frame.getSize();
|
||||||
|
assertTrue(model5Size.width >= 132 * 10, "Width should accommodate 132 cols");
|
||||||
|
|
||||||
|
// Set custom size and verify guardedPack leaves the window alone
|
||||||
|
frame.setSize(600, 400);
|
||||||
|
Dimension fixedSize = frame.getSize();
|
||||||
|
|
||||||
|
panel.guardedPack();
|
||||||
|
assertEquals(fixedSize.width, frame.getWidth());
|
||||||
|
assertEquals(fixedSize.height, frame.getHeight());
|
||||||
|
|
||||||
|
frame.dispose();
|
||||||
|
panel.dispose();
|
||||||
|
} catch (HeadlessException ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
package haus.nightmare.j3270.ui;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.ConnectionConfig;
|
||||||
|
import haus.nightmare.lib3270j.ConnectionState;
|
||||||
|
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||||
|
import haus.nightmare.lib3270j.input.InputProcessor;
|
||||||
|
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
|
||||||
|
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.InputMethodEvent;
|
||||||
|
import java.text.AttributedString;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class DBCSInputAndClipboardTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Clipboard selection skips DB_RIGHT cells to avoid duplicating wide characters")
|
||||||
|
public void testDBCSClipboardCopyNoDoubling() {
|
||||||
|
TerminalPanel panel = new TerminalPanel();
|
||||||
|
panel.setSize(800, 600);
|
||||||
|
|
||||||
|
ConnectionConfig config = new ConnectionConfig("localhost", 23);
|
||||||
|
config.setCodePage("930");
|
||||||
|
Telnet3270Client client = new Telnet3270Client(config);
|
||||||
|
client.getTelnetFSM().setConnectionState(ConnectionState.CONNECTED_3270);
|
||||||
|
|
||||||
|
ScreenBuffer sb = client.getScreenBuffer();
|
||||||
|
sb.erase(false);
|
||||||
|
|
||||||
|
// Put a 2-cell DBCS character at cells 0 and 1, followed by SBCS 'A' at cell 2
|
||||||
|
ExtendedAttribute cell0 = sb.getCell(0);
|
||||||
|
cell0.db = ExtendedAttribute.DB_LEFT;
|
||||||
|
cell0.ucs4 = '\u3000';
|
||||||
|
|
||||||
|
ExtendedAttribute cell1 = sb.getCell(1);
|
||||||
|
cell1.db = ExtendedAttribute.DB_RIGHT;
|
||||||
|
cell1.ucs4 = '\u3000';
|
||||||
|
|
||||||
|
ExtendedAttribute cell2 = sb.getCell(2);
|
||||||
|
cell2.db = ExtendedAttribute.DB_NONE;
|
||||||
|
cell2.ucs4 = 'A';
|
||||||
|
|
||||||
|
panel.setClient(client);
|
||||||
|
|
||||||
|
// Stream selection covering cells 0..2 (row 0, col 0..2)
|
||||||
|
panel.select(0, 0, 0, 2);
|
||||||
|
String selected = panel.getSelectedText();
|
||||||
|
|
||||||
|
assertEquals("\u3000A", selected, "Stream selection must not duplicate the DBCS character");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Block selection skips DB_RIGHT cells to avoid duplicating wide characters")
|
||||||
|
public void testDBCSBlockSelectionNoDoubling() {
|
||||||
|
TerminalPanel panel = new TerminalPanel();
|
||||||
|
panel.setSize(800, 600);
|
||||||
|
panel.setBlockSelectMode(true);
|
||||||
|
|
||||||
|
ConnectionConfig config = new ConnectionConfig("localhost", 23);
|
||||||
|
config.setCodePage("930");
|
||||||
|
Telnet3270Client client = new Telnet3270Client(config);
|
||||||
|
client.getTelnetFSM().setConnectionState(ConnectionState.CONNECTED_3270);
|
||||||
|
|
||||||
|
ScreenBuffer sb = client.getScreenBuffer();
|
||||||
|
sb.erase(false);
|
||||||
|
|
||||||
|
// Put a 2-cell DBCS character at cells 0 and 1, followed by SBCS 'B' at cell 2
|
||||||
|
ExtendedAttribute cell0 = sb.getCell(0);
|
||||||
|
cell0.db = ExtendedAttribute.DB_LEFT;
|
||||||
|
cell0.ucs4 = '\u3000';
|
||||||
|
|
||||||
|
ExtendedAttribute cell1 = sb.getCell(1);
|
||||||
|
cell1.db = ExtendedAttribute.DB_RIGHT;
|
||||||
|
cell1.ucs4 = '\u3000';
|
||||||
|
|
||||||
|
ExtendedAttribute cell2 = sb.getCell(2);
|
||||||
|
cell2.db = ExtendedAttribute.DB_NONE;
|
||||||
|
cell2.ucs4 = 'B';
|
||||||
|
|
||||||
|
panel.setClient(client);
|
||||||
|
|
||||||
|
panel.select(0, 0, 0, 2);
|
||||||
|
String selected = panel.getSelectedText();
|
||||||
|
|
||||||
|
assertEquals("\u3000B", selected, "Block selection must not duplicate the DBCS character");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("InputMethodRequests returns cursor bounds and processes committed text")
|
||||||
|
public void testDBCSInputMethodRequestsAndEvents() {
|
||||||
|
TerminalPanel panel = new TerminalPanel();
|
||||||
|
panel.setSize(800, 600);
|
||||||
|
|
||||||
|
ConnectionConfig config = new ConnectionConfig("localhost", 23);
|
||||||
|
config.setCodePage("930");
|
||||||
|
Telnet3270Client client = new Telnet3270Client(config);
|
||||||
|
client.getTelnetFSM().setConnectionState(ConnectionState.CONNECTED_3270);
|
||||||
|
|
||||||
|
panel.setClient(client);
|
||||||
|
|
||||||
|
assertNotNull(panel.getInputMethodRequests(), "getInputMethodRequests must return this");
|
||||||
|
|
||||||
|
Rectangle loc = panel.getTextLocation(null);
|
||||||
|
assertNotNull(loc, "Text location must not be null");
|
||||||
|
assertTrue(loc.width > 0, "Cursor width must be positive");
|
||||||
|
assertTrue(loc.height > 0, "Cursor height must be positive");
|
||||||
|
|
||||||
|
// Simulate committed IME text '\u3000'
|
||||||
|
AttributedString as = new AttributedString("\u3000");
|
||||||
|
InputMethodEvent imeEvent = new InputMethodEvent(panel, InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
|
||||||
|
as.getIterator(), 1, null, null);
|
||||||
|
panel.inputMethodTextChanged(imeEvent);
|
||||||
|
|
||||||
|
ScreenBuffer sb = client.getScreenBuffer();
|
||||||
|
assertEquals(ExtendedAttribute.DB_LEFT, sb.getCell(0).db);
|
||||||
|
assertEquals('\u3000', sb.getCell(0).ucs4);
|
||||||
|
assertEquals(ExtendedAttribute.DB_RIGHT, sb.getCell(1).db);
|
||||||
|
assertEquals('\u3000', sb.getCell(1).ucs4);
|
||||||
|
assertEquals(2, sb.getCursorAddress(), "Cursor should advance by 2 after typing 2-cell DBCS");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("InputProcessor backspace over DBCS character deletes both cells")
|
||||||
|
public void testInputProcessorDBCSTypingAndBackspace() {
|
||||||
|
ConnectionConfig config = new ConnectionConfig("localhost", 23);
|
||||||
|
config.setCodePage("930");
|
||||||
|
Telnet3270Client client = new Telnet3270Client(config);
|
||||||
|
client.getTelnetFSM().setConnectionState(ConnectionState.CONNECTED_3270);
|
||||||
|
|
||||||
|
InputProcessor input = client.getInputProcessor();
|
||||||
|
ScreenBuffer sb = client.getScreenBuffer();
|
||||||
|
sb.erase(false);
|
||||||
|
sb.setCursorAddress(0);
|
||||||
|
|
||||||
|
// Type DBCS character
|
||||||
|
input.typeCharacter('\u3000');
|
||||||
|
assertEquals(2, sb.getCursorAddress());
|
||||||
|
assertEquals(ExtendedAttribute.DB_LEFT, sb.getCell(0).db);
|
||||||
|
assertEquals(ExtendedAttribute.DB_RIGHT, sb.getCell(1).db);
|
||||||
|
|
||||||
|
// Backspace over DBCS character
|
||||||
|
input.backspace();
|
||||||
|
assertEquals(0, sb.getCursorAddress(), "Cursor should back up 2 cells");
|
||||||
|
assertEquals(0, sb.getCell(0).ucs4, "Cell 0 should be deleted (null)");
|
||||||
|
assertEquals(0, sb.getCell(1).ucs4, "Cell 1 should be deleted (null)");
|
||||||
|
assertEquals(ExtendedAttribute.DB_NONE, sb.getCell(0).db);
|
||||||
|
assertEquals(ExtendedAttribute.DB_NONE, sb.getCell(1).db);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
package haus.nightmare.j3270.ui;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for HotspotEngine (Phase 25.2).
|
||||||
|
* Verifies regex detection of URLs, PF keys, Enter, and menu choices,
|
||||||
|
* hit-testing, action strings, and word boundary isolation.
|
||||||
|
*/
|
||||||
|
public class HotspotEngineTest {
|
||||||
|
|
||||||
|
private HotspotEngine engine;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() {
|
||||||
|
engine = new HotspotEngine();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Detects HTTP, HTTPS, FTP, and WWW URLs with action strings")
|
||||||
|
public void testUrlDetection() {
|
||||||
|
String screen = "Visit https://ibm.com/support or http://example.org:8080/path?q=1 for help.\n" +
|
||||||
|
"Download from ftp://ftp.archive.org/pub/files or browse www.google.com.";
|
||||||
|
List<HotspotEngine.Hotspot> hotspots = engine.scanText(screen, 2, 80);
|
||||||
|
|
||||||
|
assertEquals(4, hotspots.size());
|
||||||
|
|
||||||
|
HotspotEngine.Hotspot h0 = hotspots.get(0);
|
||||||
|
assertEquals(HotspotEngine.HotspotType.URL, h0.getType());
|
||||||
|
assertEquals("https://ibm.com/support", h0.getDisplayText());
|
||||||
|
assertEquals("https://ibm.com/support", h0.getAction());
|
||||||
|
assertEquals(0, h0.getStartRow());
|
||||||
|
assertEquals(6, h0.getStartCol());
|
||||||
|
assertTrue(h0.contains(0, 10));
|
||||||
|
assertFalse(h0.contains(0, 5));
|
||||||
|
|
||||||
|
HotspotEngine.Hotspot h1 = hotspots.get(1);
|
||||||
|
assertEquals("http://example.org:8080/path?q=1", h1.getDisplayText());
|
||||||
|
assertEquals("http://example.org:8080/path?q=1", h1.getAction());
|
||||||
|
|
||||||
|
HotspotEngine.Hotspot h2 = hotspots.get(2);
|
||||||
|
assertEquals("ftp://ftp.archive.org/pub/files", h2.getDisplayText());
|
||||||
|
assertEquals("ftp://ftp.archive.org/pub/files", h2.getAction());
|
||||||
|
|
||||||
|
HotspotEngine.Hotspot h3 = hotspots.get(3);
|
||||||
|
assertEquals("www.google.com", h3.getDisplayText());
|
||||||
|
assertEquals("http://www.google.com", h3.getAction(), "www. URLs should have http:// prepended for action");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Detects PF1..PF24, F1..F24, PA1..PA3, and [ENTER] function keys")
|
||||||
|
public void testFunctionKeyDetection() {
|
||||||
|
String screen = "PF1=Help PF3=Exit PF7=Up PF8=Down PF12=Cancel\n" +
|
||||||
|
"F1=Help F12=Cancel PA1=Abort [ENTER]=Continue";
|
||||||
|
List<HotspotEngine.Hotspot> hotspots = engine.scanText(screen, 2, 80);
|
||||||
|
|
||||||
|
assertEquals(9, hotspots.size());
|
||||||
|
|
||||||
|
// PF1
|
||||||
|
assertEquals(HotspotEngine.HotspotType.PF_KEY, hotspots.get(0).getType());
|
||||||
|
assertEquals("PF1", hotspots.get(0).getDisplayText());
|
||||||
|
assertEquals("[pf1]", hotspots.get(0).getAction());
|
||||||
|
|
||||||
|
// PF3
|
||||||
|
assertEquals("PF3", hotspots.get(1).getDisplayText());
|
||||||
|
assertEquals("[pf3]", hotspots.get(1).getAction());
|
||||||
|
|
||||||
|
// PF7
|
||||||
|
assertEquals("PF7", hotspots.get(2).getDisplayText());
|
||||||
|
assertEquals("[pf7]", hotspots.get(2).getAction());
|
||||||
|
|
||||||
|
// PF8
|
||||||
|
assertEquals("PF8", hotspots.get(3).getDisplayText());
|
||||||
|
assertEquals("[pf8]", hotspots.get(3).getAction());
|
||||||
|
|
||||||
|
// PF12
|
||||||
|
assertEquals("PF12", hotspots.get(4).getDisplayText());
|
||||||
|
assertEquals("[pf12]", hotspots.get(4).getAction());
|
||||||
|
|
||||||
|
// F1 -> [pf1]
|
||||||
|
assertEquals("F1", hotspots.get(5).getDisplayText());
|
||||||
|
assertEquals("[pf1]", hotspots.get(5).getAction());
|
||||||
|
|
||||||
|
// F12 -> [pf12]
|
||||||
|
assertEquals("F12", hotspots.get(6).getDisplayText());
|
||||||
|
assertEquals("[pf12]", hotspots.get(6).getAction());
|
||||||
|
|
||||||
|
// PA1 -> [pa1]
|
||||||
|
assertEquals(HotspotEngine.HotspotType.PF_KEY, hotspots.get(7).getType());
|
||||||
|
assertEquals("PA1", hotspots.get(7).getDisplayText());
|
||||||
|
assertEquals("[pa1]", hotspots.get(7).getAction());
|
||||||
|
|
||||||
|
// [ENTER] -> [enter]
|
||||||
|
assertEquals(HotspotEngine.HotspotType.ENTER, hotspots.get(8).getType());
|
||||||
|
assertEquals("[ENTER]", hotspots.get(8).getDisplayText());
|
||||||
|
assertEquals("[enter]", hotspots.get(8).getAction());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Isolates function keys with strict word boundaries to avoid false positives")
|
||||||
|
public void testWordBoundaryIsolation() {
|
||||||
|
String screen = "STAFF1 MEMBER SPF12 FILE TRANSFER PROMPT";
|
||||||
|
List<HotspotEngine.Hotspot> hotspots = engine.scanText(screen, 1, 80);
|
||||||
|
|
||||||
|
// Neither STAFF1 nor SPF12 nor TRANSFER should be detected as PF1, F1, or F12
|
||||||
|
assertEquals(0, hotspots.size(), "Embedded letters in words should not trigger PF key hotspots");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Detects numbered menu choices and arrow prompts")
|
||||||
|
public void testMenuChoiceDetection() {
|
||||||
|
String screen = " 1. User Settings\n" +
|
||||||
|
" 2. Session Profiles\n" +
|
||||||
|
"===> 3";
|
||||||
|
List<HotspotEngine.Hotspot> hotspots = engine.scanText(screen, 3, 80);
|
||||||
|
|
||||||
|
assertEquals(3, hotspots.size());
|
||||||
|
|
||||||
|
HotspotEngine.Hotspot m1 = hotspots.get(0);
|
||||||
|
assertEquals(HotspotEngine.HotspotType.MENU_CHOICE, m1.getType());
|
||||||
|
assertEquals("1.", m1.getDisplayText());
|
||||||
|
assertEquals("1[enter]", m1.getAction());
|
||||||
|
assertEquals(0, m1.getStartRow());
|
||||||
|
|
||||||
|
HotspotEngine.Hotspot m2 = hotspots.get(1);
|
||||||
|
assertEquals("2.", m2.getDisplayText());
|
||||||
|
assertEquals("2[enter]", m2.getAction());
|
||||||
|
assertEquals(1, m2.getStartRow());
|
||||||
|
|
||||||
|
HotspotEngine.Hotspot m3 = hotspots.get(2);
|
||||||
|
assertEquals("===> 3", m3.getDisplayText());
|
||||||
|
assertEquals("3[enter]", m3.getAction());
|
||||||
|
assertEquals(2, m3.getStartRow());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Hit-testing finds hotspot under specific coordinate")
|
||||||
|
public void testFindHotspot() {
|
||||||
|
String screen = "PF3=Exit https://github.com ===> 1";
|
||||||
|
engine.scanText(screen, 1, 80);
|
||||||
|
|
||||||
|
// Under PF3 (cols 0..2)
|
||||||
|
HotspotEngine.Hotspot hPf = engine.findHotspot(0, 1);
|
||||||
|
assertNotNull(hPf);
|
||||||
|
assertEquals("PF3", hPf.getDisplayText());
|
||||||
|
|
||||||
|
// Space between hotspots
|
||||||
|
assertNull(engine.findHotspot(0, 5));
|
||||||
|
|
||||||
|
// Under URL (cols 11..28)
|
||||||
|
HotspotEngine.Hotspot hUrl = engine.findHotspot(0, 15);
|
||||||
|
assertNotNull(hUrl);
|
||||||
|
assertEquals("https://github.com", hUrl.getDisplayText());
|
||||||
|
|
||||||
|
// Under prompt (cols 32..37)
|
||||||
|
HotspotEngine.Hotspot hPrompt = engine.findHotspot(0, 34);
|
||||||
|
assertNotNull(hPrompt);
|
||||||
|
assertEquals("===> 1", hPrompt.getDisplayText());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Toggling engine options enables or disables specific hotspot categories")
|
||||||
|
public void testTogglingCategories() {
|
||||||
|
String screen = "PF3=Exit https://example.com 1. Options";
|
||||||
|
|
||||||
|
engine.scanText(screen, 1, 80);
|
||||||
|
assertEquals(3, engine.getHotspots().size());
|
||||||
|
|
||||||
|
// Disable URLs
|
||||||
|
engine.setUrlEnabled(false);
|
||||||
|
List<HotspotEngine.Hotspot> noUrls = engine.scanText(screen, 1, 80);
|
||||||
|
assertEquals(2, noUrls.size());
|
||||||
|
assertFalse(noUrls.stream().anyMatch(h -> h.getType() == HotspotEngine.HotspotType.URL));
|
||||||
|
|
||||||
|
// Disable PF keys
|
||||||
|
engine.setPfKeyEnabled(false);
|
||||||
|
List<HotspotEngine.Hotspot> menuOnly = engine.scanText(screen, 1, 80);
|
||||||
|
assertEquals(1, menuOnly.size());
|
||||||
|
assertEquals(HotspotEngine.HotspotType.MENU_CHOICE, menuOnly.get(0).getType());
|
||||||
|
|
||||||
|
// Disable menu choices
|
||||||
|
engine.setMenuChoiceEnabled(false);
|
||||||
|
List<HotspotEngine.Hotspot> empty = engine.scanText(screen, 1, 80);
|
||||||
|
assertEquals(0, empty.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,265 @@
|
|||||||
|
package haus.nightmare.j3270.ui;
|
||||||
|
|
||||||
|
import haus.nightmare.j3270.config.Settings;
|
||||||
|
import haus.nightmare.lib3270j.ConnectionConfig;
|
||||||
|
import haus.nightmare.lib3270j.ConnectionState;
|
||||||
|
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||||
|
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.awt.Cursor;
|
||||||
|
import java.awt.HeadlessException;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class SelectionHandlesAndTrimTest {
|
||||||
|
|
||||||
|
private boolean origRemainAfterEdit;
|
||||||
|
private boolean origSizingHandles;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() {
|
||||||
|
origRemainAfterEdit = Settings.getTrimRectRemainAfterEdit();
|
||||||
|
origSizingHandles = Settings.getTrimRectSizingHandles();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void tearDown() {
|
||||||
|
Settings.setTrimRectRemainAfterEdit(origRemainAfterEdit);
|
||||||
|
Settings.setTrimRectSizingHandles(origSizingHandles);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TerminalPanel createTestPanel() {
|
||||||
|
TerminalPanel panel = new TerminalPanel();
|
||||||
|
panel.setSize(800, 600);
|
||||||
|
panel.cellWidth = 10;
|
||||||
|
panel.cellHeight = 20;
|
||||||
|
|
||||||
|
Telnet3270Client client = new Telnet3270Client(new ConnectionConfig("localhost", 23));
|
||||||
|
client.getTelnetFSM().setConnectionState(ConnectionState.CONNECTED_3270);
|
||||||
|
ScreenBuffer sb = client.getScreenBuffer();
|
||||||
|
sb.erase(false);
|
||||||
|
panel.setClient(client);
|
||||||
|
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Trailing whitespace is trimmed on copied lines in block selection mode")
|
||||||
|
public void testBlockSelectionTrailingWhitespaceTrim() {
|
||||||
|
try {
|
||||||
|
TerminalPanel panel = createTestPanel();
|
||||||
|
ScreenBuffer sb = panel.getClient().getScreenBuffer();
|
||||||
|
int cols = sb.getCols();
|
||||||
|
|
||||||
|
// Populate row 0: "HELLO "
|
||||||
|
String text0 = "HELLO";
|
||||||
|
for (int i = 0; i < text0.length(); i++) {
|
||||||
|
sb.getCell(i).ucs4 = text0.charAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate row 1: "WORLD 123 "
|
||||||
|
String text1 = "WORLD 123";
|
||||||
|
for (int i = 0; i < text1.length(); i++) {
|
||||||
|
sb.getCell(cols + i).ucs4 = text1.charAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select rectangle from (row 0, col 0) to (row 1, col 20)
|
||||||
|
panel.setBlockSelectMode(true);
|
||||||
|
panel.setSelectionRange(0, cols + 20);
|
||||||
|
|
||||||
|
String copied = panel.getSelectedText();
|
||||||
|
// Should be "HELLO\nWORLD 123" without the trailing spaces up to col 20
|
||||||
|
assertEquals("HELLO\nWORLD 123", copied);
|
||||||
|
} catch (HeadlessException ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Trailing whitespace is trimmed on copied lines in stream selection mode")
|
||||||
|
public void testStreamSelectionTrailingWhitespaceTrim() {
|
||||||
|
try {
|
||||||
|
TerminalPanel panel = createTestPanel();
|
||||||
|
ScreenBuffer sb = panel.getClient().getScreenBuffer();
|
||||||
|
int cols = sb.getCols();
|
||||||
|
|
||||||
|
// Populate row 0: "LINE1 "
|
||||||
|
String text0 = "LINE1";
|
||||||
|
for (int i = 0; i < text0.length(); i++) {
|
||||||
|
sb.getCell(i).ucs4 = text0.charAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate row 1: "LINE2"
|
||||||
|
String text1 = "LINE2";
|
||||||
|
for (int i = 0; i < text1.length(); i++) {
|
||||||
|
sb.getCell(cols + i).ucs4 = text1.charAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
panel.setBlockSelectMode(false);
|
||||||
|
panel.setTrimRectSizingHandles(false);
|
||||||
|
// Select from row 0, col 0 to row 1, col 10
|
||||||
|
panel.setSelectionRange(0, cols + 10);
|
||||||
|
|
||||||
|
String copied = panel.getSelectedText();
|
||||||
|
assertEquals("LINE1\nLINE2", copied);
|
||||||
|
} catch (HeadlessException ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Drag handle detection identifies 8 resize handles, MOVE inside box, and NONE outside")
|
||||||
|
public void testDragHandleDetection() {
|
||||||
|
try {
|
||||||
|
TerminalPanel panel = createTestPanel();
|
||||||
|
panel.setBlockSelectMode(true);
|
||||||
|
panel.setTrimRectSizingHandles(true);
|
||||||
|
|
||||||
|
// Without selection, should return NONE
|
||||||
|
assertEquals(TerminalPanel.DragHandle.NONE, panel.getDragHandleAt(100, 100));
|
||||||
|
|
||||||
|
// Select rectangle: rows 2 to 5, cols 10 to 20
|
||||||
|
ScreenBuffer sb = panel.getClient().getScreenBuffer();
|
||||||
|
int cols = sb.getCols();
|
||||||
|
panel.setSelectionRange(2 * cols + 10, 5 * cols + 20);
|
||||||
|
|
||||||
|
int ox = panel.getRenderOffsetX();
|
||||||
|
int oy = panel.getRenderOffsetY();
|
||||||
|
int cw = panel.cellWidth;
|
||||||
|
int ch = panel.cellHeight;
|
||||||
|
|
||||||
|
int x1 = ox + 10 * cw;
|
||||||
|
int y1 = oy + 2 * ch;
|
||||||
|
int x2 = ox + 21 * cw;
|
||||||
|
int y2 = oy + 6 * ch;
|
||||||
|
int xm = (x1 + x2) / 2;
|
||||||
|
int ym = (y1 + y2) / 2;
|
||||||
|
|
||||||
|
// Test 8 handles
|
||||||
|
assertEquals(TerminalPanel.DragHandle.NW, panel.getDragHandleAt(x1, y1));
|
||||||
|
assertEquals(TerminalPanel.DragHandle.N, panel.getDragHandleAt(xm, y1));
|
||||||
|
assertEquals(TerminalPanel.DragHandle.NE, panel.getDragHandleAt(x2, y1));
|
||||||
|
assertEquals(TerminalPanel.DragHandle.E, panel.getDragHandleAt(x2, ym));
|
||||||
|
assertEquals(TerminalPanel.DragHandle.SE, panel.getDragHandleAt(x2, y2));
|
||||||
|
assertEquals(TerminalPanel.DragHandle.S, panel.getDragHandleAt(xm, y2));
|
||||||
|
assertEquals(TerminalPanel.DragHandle.SW, panel.getDragHandleAt(x1, y2));
|
||||||
|
assertEquals(TerminalPanel.DragHandle.W, panel.getDragHandleAt(x1, ym));
|
||||||
|
|
||||||
|
// Test inside box -> MOVE
|
||||||
|
assertEquals(TerminalPanel.DragHandle.MOVE, panel.getDragHandleAt(xm, ym));
|
||||||
|
|
||||||
|
// Test far outside -> NONE
|
||||||
|
assertEquals(TerminalPanel.DragHandle.NONE, panel.getDragHandleAt(0, 0));
|
||||||
|
assertEquals(TerminalPanel.DragHandle.NONE, panel.getDragHandleAt(x2 + 50, y2 + 50));
|
||||||
|
} catch (HeadlessException ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Cursor mapping for all drag handles")
|
||||||
|
public void testCursorForDragHandles() {
|
||||||
|
try {
|
||||||
|
TerminalPanel panel = createTestPanel();
|
||||||
|
assertEquals(Cursor.NW_RESIZE_CURSOR, panel.getCursorForDragHandle(TerminalPanel.DragHandle.NW).getType());
|
||||||
|
assertEquals(Cursor.N_RESIZE_CURSOR, panel.getCursorForDragHandle(TerminalPanel.DragHandle.N).getType());
|
||||||
|
assertEquals(Cursor.NE_RESIZE_CURSOR, panel.getCursorForDragHandle(TerminalPanel.DragHandle.NE).getType());
|
||||||
|
assertEquals(Cursor.E_RESIZE_CURSOR, panel.getCursorForDragHandle(TerminalPanel.DragHandle.E).getType());
|
||||||
|
assertEquals(Cursor.SE_RESIZE_CURSOR, panel.getCursorForDragHandle(TerminalPanel.DragHandle.SE).getType());
|
||||||
|
assertEquals(Cursor.S_RESIZE_CURSOR, panel.getCursorForDragHandle(TerminalPanel.DragHandle.S).getType());
|
||||||
|
assertEquals(Cursor.SW_RESIZE_CURSOR, panel.getCursorForDragHandle(TerminalPanel.DragHandle.SW).getType());
|
||||||
|
assertEquals(Cursor.W_RESIZE_CURSOR, panel.getCursorForDragHandle(TerminalPanel.DragHandle.W).getType());
|
||||||
|
assertEquals(Cursor.MOVE_CURSOR, panel.getCursorForDragHandle(TerminalPanel.DragHandle.MOVE).getType());
|
||||||
|
assertEquals(Cursor.DEFAULT_CURSOR, panel.getCursorForDragHandle(TerminalPanel.DragHandle.NONE).getType());
|
||||||
|
} catch (HeadlessException ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("trimRectRemainAfterEdit controls whether selection is retained after copy")
|
||||||
|
public void testTrimRectRemainAfterEdit() {
|
||||||
|
try {
|
||||||
|
TerminalPanel panel = createTestPanel();
|
||||||
|
ScreenBuffer sb = panel.getClient().getScreenBuffer();
|
||||||
|
|
||||||
|
sb.getCell(0).ucs4 = 'A';
|
||||||
|
panel.setSelectionRange(0, 5);
|
||||||
|
assertTrue(panel.hasSelection());
|
||||||
|
|
||||||
|
// By default (false), copySelection() should clear the selection
|
||||||
|
Settings.setTrimRectRemainAfterEdit(false);
|
||||||
|
panel.copySelection();
|
||||||
|
assertFalse(panel.hasSelection(), "Selection should be cleared after copy when remainAfterEdit is false");
|
||||||
|
|
||||||
|
// When set to true, copySelection() should preserve the selection
|
||||||
|
Settings.setTrimRectRemainAfterEdit(true);
|
||||||
|
panel.setSelectionRange(0, 5);
|
||||||
|
assertTrue(panel.hasSelection());
|
||||||
|
panel.copySelection();
|
||||||
|
assertTrue(panel.hasSelection(), "Selection should remain active after copy when remainAfterEdit is true");
|
||||||
|
} catch (HeadlessException ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("cutSelection clears unprotected cells, sets FA_MODIFY, and respects remainAfterEdit")
|
||||||
|
public void testCutSelectionBehavior() {
|
||||||
|
try {
|
||||||
|
TerminalPanel panel = createTestPanel();
|
||||||
|
ScreenBuffer sb = panel.getClient().getScreenBuffer();
|
||||||
|
|
||||||
|
// Field at 0: Unprotected field
|
||||||
|
sb.setCellFA(0, (byte) (FA_PRINTABLE | FA_MODIFY));
|
||||||
|
String data = "TESTDATA";
|
||||||
|
for (int i = 0; i < data.length(); i++) {
|
||||||
|
sb.getCell(1 + i).ucs4 = data.charAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select the field contents
|
||||||
|
panel.setSelectionRange(1, 8);
|
||||||
|
assertTrue(panel.hasSelection());
|
||||||
|
|
||||||
|
Settings.setTrimRectRemainAfterEdit(false);
|
||||||
|
panel.cutSelection();
|
||||||
|
|
||||||
|
// Characters should be replaced with spaces
|
||||||
|
for (int i = 1; i <= 8; i++) {
|
||||||
|
assertEquals(' ', sb.getCell(i).ucs4, "Cell " + i + " should be erased to space");
|
||||||
|
}
|
||||||
|
// FA_MODIFY should remain or be set
|
||||||
|
assertTrue((sb.getCell(0).ec & FA_MODIFY) != 0, "FA_MODIFY should be set on field attribute");
|
||||||
|
assertFalse(panel.hasSelection(), "Selection should be cleared when remainAfterEdit is false");
|
||||||
|
} catch (HeadlessException ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("KeyBindings exportKmp generates HoD format and round-trips through parseKmp")
|
||||||
|
public void testKeyBindingsExportAndParseRoundTrip() throws Exception {
|
||||||
|
Map<String, String> keymap = new LinkedHashMap<>();
|
||||||
|
keymap.put("ENTER", "ENTER");
|
||||||
|
keymap.put("shift TAB", "TAB");
|
||||||
|
keymap.put("CLEAR", "PAUSE");
|
||||||
|
keymap.put("PF1", "F1");
|
||||||
|
keymap.put("PF13", "shift F1");
|
||||||
|
keymap.put("ESCAPE", "ESCAPE");
|
||||||
|
|
||||||
|
StringWriter writer = new StringWriter();
|
||||||
|
KeyBindings.exportKmp(writer, keymap);
|
||||||
|
String exported = writer.toString();
|
||||||
|
|
||||||
|
assertTrue(exported.contains("[KeyRemap]"), "Exported KMP must contain [KeyRemap] section header");
|
||||||
|
assertTrue(exported.contains("[enter]"), "Exported KMP must contain HoD mnemonics");
|
||||||
|
assertTrue(exported.contains("[backtab]"), "Exported KMP must contain [backtab]");
|
||||||
|
|
||||||
|
// Roundtrip parse
|
||||||
|
Map<String, String> parsed = KeyBindings.parseKmp(new StringReader(exported));
|
||||||
|
assertNotNull(parsed);
|
||||||
|
assertEquals("ENTER", parsed.get("ENTER"));
|
||||||
|
assertEquals("TAB", parsed.get("shift TAB"));
|
||||||
|
assertEquals("PAUSE", parsed.get("CLEAR"));
|
||||||
|
assertEquals("F1", parsed.get("PF1"));
|
||||||
|
assertEquals("shift F1", parsed.get("PF13"));
|
||||||
|
assertEquals("ESCAPE", parsed.get("ESCAPE"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -59,4 +59,49 @@ public class StatusBarTest {
|
|||||||
// Ignored in headless environments
|
// Ignored in headless environments
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStatusBarXSystemAndXProt() {
|
||||||
|
try {
|
||||||
|
ConnectionConfig config = new ConnectionConfig("mvs.example.com", 23, TerminalModel.IBM_3279_2, false);
|
||||||
|
Telnet3270Client client = new Telnet3270Client(config);
|
||||||
|
client.getTelnetFSM().setConnectionState(haus.nightmare.lib3270j.ConnectionState.CONNECTED_3270);
|
||||||
|
|
||||||
|
StatusBar statusBar = new StatusBar();
|
||||||
|
statusBar.setClient(client, null);
|
||||||
|
statusBar.updateStatus();
|
||||||
|
|
||||||
|
// Initially not locked
|
||||||
|
assertEquals("", statusBar.getLockStatusText());
|
||||||
|
|
||||||
|
// 1. Setup protected field and type into it -> triggers X PROT
|
||||||
|
client.getScreenBuffer().setFieldAttribute(0, (byte) haus.nightmare.lib3270j.protocol.DS3270Constants.FA_PROTECT);
|
||||||
|
client.getScreenBuffer().setCursorAddress(1);
|
||||||
|
client.typeCharacter('A');
|
||||||
|
statusBar.updateStatus();
|
||||||
|
assertEquals("X PROT", statusBar.getLockStatusText());
|
||||||
|
|
||||||
|
// 2. Reset -> clears X PROT
|
||||||
|
client.reset();
|
||||||
|
statusBar.updateStatus();
|
||||||
|
assertEquals("", statusBar.getLockStatusText());
|
||||||
|
|
||||||
|
// 3. Send Enter -> triggers X SYSTEM
|
||||||
|
client.sendEnter();
|
||||||
|
statusBar.updateStatus();
|
||||||
|
assertEquals("X SYSTEM", statusBar.getLockStatusText());
|
||||||
|
|
||||||
|
// 4. Host unlocks keyboard
|
||||||
|
client.getInputProcessor().setKeyboardLocked(false);
|
||||||
|
statusBar.updateStatus();
|
||||||
|
assertEquals("", statusBar.getLockStatusText());
|
||||||
|
|
||||||
|
// 5. Subsequent Enter -> triggers X SYSTEM again
|
||||||
|
client.sendEnter();
|
||||||
|
statusBar.updateStatus();
|
||||||
|
assertEquals("X SYSTEM", statusBar.getLockStatusText());
|
||||||
|
} catch (HeadlessException e) {
|
||||||
|
// Ignored in headless environments
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package haus.nightmare.j3270.ui;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.ConnectionConfig;
|
||||||
|
import haus.nightmare.lib3270j.ConnectionState;
|
||||||
|
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||||
|
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.awt.HeadlessException;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.awt.event.MouseListener;
|
||||||
|
import java.awt.event.MouseMotionListener;
|
||||||
|
|
||||||
|
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class TerminalPanelSelectionTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Selection drag over protected/intensified fields is preserved on mouse release (Bugs 3 & 4)")
|
||||||
|
public void testSelectionOverProtectedFieldsAndReleasePreservation() {
|
||||||
|
try {
|
||||||
|
TerminalPanel panel = new TerminalPanel();
|
||||||
|
panel.setSize(800, 600);
|
||||||
|
|
||||||
|
Telnet3270Client client = new Telnet3270Client(new ConnectionConfig("localhost", 23));
|
||||||
|
client.getTelnetFSM().setConnectionState(ConnectionState.CONNECTED_3270);
|
||||||
|
|
||||||
|
ScreenBuffer sb = client.getScreenBuffer();
|
||||||
|
sb.erase(false);
|
||||||
|
|
||||||
|
// Put a protected, intensified selectable field at pos 0 (ISPF header simulation)
|
||||||
|
sb.setCellFA(0, (byte) (FA_PRINTABLE | FA_PROTECT | FA_INT_HIGH_SEL));
|
||||||
|
String headerText = " ISPF PRIMARY OPTION MENU";
|
||||||
|
for (int i = 0; i < headerText.length(); i++) {
|
||||||
|
sb.getCell(1 + i).ucs4 = headerText.charAt(i);
|
||||||
|
sb.getCell(1 + i).ec = (byte) (0x40 + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
panel.setClient(client);
|
||||||
|
|
||||||
|
// Cell dimensions in panel
|
||||||
|
int cellW = panel.cellWidth > 0 ? panel.cellWidth : 10;
|
||||||
|
int cellH = panel.cellHeight > 0 ? panel.cellHeight : 18;
|
||||||
|
int ox = panel.getRenderOffsetX();
|
||||||
|
int oy = panel.getRenderOffsetY();
|
||||||
|
|
||||||
|
MouseListener[] mouseListeners = panel.getMouseListeners();
|
||||||
|
MouseMotionListener[] motionListeners = panel.getMouseMotionListeners();
|
||||||
|
assertTrue(mouseListeners.length > 0);
|
||||||
|
assertTrue(motionListeners.length > 0);
|
||||||
|
|
||||||
|
MouseListener ml = mouseListeners[0];
|
||||||
|
MouseMotionListener mml = motionListeners[0];
|
||||||
|
|
||||||
|
// 1. Mouse Pressed at cell (row 0, col 2)
|
||||||
|
int startX = ox + 2 * cellW + cellW / 2;
|
||||||
|
int startY = oy + cellH / 2;
|
||||||
|
MouseEvent pressEvent = new MouseEvent(panel, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(),
|
||||||
|
0, startX, startY, 1, false, MouseEvent.BUTTON1);
|
||||||
|
ml.mousePressed(pressEvent);
|
||||||
|
|
||||||
|
// 2. Drag to cell (row 0, col 15)
|
||||||
|
int dragX = ox + 15 * cellW + cellW / 2;
|
||||||
|
int dragY = oy + cellH / 2;
|
||||||
|
MouseEvent dragEvent = new MouseEvent(panel, MouseEvent.MOUSE_DRAGGED, System.currentTimeMillis(),
|
||||||
|
0, dragX, dragY, 1, false, MouseEvent.BUTTON1);
|
||||||
|
mml.mouseDragged(dragEvent);
|
||||||
|
|
||||||
|
assertTrue(panel.hasSelection(), "Drag over protected field must create active selection (Bug 3)");
|
||||||
|
|
||||||
|
// 3. Mouse Released at cell (row 0, col 15)
|
||||||
|
MouseEvent releaseEvent = new MouseEvent(panel, MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(),
|
||||||
|
0, dragX, dragY, 1, false, MouseEvent.BUTTON1);
|
||||||
|
ml.mouseReleased(releaseEvent);
|
||||||
|
|
||||||
|
// Selection MUST NOT be dropped upon mouse release! (Bug 4)
|
||||||
|
assertTrue(panel.hasSelection(), "Selection must remain active after mouse release (Bug 4)");
|
||||||
|
String selected = panel.getSelectedText();
|
||||||
|
assertNotNull(selected);
|
||||||
|
assertTrue(selected.length() > 0, "Selected text must not be empty");
|
||||||
|
|
||||||
|
// 4. A subsequent single click without drag should clear the selection and move the cursor
|
||||||
|
int clickX = ox + 5 * cellW + cellW / 2;
|
||||||
|
int clickY = oy + cellH / 2;
|
||||||
|
MouseEvent clickPress = new MouseEvent(panel, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(),
|
||||||
|
0, clickX, clickY, 1, false, MouseEvent.BUTTON1);
|
||||||
|
ml.mousePressed(clickPress);
|
||||||
|
MouseEvent clickRelease = new MouseEvent(panel, MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(),
|
||||||
|
0, clickX, clickY, 1, false, MouseEvent.BUTTON1);
|
||||||
|
ml.mouseReleased(clickRelease);
|
||||||
|
|
||||||
|
assertFalse(panel.hasSelection(), "Single click must clear previous selection");
|
||||||
|
assertEquals(5, sb.getCursorAddress(), "Single click must position cursor at clicked cell");
|
||||||
|
|
||||||
|
} catch (HeadlessException e) {
|
||||||
|
// Handled gracefully in headless environments
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Mouse press and release on TerminalPanel requests focus")
|
||||||
|
public void testTerminalPanelMousePressRequestsFocus() {
|
||||||
|
try {
|
||||||
|
TerminalPanel panel = new TerminalPanel();
|
||||||
|
assertTrue(panel.isFocusable());
|
||||||
|
|
||||||
|
MouseListener[] mouseListeners = panel.getMouseListeners();
|
||||||
|
assertTrue(mouseListeners.length > 0);
|
||||||
|
|
||||||
|
MouseEvent pressEvent = new MouseEvent(panel, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(),
|
||||||
|
0, 50, 50, 1, false, MouseEvent.BUTTON1);
|
||||||
|
mouseListeners[0].mousePressed(pressEvent);
|
||||||
|
|
||||||
|
MouseEvent releaseEvent = new MouseEvent(panel, MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(),
|
||||||
|
0, 50, 50, 1, false, MouseEvent.BUTTON1);
|
||||||
|
mouseListeners[0].mouseReleased(releaseEvent);
|
||||||
|
} catch (HeadlessException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.ibm.eNetwork.ECL;
|
||||||
|
|
||||||
|
import com.ibm.eNetwork.ECL.event.ECLEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Communications event matching IBM Host On-Demand ECLCommEvent.
|
||||||
|
*/
|
||||||
|
public class ECLCommEvent extends ECLEvent {
|
||||||
|
|
||||||
|
private final boolean status;
|
||||||
|
private final int eventType;
|
||||||
|
|
||||||
|
public ECLCommEvent(Object object, boolean status) {
|
||||||
|
super(object);
|
||||||
|
this.status = status;
|
||||||
|
this.eventType = status ? 1 : 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLCommEvent(Object object, int eventType, boolean status) {
|
||||||
|
super(object);
|
||||||
|
this.eventType = eventType;
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean connected() {
|
||||||
|
return this.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnected() {
|
||||||
|
return this.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEventType() {
|
||||||
|
return this.eventType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLConnection GetConnection() {
|
||||||
|
Object src = GetSource();
|
||||||
|
if (src instanceof ECLConnection) return (ECLConnection) src;
|
||||||
|
if (src instanceof haus.nightmare.lib3270j.ecl.ECLConnection) {
|
||||||
|
return new ECLConnection((haus.nightmare.lib3270j.ecl.ECLConnection) src);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.ibm.eNetwork.ECL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Communications notification interface matching IBM Host On-Demand ECLCommNotify.
|
||||||
|
*/
|
||||||
|
public interface ECLCommNotify {
|
||||||
|
void NotifyEvent(ECLConnection conn, boolean connected);
|
||||||
|
default void NotifyStop(ECLConnection conn, int reason) {}
|
||||||
|
default void NotifyError(ECLConnection conn, ECLErr err) {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package com.ibm.eNetwork.ECL;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Communications and connection management facade matching IBM Host On-Demand ECLConnection.
|
||||||
|
*/
|
||||||
|
public class ECLConnection extends haus.nightmare.lib3270j.ecl.ECLConnection implements ECLConstants {
|
||||||
|
|
||||||
|
private final List<ECLCommNotify> commNotifies = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
|
public ECLConnection() {
|
||||||
|
super();
|
||||||
|
setupListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLConnection(Properties props) {
|
||||||
|
super(props);
|
||||||
|
setupListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLConnection(String host, int port) {
|
||||||
|
super(host, port);
|
||||||
|
setupListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLConnection(haus.nightmare.lib3270j.ecl.ECLSession session) {
|
||||||
|
super(session);
|
||||||
|
setupListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLConnection(haus.nightmare.lib3270j.ecl.ECLConnection delegate) {
|
||||||
|
super(delegate != null ? delegate.getSession() : null);
|
||||||
|
setupListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupListener() {
|
||||||
|
RegisterCommEvent(new haus.nightmare.lib3270j.ecl.ECLCommListener() {
|
||||||
|
@Override
|
||||||
|
public void CommNotifyEvent(haus.nightmare.lib3270j.ecl.ECLCommEvent event) {
|
||||||
|
boolean conn = event != null && event.isConnected();
|
||||||
|
for (ECLCommNotify n : commNotifies) {
|
||||||
|
try {
|
||||||
|
n.NotifyEvent(ECLConnection.this, conn);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterCommEvent(ECLCommNotify notify) {
|
||||||
|
if (notify != null && !commNotifies.contains(notify)) {
|
||||||
|
commNotifies.add(notify);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerCommEvent(ECLCommNotify notify) { RegisterCommEvent(notify); }
|
||||||
|
|
||||||
|
public void UnregisterCommEvent(ECLCommNotify notify) {
|
||||||
|
if (notify != null) {
|
||||||
|
commNotifies.remove(notify);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterCommEvent(ECLCommNotify notify) { UnregisterCommEvent(notify); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void StartCommunication() throws IOException {
|
||||||
|
super.StartCommunication();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void StopCommunication() {
|
||||||
|
super.StopCommunication();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
package com.ibm.eNetwork.ECL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host Access Class Library (HACL) common constants.
|
||||||
|
* Conforms 1:1 to IBM Host On-Demand com.ibm.eNetwork.ECL.ECLConstants.
|
||||||
|
*/
|
||||||
|
public interface ECLConstants extends haus.nightmare.lib3270j.ecl.ECLConstants {
|
||||||
|
|
||||||
|
// Presentation space logical planes (HoD bitmask values)
|
||||||
|
int TEXT_PLANE = 1;
|
||||||
|
int COLOR_PLANE = 2;
|
||||||
|
int DBCS_PLANE = 4;
|
||||||
|
int GRID_PLANE = 8;
|
||||||
|
int FIELD_PLANE = 16;
|
||||||
|
int EXFIELD_PLANE = 32;
|
||||||
|
int NLS_PLANE = 64;
|
||||||
|
int HOST_PLANE = 128;
|
||||||
|
int RAWTEXT_PLANE = 256;
|
||||||
|
int COLOR_ATTRIBUTES_PLANE = 512;
|
||||||
|
int ALL_PLANES = 319;
|
||||||
|
|
||||||
|
// Search directions
|
||||||
|
int SEARCH_FORWARD = 1;
|
||||||
|
int SEARCH_BACKWARD = 2;
|
||||||
|
|
||||||
|
// Field filters
|
||||||
|
int ALL_FIELDS = 0;
|
||||||
|
int MODIFIED_FIELD = 1;
|
||||||
|
int ALPHANUM_FIELD = 2;
|
||||||
|
int HILIGHT_FIELD = 16;
|
||||||
|
int PROTECTED_FIELD = 32;
|
||||||
|
int DISPLAYABLE_FIELD = 64;
|
||||||
|
int PENDETECTABLE_FIELD = 128;
|
||||||
|
int UNMODIFIED_FIELD = 256;
|
||||||
|
int NUMERIC_FIELD = 512;
|
||||||
|
int LOLIGHT_FIELD = 4096;
|
||||||
|
int UNPROTECTED_FIELD = 8192;
|
||||||
|
int NON_DISPLAYABLE_FIELD = 16384;
|
||||||
|
int NON_PENDETECTABLE_FIELD = 32768;
|
||||||
|
|
||||||
|
// Standard Mnemonics Constants
|
||||||
|
String ENTER_STR = "[enter]";
|
||||||
|
String CLEAR_STR = "[clear]";
|
||||||
|
String RESET_STR = "[reset]";
|
||||||
|
String TAB_STR = "[tab]";
|
||||||
|
String FWDTAB_STR = "[tab]";
|
||||||
|
String BACKTAB_STR = "[backtab]";
|
||||||
|
String BACKSP_STR = "[backspace]";
|
||||||
|
String DELCHAR_STR = "[delete]";
|
||||||
|
String INSERT_STR = "[insert]";
|
||||||
|
String HOME_STR = "[home]";
|
||||||
|
String NEWLINE_STR = "[newline]";
|
||||||
|
String ENDLINE_STR = "[eof]";
|
||||||
|
String ERASEEOF_STR = "[eraseeof]";
|
||||||
|
String ERASEFLD_STR = "[erasefld]";
|
||||||
|
String ERASEINPUT_STR = "[erinp]";
|
||||||
|
String ATTN_STR = "[attn]";
|
||||||
|
String SYSREQ_STR = "[sysreq]";
|
||||||
|
String DUP_STR = "[dup]";
|
||||||
|
String FLDMRK_STR = "[fieldmark]";
|
||||||
|
String PA1_STR = "[pa1]";
|
||||||
|
String PA2_STR = "[pa2]";
|
||||||
|
String PA3_STR = "[pa3]";
|
||||||
|
String CURUP_STR = "[up]";
|
||||||
|
String CURDOWN_STR = "[down]";
|
||||||
|
String CURLEFT_STR = "[left]";
|
||||||
|
String CURRIGHT_STR = "[right]";
|
||||||
|
String PAGEUP_STR = "[pageup]";
|
||||||
|
String PAGEDWN_STR = "[pagedn]";
|
||||||
|
String COPY_STR = "[copy]";
|
||||||
|
String PASTE_STR = "[paste]";
|
||||||
|
String CUT_STR = "[cut]";
|
||||||
|
String SELECTALL_STR = "[selectall]";
|
||||||
|
|
||||||
|
// Function keys F1 - F24
|
||||||
|
String F1_STR = "[pf1]";
|
||||||
|
String F2_STR = "[pf2]";
|
||||||
|
String F3_STR = "[pf3]";
|
||||||
|
String F4_STR = "[pf4]";
|
||||||
|
String F5_STR = "[pf5]";
|
||||||
|
String F6_STR = "[pf6]";
|
||||||
|
String F7_STR = "[pf7]";
|
||||||
|
String F8_STR = "[pf8]";
|
||||||
|
String F9_STR = "[pf9]";
|
||||||
|
String F10_STR = "[pf10]";
|
||||||
|
String F11_STR = "[pf11]";
|
||||||
|
String F12_STR = "[pf12]";
|
||||||
|
String F13_STR = "[pf13]";
|
||||||
|
String F14_STR = "[pf14]";
|
||||||
|
String F15_STR = "[pf15]";
|
||||||
|
String F16_STR = "[pf16]";
|
||||||
|
String F17_STR = "[pf17]";
|
||||||
|
String F18_STR = "[pf18]";
|
||||||
|
String F19_STR = "[pf19]";
|
||||||
|
String F20_STR = "[pf20]";
|
||||||
|
String F21_STR = "[pf21]";
|
||||||
|
String F22_STR = "[pf22]";
|
||||||
|
String F23_STR = "[pf23]";
|
||||||
|
String F24_STR = "[pf24]";
|
||||||
|
|
||||||
|
// Shift F1 - F24
|
||||||
|
String SHIFT_F1_STR = "[Spf1]";
|
||||||
|
String SHIFT_F2_STR = "[Spf2]";
|
||||||
|
String SHIFT_F3_STR = "[Spf3]";
|
||||||
|
String SHIFT_F4_STR = "[Spf4]";
|
||||||
|
String SHIFT_F5_STR = "[Spf5]";
|
||||||
|
String SHIFT_F6_STR = "[Spf6]";
|
||||||
|
String SHIFT_F7_STR = "[Spf7]";
|
||||||
|
String SHIFT_F8_STR = "[Spf8]";
|
||||||
|
String SHIFT_F9_STR = "[Spf9]";
|
||||||
|
String SHIFT_F10_STR = "[Spf10]";
|
||||||
|
String SHIFT_F11_STR = "[Spf11]";
|
||||||
|
String SHIFT_F12_STR = "[Spf12]";
|
||||||
|
String SHIFT_F13_STR = "[Spf13]";
|
||||||
|
String SHIFT_F14_STR = "[Spf14]";
|
||||||
|
String SHIFT_F15_STR = "[Spf15]";
|
||||||
|
String SHIFT_F16_STR = "[Spf16]";
|
||||||
|
String SHIFT_F17_STR = "[Spf17]";
|
||||||
|
String SHIFT_F18_STR = "[Spf18]";
|
||||||
|
String SHIFT_F19_STR = "[Spf19]";
|
||||||
|
String SHIFT_F20_STR = "[Spf20]";
|
||||||
|
|
||||||
|
// Field attributes
|
||||||
|
short ATTR_PROTECTED = 32;
|
||||||
|
short ATTR_ALPHANUM = 16;
|
||||||
|
short ATTR_DISPLAY1 = 8;
|
||||||
|
short ATTR_DISPLAY2 = 4;
|
||||||
|
short ATTR_MDT = 1;
|
||||||
|
|
||||||
|
// Extended attributes
|
||||||
|
short EXT_ATTR_3270_UNDERLINE = 192;
|
||||||
|
short EXT_ATTR_3270_REVERSE = 128;
|
||||||
|
short EXT_ATTR_3270_BLINK = 64;
|
||||||
|
short EXT_ATTR_3270_EBCDIC = 0;
|
||||||
|
|
||||||
|
// Timeout
|
||||||
|
int NO_TIMEOUT = -1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.ibm.eNetwork.ECL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host Access Class Library (HACL) ECLErr checked exception facade.
|
||||||
|
* Conforms 1:1 to IBM Host On-Demand com.ibm.eNetwork.ECL.ECLErr.
|
||||||
|
*/
|
||||||
|
public class ECLErr extends haus.nightmare.lib3270j.ecl.ECLErr {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public ECLErr() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLErr(String text) {
|
||||||
|
super(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLErr(String location, String msgNum) {
|
||||||
|
super(location, msgNum);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLErr(String location, String msgNum, String insert) {
|
||||||
|
super(location, msgNum, insert);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLErr(String location, String msgNum, String insert1, String insert2) {
|
||||||
|
super(location, msgNum, insert1, insert2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLErr(String location, String msgNum, String insert1, String insert2, boolean log) {
|
||||||
|
super(location, msgNum, insert1, insert2, log);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLErr(String location, String msgNum, String insert1, String insert2, String insert3) {
|
||||||
|
super(location, msgNum, insert1, insert2, insert3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLErr(String location, String msgNum, int reasonCode) {
|
||||||
|
super(location, msgNum, reasonCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLErr(String location, String msgNum, String insert1, int reasonCode) {
|
||||||
|
super(location, msgNum, insert1, reasonCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLErr(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLErr(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
package com.ibm.eNetwork.ECL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discrete 3270 field facade matching IBM Host On-Demand ECLField.
|
||||||
|
*/
|
||||||
|
public class ECLField extends haus.nightmare.lib3270j.ecl.ECLField implements ECLConstants {
|
||||||
|
|
||||||
|
private final haus.nightmare.lib3270j.ecl.ECLField delegate;
|
||||||
|
|
||||||
|
public ECLField(haus.nightmare.lib3270j.ecl.ECLField delegate) {
|
||||||
|
super(delegate != null ? delegate.getPS() : null,
|
||||||
|
delegate != null ? delegate.getStart() : 0,
|
||||||
|
delegate != null ? delegate.getDataStart() : 0,
|
||||||
|
delegate != null ? delegate.getEnd() : 0,
|
||||||
|
delegate != null ? delegate.getLength() : 0,
|
||||||
|
(byte) (delegate != null ? delegate.getAttribute() : 0));
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetStart() {
|
||||||
|
return delegate != null ? delegate.GetStart() + 1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStart() { return GetStart(); }
|
||||||
|
|
||||||
|
public int GetStartRow() {
|
||||||
|
return delegate != null ? delegate.GetStartRow() + 1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStartRow() { return GetStartRow(); }
|
||||||
|
|
||||||
|
public int GetStartCol() {
|
||||||
|
return delegate != null ? delegate.GetStartCol() + 1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStartCol() { return GetStartCol(); }
|
||||||
|
|
||||||
|
public int GetEnd() {
|
||||||
|
return delegate != null ? delegate.GetEnd() + 1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEnd() { return GetEnd(); }
|
||||||
|
|
||||||
|
public int GetEndRow() {
|
||||||
|
return delegate != null ? delegate.GetEndRow() + 1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEndRow() { return GetEndRow(); }
|
||||||
|
|
||||||
|
public int GetEndCol() {
|
||||||
|
return delegate != null ? delegate.GetEndCol() + 1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEndCol() { return GetEndCol(); }
|
||||||
|
|
||||||
|
public int GetLength() {
|
||||||
|
return delegate != null ? delegate.GetLength() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLength() { return GetLength(); }
|
||||||
|
|
||||||
|
public String GetString() {
|
||||||
|
return delegate != null ? delegate.getString() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString() { return GetString(); }
|
||||||
|
|
||||||
|
public int GetString(char[] buffer, int len) throws ECLErr {
|
||||||
|
if (buffer == null) throw new ECLErr("ECLField:GetString", "ECL0011", "1");
|
||||||
|
String text = GetString();
|
||||||
|
int count = Math.min(len, Math.min(buffer.length, text.length()));
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
buffer[i] = text.charAt(i);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetString(String text) throws ECLErr {
|
||||||
|
if (delegate != null) {
|
||||||
|
delegate.setText(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetText(String text) throws ECLErr {
|
||||||
|
SetString(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setText(String text) {
|
||||||
|
if (delegate != null) {
|
||||||
|
delegate.setText(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean IsModified() {
|
||||||
|
return delegate != null && delegate.isModified();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isModified() { return IsModified(); }
|
||||||
|
|
||||||
|
public boolean IsProtected() {
|
||||||
|
return delegate != null && delegate.isProtected();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isProtected() { return IsProtected(); }
|
||||||
|
|
||||||
|
public boolean IsNumeric() {
|
||||||
|
return delegate != null && delegate.isNumeric();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNumeric() { return IsNumeric(); }
|
||||||
|
|
||||||
|
public boolean IsHighIntensity() {
|
||||||
|
return delegate != null && delegate.isHighIntensity();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isHighIntensity() { return IsHighIntensity(); }
|
||||||
|
|
||||||
|
public boolean IsPenDetectable() {
|
||||||
|
return delegate != null && delegate.isPenDetectable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPenDetectable() { return IsPenDetectable(); }
|
||||||
|
|
||||||
|
public boolean IsDisplay() {
|
||||||
|
return delegate != null && delegate.isDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDisplay() { return IsDisplay(); }
|
||||||
|
|
||||||
|
public short GetAttribute() {
|
||||||
|
return delegate != null ? (short) (delegate.getAttribute() & 0xFF) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean IsDBCS() {
|
||||||
|
return delegate != null && delegate.isDBCS();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDBCS() { return IsDBCS(); }
|
||||||
|
|
||||||
|
public boolean IsDBCSOnly() {
|
||||||
|
return delegate != null && delegate.isDBCSOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDBCSOnly() { return IsDBCSOnly(); }
|
||||||
|
|
||||||
|
public haus.nightmare.lib3270j.ecl.ECLField getDelegate() { return delegate; }
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package com.ibm.eNetwork.ECL;
|
||||||
|
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection of 3270 fields matching IBM Host On-Demand ECLFieldList.
|
||||||
|
*/
|
||||||
|
public class ECLFieldList extends Vector<ECLField> implements ECLConstants {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final haus.nightmare.lib3270j.ecl.ECLFieldList delegate;
|
||||||
|
private final ECLPS ps;
|
||||||
|
|
||||||
|
public ECLFieldList(haus.nightmare.lib3270j.ecl.ECLFieldList delegate, ECLPS ps) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.ps = ps;
|
||||||
|
refreshFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLFieldList(haus.nightmare.lib3270j.ecl.ECLFieldList delegate) {
|
||||||
|
this(delegate, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshFields() {
|
||||||
|
clear();
|
||||||
|
if (delegate != null) {
|
||||||
|
delegate.refresh();
|
||||||
|
for (haus.nightmare.lib3270j.ecl.ECLField f : delegate.getFields()) {
|
||||||
|
add(new ECLField(f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Refresh() {
|
||||||
|
refreshFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Refresh(int planes) throws ECLErr {
|
||||||
|
refreshFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refresh() {
|
||||||
|
refreshFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetFieldCount() {
|
||||||
|
return size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFieldCount() { return GetFieldCount(); }
|
||||||
|
|
||||||
|
public ECLField GetFirstField() {
|
||||||
|
return GetFirstField(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLField GetFirstField(int filter) {
|
||||||
|
for (ECLField f : this) {
|
||||||
|
if (matchesFilter(f, filter)) return f;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLField GetNextField(ECLField current) {
|
||||||
|
return GetNextField(current, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLField GetNextField(ECLField current, int filter) {
|
||||||
|
if (current == null) return GetFirstField(filter);
|
||||||
|
int idx = indexOf(current);
|
||||||
|
if (idx >= 0) {
|
||||||
|
for (int i = idx + 1; i < size(); i++) {
|
||||||
|
ECLField f = get(i);
|
||||||
|
if (matchesFilter(f, filter)) return f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLField FindField(int pos) throws ECLErr {
|
||||||
|
if (ps != null && (pos < 1 || pos > ps.GetSize())) {
|
||||||
|
throw new ECLErr("ECLFieldList:FindField", "ECL0010", "1", String.valueOf(pos));
|
||||||
|
}
|
||||||
|
for (ECLField f : this) {
|
||||||
|
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 row, int col) throws ECLErr {
|
||||||
|
if (ps == null) return null;
|
||||||
|
int pos = ps.ConvertRowColToPos(row, col);
|
||||||
|
return FindField(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean matchesFilter(ECLField f, int filter) {
|
||||||
|
if (filter == 0 || filter == ALL_FIELDS) return true;
|
||||||
|
if ((filter & MODIFIED_FIELD) != 0 && !f.IsModified()) return false;
|
||||||
|
if ((filter & PROTECTED_FIELD) != 0 && !f.IsProtected()) return false;
|
||||||
|
if ((filter & UNPROTECTED_FIELD) != 0 && f.IsProtected()) return false;
|
||||||
|
if ((filter & NUMERIC_FIELD) != 0 && !f.IsNumeric()) return false;
|
||||||
|
if ((filter & HILIGHT_FIELD) != 0 && !f.IsHighIntensity()) return false;
|
||||||
|
if ((filter & DISPLAYABLE_FIELD) != 0 && !f.IsDisplay()) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.ibm.eNetwork.ECL;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Host Print Session facade matching IBM Host On-Demand ECLHostPrintSession.
|
||||||
|
*/
|
||||||
|
public class ECLHostPrintSession extends ECLSession {
|
||||||
|
|
||||||
|
public static final String SESSION_PRINT_SESSION_LABEL_DEFAULT = "";
|
||||||
|
public static final String SESSION_PRINT_SESSION_TYPE_DEFAULT = "5";
|
||||||
|
public static final String SESSION_PRINT_SESSION_HOST_DEFAULT = "";
|
||||||
|
public static final String SESSION_PRINT_SESSION_HOST_PORT_DEFAULT = "23";
|
||||||
|
public static final String SESSION_PRINT_SESSION_LU_NAME_DEFAULT = "";
|
||||||
|
public static final String SESSION_PRINT_SESSION_AUTOCONNECT_DEFAULT = "true";
|
||||||
|
public static final String SESSION_PRINT_SESSION_AUTORECONNECT_DEFAULT = "true";
|
||||||
|
public static final String SESSION_LU_TYPE = "printerLUType";
|
||||||
|
|
||||||
|
public ECLHostPrintSession(Properties properties) throws ECLErr {
|
||||||
|
super(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLHostPrintSession() throws ECLErr {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancelPrintJob() {
|
||||||
|
ECLPS ps = GetPS();
|
||||||
|
if (ps != null) {
|
||||||
|
try {
|
||||||
|
ps.SendKeys("[cancelpjob]");
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CancelPrintJob() {
|
||||||
|
cancelPrintJob();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package com.ibm.eNetwork.ECL;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Operator Information Area facade matching IBM Host On-Demand ECLOIA.
|
||||||
|
*/
|
||||||
|
public class ECLOIA implements ECLConstants {
|
||||||
|
|
||||||
|
private final haus.nightmare.lib3270j.ecl.ECLOIA delegate;
|
||||||
|
private final List<ECLOIANotify> oiaNotifies = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
|
public ECLOIA(haus.nightmare.lib3270j.ecl.ECLOIA delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
if (delegate != null) {
|
||||||
|
delegate.RegisterOIAEvent(new haus.nightmare.lib3270j.ecl.ECLOIAListener() {
|
||||||
|
@Override
|
||||||
|
public void OIANotifyEvent(haus.nightmare.lib3270j.ecl.ECLOIAEvent event) {
|
||||||
|
ECLOIAEvent facadeEvt = new ECLOIAEvent(event);
|
||||||
|
for (ECLOIANotify n : oiaNotifies) {
|
||||||
|
try {
|
||||||
|
n.OIANotifyEvent(facadeEvt);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterOIAEvent(ECLOIANotify notify) {
|
||||||
|
if (notify != null && !oiaNotifies.contains(notify)) {
|
||||||
|
oiaNotifies.add(notify);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerOIAEvent(ECLOIANotify notify) { RegisterOIAEvent(notify); }
|
||||||
|
|
||||||
|
public void RegisterOIAEvent(haus.nightmare.lib3270j.ecl.ECLOIAListener listener) {
|
||||||
|
if (delegate != null) {
|
||||||
|
delegate.RegisterOIAEvent(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnregisterOIAEvent(ECLOIANotify notify) {
|
||||||
|
if (notify != null) {
|
||||||
|
oiaNotifies.remove(notify);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterOIAEvent(ECLOIANotify notify) { UnregisterOIAEvent(notify); }
|
||||||
|
|
||||||
|
public void UnregisterOIAEvent(haus.nightmare.lib3270j.ecl.ECLOIAListener listener) {
|
||||||
|
if (delegate != null) {
|
||||||
|
delegate.UnregisterOIAEvent(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean IsAlphanumeric() {
|
||||||
|
return delegate != null && delegate.IsAlphanumeric();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAlphanumeric() { return IsAlphanumeric(); }
|
||||||
|
|
||||||
|
public boolean IsNumeric() {
|
||||||
|
return delegate != null && delegate.IsNumeric();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNumeric() { return IsNumeric(); }
|
||||||
|
|
||||||
|
public boolean IsDBCS() {
|
||||||
|
return delegate != null && delegate.IsDBCS();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDBCS() { return IsDBCS(); }
|
||||||
|
|
||||||
|
public boolean IsInsertMode() {
|
||||||
|
return delegate != null && delegate.IsInsertMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInsertMode() { return IsInsertMode(); }
|
||||||
|
|
||||||
|
public boolean IsCommError() {
|
||||||
|
return delegate != null && delegate.IsCommError();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCommError() { return IsCommError(); }
|
||||||
|
|
||||||
|
public boolean IsKeyLocked() {
|
||||||
|
return delegate != null && delegate.isKeyLocked();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isKeyLocked() { return IsKeyLocked(); }
|
||||||
|
|
||||||
|
public boolean IsMessageWaiting() {
|
||||||
|
return delegate != null && delegate.IsMessageWaiting();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMessageWaiting() { return IsMessageWaiting(); }
|
||||||
|
|
||||||
|
public int GetInputInhibited() {
|
||||||
|
return delegate != null ? delegate.getInputInhibited() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getInputInhibited() { return GetInputInhibited(); }
|
||||||
|
|
||||||
|
public int GetAlphanumericType() {
|
||||||
|
return delegate != null ? delegate.getAlphanumericType() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAlphanumericType() { return GetAlphanumericType(); }
|
||||||
|
|
||||||
|
public boolean WaitForInput(long timeout) {
|
||||||
|
return delegate != null && delegate.WaitForInput(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean WaitForSystemAvailable(long timeout) {
|
||||||
|
return delegate != null && delegate.WaitForSystemAvailable(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean WaitForAppAvailable(long timeout) {
|
||||||
|
return delegate != null && delegate.WaitForAppAvailable(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean WaitForTransition(long timeout) {
|
||||||
|
return delegate != null && delegate.WaitForTransition(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
public haus.nightmare.lib3270j.ecl.ECLOIA getDelegate() { return delegate; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package com.ibm.eNetwork.ECL;
|
||||||
|
|
||||||
|
import com.ibm.eNetwork.ECL.event.ECLEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event object dispatched on Operator Information Area (ECLOIA) status changes matching IBM HoD.
|
||||||
|
*/
|
||||||
|
public class ECLOIAEvent extends ECLEvent {
|
||||||
|
|
||||||
|
public String data;
|
||||||
|
public long newState;
|
||||||
|
public long changedMask;
|
||||||
|
|
||||||
|
private int inputInhibited = 0;
|
||||||
|
private int alphanumericType = 0;
|
||||||
|
private boolean insertMode = false;
|
||||||
|
|
||||||
|
public ECLOIAEvent(Object source, long newState, long changedMask, String data) {
|
||||||
|
super(source);
|
||||||
|
this.newState = newState;
|
||||||
|
this.changedMask = changedMask;
|
||||||
|
this.data = data != null ? data : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLOIAEvent(Object source, int eventType, int inputInhibited, int alphanumericType,
|
||||||
|
boolean insertMode, String statusString) {
|
||||||
|
super(source);
|
||||||
|
this.inputInhibited = inputInhibited;
|
||||||
|
this.alphanumericType = alphanumericType;
|
||||||
|
this.insertMode = insertMode;
|
||||||
|
this.data = statusString != null ? statusString : "";
|
||||||
|
this.newState = inputInhibited;
|
||||||
|
this.changedMask = eventType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLOIAEvent(haus.nightmare.lib3270j.ecl.ECLOIAEvent delegate) {
|
||||||
|
super(delegate != null ? delegate.getSource() : null);
|
||||||
|
if (delegate != null) {
|
||||||
|
this.inputInhibited = delegate.getInputInhibited();
|
||||||
|
this.alphanumericType = delegate.getAlphanumericType();
|
||||||
|
this.insertMode = delegate.isInsertMode();
|
||||||
|
this.data = delegate.getStatusString();
|
||||||
|
this.newState = delegate.getInputInhibited();
|
||||||
|
this.changedMask = delegate.getEventType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLOIA GetOIA() {
|
||||||
|
Object src = GetSource();
|
||||||
|
if (src instanceof ECLOIA) {
|
||||||
|
return (ECLOIA) src;
|
||||||
|
} else if (src instanceof haus.nightmare.lib3270j.ecl.ECLOIA) {
|
||||||
|
return new ECLOIA((haus.nightmare.lib3270j.ecl.ECLOIA) src);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLOIA getOIA() { return GetOIA(); }
|
||||||
|
|
||||||
|
public int GetInputInhibited() { return inputInhibited; }
|
||||||
|
public int getInputInhibited() { return inputInhibited; }
|
||||||
|
|
||||||
|
public int GetAlphanumericType() { return alphanumericType; }
|
||||||
|
public int getAlphanumericType() { return alphanumericType; }
|
||||||
|
|
||||||
|
public boolean IsInsertMode() { return insertMode; }
|
||||||
|
public boolean isInsertMode() { return insertMode; }
|
||||||
|
|
||||||
|
public boolean IsInputInhibited() { return inputInhibited != 0; }
|
||||||
|
public boolean isInputInhibited() { return IsInputInhibited(); }
|
||||||
|
|
||||||
|
public String GetStatusString() { return data; }
|
||||||
|
public String getStatusString() { return data; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ECLOIAEvent: changedMask=0x" + Long.toHexString(this.changedMask) +
|
||||||
|
", newState=0x" + Long.toHexString(this.newState) + ", data=" + this.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.ibm.eNetwork.ECL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Operator Information Area notification interface matching IBM Host On-Demand ECLOIANotify.
|
||||||
|
*/
|
||||||
|
public interface ECLOIANotify {
|
||||||
|
void NotifyEvent(ECLOIA oia, int inhibitReason, int state, String text);
|
||||||
|
default void NotifyStop(ECLOIA oia, int reason) {}
|
||||||
|
default void NotifyError(ECLOIA oia, ECLErr err) {}
|
||||||
|
default void OIANotifyStop(ECLOIA oia, int reason) { NotifyStop(oia, reason); }
|
||||||
|
default void OIANotifyError(ECLOIA oia, ECLErr err) { NotifyError(oia, err); }
|
||||||
|
default void OIANotifyEvent(ECLOIAEvent event) {
|
||||||
|
if (event != null) {
|
||||||
|
NotifyEvent(event.GetOIA(), event.GetInputInhibited(), 0, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,571 @@
|
|||||||
|
package com.ibm.eNetwork.ECL;
|
||||||
|
|
||||||
|
import com.ibm.eNetwork.ECL.event.ECLPSListener;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Presentation Space facade matching IBM Host On-Demand ECLPS.
|
||||||
|
* Provides 1-based indexing, method signature parity, and checked ECLErr exception semantics.
|
||||||
|
*/
|
||||||
|
public class ECLPS implements ECLConstants, Serializable, Cloneable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final haus.nightmare.lib3270j.ecl.ECLPS delegate;
|
||||||
|
|
||||||
|
public ECLPS(haus.nightmare.lib3270j.ecl.ECLPS delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLPS(haus.nightmare.lib3270j.ecl.ECLSession session) {
|
||||||
|
this(session != null ? session.GetPS() : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLPS(ECLSession session) {
|
||||||
|
this(session != null ? session.getDelegate() : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public haus.nightmare.lib3270j.ecl.ECLPS getDelegate() {
|
||||||
|
return delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Dimensions & Position Conversion (1-based) ==========
|
||||||
|
|
||||||
|
public int GetSize() {
|
||||||
|
return delegate != null ? delegate.GetSize() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSize() { return GetSize(); }
|
||||||
|
|
||||||
|
public int GetRows() {
|
||||||
|
return delegate != null ? delegate.GetRows() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRows() { return GetRows(); }
|
||||||
|
|
||||||
|
public int GetCols() {
|
||||||
|
return delegate != null ? delegate.GetCols() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCols() { return GetCols(); }
|
||||||
|
|
||||||
|
public int GetSizeRows() { return GetRows(); }
|
||||||
|
public int GetSizeCols() { return GetCols(); }
|
||||||
|
|
||||||
|
public int ConvertRowColToPos(int row, int col) throws ECLErr {
|
||||||
|
if (delegate != null) {
|
||||||
|
try {
|
||||||
|
return delegate.ConvertRowColToPos(row, col);
|
||||||
|
} catch (haus.nightmare.lib3270j.ecl.ECLErr e) {
|
||||||
|
throw new ECLErr(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int convertRowColToPos(int row, int col) throws ECLErr {
|
||||||
|
return ConvertRowColToPos(row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ConvertPosToRow(int pos) throws ECLErr {
|
||||||
|
if (delegate != null) {
|
||||||
|
try {
|
||||||
|
return delegate.ConvertPosToRow(pos);
|
||||||
|
} catch (haus.nightmare.lib3270j.ecl.ECLErr e) {
|
||||||
|
throw new ECLErr(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int convertPosToRow(int pos) throws ECLErr {
|
||||||
|
return ConvertPosToRow(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ConvertPosToCol(int pos) throws ECLErr {
|
||||||
|
if (delegate != null) {
|
||||||
|
try {
|
||||||
|
return delegate.ConvertPosToCol(pos);
|
||||||
|
} catch (haus.nightmare.lib3270j.ecl.ECLErr e) {
|
||||||
|
throw new ECLErr(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int convertPosToCol(int pos) throws ECLErr {
|
||||||
|
return ConvertPosToCol(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConvertPosToRowCol(int pos, int[] rowCol) throws ECLErr {
|
||||||
|
if (delegate != null) {
|
||||||
|
try {
|
||||||
|
delegate.ConvertPosToRowCol(pos, rowCol);
|
||||||
|
} catch (haus.nightmare.lib3270j.ecl.ECLErr e) {
|
||||||
|
throw new ECLErr(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void convertPosToRowCol(int pos, int[] rowCol) throws ECLErr {
|
||||||
|
ConvertPosToRowCol(pos, rowCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int[] ConvertPosToRowCol(int pos) throws ECLErr {
|
||||||
|
if (delegate != null) {
|
||||||
|
try {
|
||||||
|
return delegate.ConvertPosToRowCol(pos);
|
||||||
|
} catch (haus.nightmare.lib3270j.ecl.ECLErr e) {
|
||||||
|
throw new ECLErr(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new int[]{1, 1};
|
||||||
|
}
|
||||||
|
|
||||||
|
public int[] convertPosToRowCol(int pos) throws ECLErr {
|
||||||
|
return ConvertPosToRowCol(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Cursor Operations (1-based) ==========
|
||||||
|
|
||||||
|
public int GetCursorPos() {
|
||||||
|
return delegate != null ? delegate.GetCursorPos() : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCursorPos() { return GetCursorPos(); }
|
||||||
|
|
||||||
|
public int GetCursorRow() {
|
||||||
|
return delegate != null ? delegate.GetCursorRow() : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCursorRow() { return GetCursorRow(); }
|
||||||
|
|
||||||
|
public int GetCursorCol() {
|
||||||
|
return delegate != null ? delegate.GetCursorCol() : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCursorCol() { return GetCursorCol(); }
|
||||||
|
|
||||||
|
public void SetCursorPos(int pos) throws ECLErr {
|
||||||
|
if (delegate != null) {
|
||||||
|
try {
|
||||||
|
delegate.SetCursorPos(pos);
|
||||||
|
} catch (haus.nightmare.lib3270j.ecl.ECLErr e) {
|
||||||
|
throw new ECLErr(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCursorPos(int pos) throws ECLErr {
|
||||||
|
SetCursorPos(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetCursorPos(int row, int col) throws ECLErr {
|
||||||
|
if (delegate != null) {
|
||||||
|
try {
|
||||||
|
delegate.SetCursorPos(row, col);
|
||||||
|
} catch (haus.nightmare.lib3270j.ecl.ECLErr e) {
|
||||||
|
throw new ECLErr(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCursorPos(int row, int col) throws ECLErr {
|
||||||
|
SetCursorPos(row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== GetString & SetString Operations (1-based) ==========
|
||||||
|
|
||||||
|
public int GetString(char[] buffer, int len, int pos) throws ECLErr {
|
||||||
|
return GetString(buffer, len, pos, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetString(char[] buffer, int len, int pos, int length) throws ECLErr {
|
||||||
|
if (delegate != null) {
|
||||||
|
try {
|
||||||
|
return delegate.GetString(buffer, len, pos, length);
|
||||||
|
} catch (haus.nightmare.lib3270j.ecl.ECLErr e) {
|
||||||
|
throw new ECLErr(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetString(char[] buffer, int len, int row, int col, int length) throws ECLErr {
|
||||||
|
return GetString(buffer, len, ConvertRowColToPos(row, col), length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String GetString(int pos, int length) throws ECLErr {
|
||||||
|
if (delegate != null) {
|
||||||
|
try {
|
||||||
|
return delegate.GetString(pos, length);
|
||||||
|
} catch (haus.nightmare.lib3270j.ecl.ECLErr e) {
|
||||||
|
throw new ECLErr(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString(int pos, int length) throws ECLErr {
|
||||||
|
return GetString(pos, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String GetString(int row, int col, int length) throws ECLErr {
|
||||||
|
return GetString(ConvertRowColToPos(row, col), length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString(int row, int col, int length) throws ECLErr {
|
||||||
|
return GetString(row, col, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetString(String text, int pos) throws ECLErr {
|
||||||
|
if (delegate != null) {
|
||||||
|
try {
|
||||||
|
delegate.SetString(text, pos);
|
||||||
|
} catch (haus.nightmare.lib3270j.ecl.ECLErr e) {
|
||||||
|
throw new ECLErr(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setString(String text, int pos) throws ECLErr {
|
||||||
|
SetString(text, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetString(String text, int row, int col) throws ECLErr {
|
||||||
|
SetString(text, ConvertRowColToPos(row, col));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setString(String text, int row, int col) throws ECLErr {
|
||||||
|
SetString(text, row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetString(String text) throws ECLErr {
|
||||||
|
SetString(text, GetCursorPos());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setString(String text) throws ECLErr {
|
||||||
|
SetString(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetText(String text, int pos) throws ECLErr {
|
||||||
|
SetString(text, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetText(String text, int row, int col) throws ECLErr {
|
||||||
|
SetString(text, row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setText(String text, int pos) {
|
||||||
|
if (delegate != null) delegate.setText(text, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setText(String text, int row, int col) {
|
||||||
|
if (delegate != null) delegate.setText(text, row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Search Operations (1-based) ==========
|
||||||
|
|
||||||
|
public int SearchText(String text, int dir, int startPos) {
|
||||||
|
return delegate != null ? delegate.SearchText(text, dir, startPos) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int SearchText(String text, int dir) {
|
||||||
|
return delegate != null ? delegate.SearchText(text, dir) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int SearchText(String text) {
|
||||||
|
return delegate != null ? delegate.SearchText(text) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int SearchText(String text, int row, int col, int dir) throws ECLErr {
|
||||||
|
return SearchText(text, dir, ConvertRowColToPos(row, col));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int SearchString(String text, int dir, int startPos) {
|
||||||
|
return SearchText(text, dir, startPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int SearchString(String text, int dir) {
|
||||||
|
return SearchText(text, dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int SearchString(String text) {
|
||||||
|
return SearchText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int SearchString(String text, int row, int col, int dir) throws ECLErr {
|
||||||
|
return SearchText(text, row, col, dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int SearchPS(String text) {
|
||||||
|
return delegate != null ? delegate.SearchPS(text) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int SearchPS(String text, int startPos) {
|
||||||
|
return delegate != null ? delegate.SearchPS(text, startPos) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int SearchPSExt(String text, int startPos, int endPos, int dir, boolean ignoreCase, boolean wrap) {
|
||||||
|
return delegate != null ? delegate.SearchPSExt(text, startPos, endPos, dir, ignoreCase, wrap) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Keystrokes & Mnemonics ==========
|
||||||
|
|
||||||
|
public void SendKeys(String keys) {
|
||||||
|
if (delegate != null) delegate.SendKeys(keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendKeys(String keys) { SendKeys(keys); }
|
||||||
|
|
||||||
|
public void SendKeys(String keys, int pos) {
|
||||||
|
if (delegate != null) delegate.SendKeys(keys, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendKeys(String keys, int pos) { SendKeys(keys, pos); }
|
||||||
|
|
||||||
|
public void SendKeys(String keys, int row, int col) {
|
||||||
|
if (delegate != null) delegate.SendKeys(keys, row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendKeys(String keys, int row, int col) { SendKeys(keys, row, col); }
|
||||||
|
|
||||||
|
public void SendCharacters(String keys) {
|
||||||
|
if (delegate != null) delegate.SendCharacters(keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendCharacters(String keys) { SendCharacters(keys); }
|
||||||
|
|
||||||
|
public void SendCharacters(String keys, int delayMs) {
|
||||||
|
if (delegate != null) delegate.SendCharacters(keys, delayMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendCharacters(String keys, int delayMs) { SendCharacters(keys, delayMs); }
|
||||||
|
|
||||||
|
public void SendCharacters(String keys, int pos, int delayMs) {
|
||||||
|
if (delegate != null) delegate.SendCharacters(keys, pos, delayMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendCharacters(String keys, int pos, int delayMs) { SendCharacters(keys, pos, delayMs); }
|
||||||
|
|
||||||
|
public void SendCharacters(String keys, int row, int col, int delayMs) {
|
||||||
|
if (delegate != null) delegate.SendCharacters(keys, row, col, delayMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendCharacters(String keys, int row, int col, int delayMs) { SendCharacters(keys, row, col, delayMs); }
|
||||||
|
|
||||||
|
public String[] GetSendKeyMnemonics() {
|
||||||
|
return delegate != null ? delegate.GetSendKeyMnemonics() : new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getSendKeyMnemonics() { return GetSendKeyMnemonics(); }
|
||||||
|
|
||||||
|
// ========== Field Operations ==========
|
||||||
|
|
||||||
|
public ECLFieldList GetFieldList() {
|
||||||
|
haus.nightmare.lib3270j.ecl.ECLFieldList fl = delegate != null ? delegate.GetFieldList() : null;
|
||||||
|
return new ECLFieldList(fl, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLFieldList getFieldList() { return GetFieldList(); }
|
||||||
|
|
||||||
|
public ECLField GetField(int pos) throws ECLErr {
|
||||||
|
return GetFieldList().FindField(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLField getField(int pos) throws ECLErr { return GetField(pos); }
|
||||||
|
|
||||||
|
public ECLField GetField(int row, int col) throws ECLErr {
|
||||||
|
return GetFieldList().FindField(row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLField getField(int row, int col) throws ECLErr { 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); }
|
||||||
|
|
||||||
|
// ========== Listener Overloads for HoD Interfaces ==========
|
||||||
|
|
||||||
|
public void RegisterPSEvent(ECLPSNotify notify) {
|
||||||
|
if (delegate != null) delegate.RegisterPSEvent(notify);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerPSEvent(ECLPSNotify notify) { RegisterPSEvent(notify); }
|
||||||
|
|
||||||
|
public void RegisterPSEvent(ECLPSNotify notify, int eventType) {
|
||||||
|
if (delegate != null) delegate.RegisterPSEvent(notify, eventType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerPSEvent(ECLPSNotify notify, int eventType) { RegisterPSEvent(notify, eventType); }
|
||||||
|
|
||||||
|
public void RegisterPSEvent(ECLPSNotify notify, ECLScreenDesc desc) {
|
||||||
|
if (delegate != null) delegate.RegisterPSEvent(notify, desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerPSEvent(ECLPSNotify notify, ECLScreenDesc desc) { RegisterPSEvent(notify, desc); }
|
||||||
|
|
||||||
|
public void RegisterPSEvent(ECLPSNotify notify, ECLScreenDesc desc, int eventType) {
|
||||||
|
if (delegate != null) delegate.RegisterPSEvent(notify, desc, eventType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerPSEvent(ECLPSNotify notify, ECLScreenDesc desc, int eventType) { RegisterPSEvent(notify, desc, eventType); }
|
||||||
|
|
||||||
|
public void UnregisterPSEvent(ECLPSNotify notify) {
|
||||||
|
if (delegate != null) delegate.UnregisterPSEvent(notify);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterPSEvent(ECLPSNotify notify) { UnregisterPSEvent(notify); }
|
||||||
|
|
||||||
|
public void RegisterPSEvent(ECLPSListener listener) {
|
||||||
|
if (delegate != null) delegate.RegisterPSEvent(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerPSEvent(ECLPSListener listener) { RegisterPSEvent(listener); }
|
||||||
|
|
||||||
|
public void UnregisterPSEvent(ECLPSListener listener) {
|
||||||
|
if (delegate != null) delegate.UnregisterPSEvent(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterPSEvent(ECLPSListener listener) { UnregisterPSEvent(listener); }
|
||||||
|
|
||||||
|
// ========== Synchronization & Waits ==========
|
||||||
|
|
||||||
|
public boolean WaitForScreen(ECLScreenDesc desc, long timeoutMs) {
|
||||||
|
return delegate != null && delegate.WaitForScreen(desc, timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean waitForScreen(ECLScreenDesc desc, long timeoutMs) { return WaitForScreen(desc, timeoutMs); }
|
||||||
|
|
||||||
|
public boolean WaitForScreen(ECLScreenDesc desc) {
|
||||||
|
return delegate != null && delegate.WaitForScreen(desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean waitForScreen(ECLScreenDesc desc) { return WaitForScreen(desc); }
|
||||||
|
|
||||||
|
public boolean WaitWhileScreen(ECLScreenDesc desc, long timeoutMs) {
|
||||||
|
return delegate != null && delegate.WaitWhileScreen(desc, timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean waitWhileScreen(ECLScreenDesc desc, long timeoutMs) { return WaitWhileScreen(desc, timeoutMs); }
|
||||||
|
|
||||||
|
public boolean WaitWhileScreen(ECLScreenDesc desc) {
|
||||||
|
return delegate != null && delegate.WaitWhileScreen(desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean waitWhileScreen(ECLScreenDesc desc) { return WaitWhileScreen(desc); }
|
||||||
|
|
||||||
|
public boolean WaitForString(String text, long timeoutMs) {
|
||||||
|
return delegate != null && delegate.WaitForString(text, timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean waitForString(String text, long timeoutMs) { return WaitForString(text, timeoutMs); }
|
||||||
|
|
||||||
|
public boolean WaitForString(String text) {
|
||||||
|
return delegate != null && delegate.WaitForString(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean waitForString(String text) { return WaitForString(text); }
|
||||||
|
|
||||||
|
public boolean WaitForString(String text, int row, int col, long timeoutMs) {
|
||||||
|
return delegate != null && delegate.WaitForString(text, row, col, timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean waitForString(String text, int row, int col, long timeoutMs) { return WaitForString(text, row, col, timeoutMs); }
|
||||||
|
|
||||||
|
public boolean WaitForCursor(int row, int col, long timeoutMs) {
|
||||||
|
return delegate != null && delegate.WaitForCursor(row, col, timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean waitForCursor(int row, int col, long timeoutMs) { return WaitForCursor(row, col, timeoutMs); }
|
||||||
|
|
||||||
|
// ========== Screen Update Inquiries ==========
|
||||||
|
|
||||||
|
public ECLPSUpdate GetScreen(int startRow, int startCol, int length) throws ECLErr {
|
||||||
|
return GetScreen(startRow, startCol, length, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLPSUpdate GetScreen(int startRow, int startCol, int length, boolean fillNulls) throws ECLErr {
|
||||||
|
int pos = ConvertRowColToPos(startRow, startCol);
|
||||||
|
char[] buf = new char[length];
|
||||||
|
GetString(buf, length, pos, length);
|
||||||
|
if (fillNulls) {
|
||||||
|
for (int i = 0; i < buf.length; i++) {
|
||||||
|
if (buf[i] == '\0') buf[i] = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int endPos = pos + length - 1;
|
||||||
|
int endRow = ConvertPosToRow(endPos);
|
||||||
|
int endCol = ConvertPosToCol(endPos);
|
||||||
|
return new ECLPSUpdate(this, startRow, startCol, endRow, endCol, new String(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetScreen(char[] buffer, int len, int row, int col, int length, int plane) {
|
||||||
|
return delegate.GetScreen(buffer, len, row, col, length, plane);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== DBCS Character Status ==========
|
||||||
|
|
||||||
|
public boolean IsDBCSChar(int pos) {
|
||||||
|
return delegate != null && delegate.IsDBCSChar(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDBCSChar(int pos) { return IsDBCSChar(pos); }
|
||||||
|
|
||||||
|
public boolean IsDBCSChar(int row, int col) {
|
||||||
|
return delegate != null && delegate.IsDBCSChar(row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDBCSChar(int row, int col) { return IsDBCSChar(row, col); }
|
||||||
|
|
||||||
|
public boolean IsDBCS1stChar(int pos) {
|
||||||
|
return delegate != null && delegate.IsDBCS1stChar(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDBCS1stChar(int pos) { return IsDBCS1stChar(pos); }
|
||||||
|
|
||||||
|
public boolean IsDBCS1stChar(int row, int col) {
|
||||||
|
return delegate != null && delegate.IsDBCS1stChar(row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDBCS1stChar(int row, int col) { return IsDBCS1stChar(row, col); }
|
||||||
|
|
||||||
|
public boolean IsDBCS2ndChar(int pos) {
|
||||||
|
return delegate != null && delegate.IsDBCS2ndChar(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDBCS2ndChar(int pos) { return IsDBCS2ndChar(pos); }
|
||||||
|
|
||||||
|
public boolean IsDBCS2ndChar(int row, int col) {
|
||||||
|
return delegate != null && delegate.IsDBCS2ndChar(row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDBCS2ndChar(int row, int col) { return IsDBCS2ndChar(row, col); }
|
||||||
|
|
||||||
|
public boolean IsSOChar(int pos) {
|
||||||
|
return delegate != null && delegate.IsSOChar(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSOChar(int pos) { return IsSOChar(pos); }
|
||||||
|
|
||||||
|
public boolean IsSIChar(int pos) {
|
||||||
|
return delegate != null && delegate.IsSIChar(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSIChar(int pos) { return IsSIChar(pos); }
|
||||||
|
|
||||||
|
public boolean IsSOSIChar(int pos) {
|
||||||
|
return delegate != null && delegate.IsSOSIChar(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSOSIChar(int pos) { return IsSOSIChar(pos); }
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.ibm.eNetwork.ECL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Presentation space notification interface matching IBM Host On-Demand ECLPSNotify.
|
||||||
|
*/
|
||||||
|
public interface ECLPSNotify {
|
||||||
|
void NotifyEvent(ECLPS ps);
|
||||||
|
default void NotifyStop(ECLPS ps, int reason) {}
|
||||||
|
default void NotifyError(ECLPS ps, ECLErr err) {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package com.ibm.eNetwork.ECL;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conforms to IBM Host On-Demand ECLPSUpdate.
|
||||||
|
*/
|
||||||
|
public class ECLPSUpdate implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final haus.nightmare.lib3270j.ecl.ECLPSUpdate delegate;
|
||||||
|
private final int startRow;
|
||||||
|
private final int startCol;
|
||||||
|
private final int endRow;
|
||||||
|
private final int endCol;
|
||||||
|
private final int start;
|
||||||
|
private final int end;
|
||||||
|
private final boolean fullUpdate;
|
||||||
|
private final String text;
|
||||||
|
private final transient ECLPS ps;
|
||||||
|
|
||||||
|
public ECLPSUpdate(ECLPS ps, int startRow, int startCol, int endRow, int endCol,
|
||||||
|
int start, int end, boolean fullUpdate, String text) {
|
||||||
|
this.ps = ps;
|
||||||
|
this.startRow = startRow;
|
||||||
|
this.startCol = startCol;
|
||||||
|
this.endRow = endRow;
|
||||||
|
this.endCol = endCol;
|
||||||
|
this.start = start;
|
||||||
|
this.end = end;
|
||||||
|
this.fullUpdate = fullUpdate;
|
||||||
|
this.text = text != null ? text : "";
|
||||||
|
this.delegate = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLPSUpdate(ECLPS ps, int startRow, int startCol, int endRow, int endCol, String text) {
|
||||||
|
this(ps, startRow, startCol, endRow, endCol, 0, 0, false, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLPSUpdate(int startRow, int startCol, int endRow, int endCol, String text) {
|
||||||
|
this(null, startRow, startCol, endRow, endCol, 0, 0, false, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLPSUpdate(haus.nightmare.lib3270j.ecl.ECLPSUpdate delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.ps = (delegate != null && delegate.getPS() != null) ? new ECLPS(delegate.getPS()) : null;
|
||||||
|
this.startRow = delegate != null ? delegate.getStartRow() : 0;
|
||||||
|
this.startCol = delegate != null ? delegate.getStartCol() : 0;
|
||||||
|
this.endRow = delegate != null ? delegate.getEndRow() : 0;
|
||||||
|
this.endCol = delegate != null ? delegate.getEndCol() : 0;
|
||||||
|
this.start = delegate != null ? delegate.getStart() : 0;
|
||||||
|
this.end = delegate != null ? delegate.getEnd() : 0;
|
||||||
|
this.fullUpdate = delegate != null && delegate.isFullUpdate();
|
||||||
|
this.text = delegate != null ? delegate.getText() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLPS getPS() { return ps; }
|
||||||
|
public ECLPS GetPS() { return ps; }
|
||||||
|
|
||||||
|
public int getStartRow() { return startRow; }
|
||||||
|
public int GetStartRow() { return startRow + 1; }
|
||||||
|
|
||||||
|
public int getStartCol() { return startCol; }
|
||||||
|
public int GetStartCol() { return startCol + 1; }
|
||||||
|
|
||||||
|
public int getEndRow() { return endRow; }
|
||||||
|
public int GetEndRow() { return endRow + 1; }
|
||||||
|
|
||||||
|
public int getEndCol() { return endCol; }
|
||||||
|
public int GetEndCol() { return endCol + 1; }
|
||||||
|
|
||||||
|
public int getStart() { return start; }
|
||||||
|
public int GetStart() { return start + 1; }
|
||||||
|
|
||||||
|
public int getEnd() { return end; }
|
||||||
|
public int GetEnd() { return end + 1; }
|
||||||
|
|
||||||
|
public boolean isFullUpdate() { return fullUpdate; }
|
||||||
|
public boolean IsFullUpdate() { return fullUpdate; }
|
||||||
|
|
||||||
|
public String getText() { return text; }
|
||||||
|
public String GetText() { return text; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("ECLPSUpdate[start=(%d,%d), end=(%d,%d), full=%b]",
|
||||||
|
startRow, startCol, endRow, endCol, fullUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.ibm.eNetwork.ECL;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Screen descriptor matching engine facade matching IBM Host On-Demand ECLScreenDesc.
|
||||||
|
*/
|
||||||
|
public class ECLScreenDesc extends haus.nightmare.lib3270j.ecl.ECLScreenDesc implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public ECLScreenDesc() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean Matches(ECLPS ps, ECLOIA oia) {
|
||||||
|
haus.nightmare.lib3270j.ecl.ECLPS p = (ps != null) ? ps.getDelegate() : null;
|
||||||
|
haus.nightmare.lib3270j.ecl.ECLOIA o = (oia != null) ? oia.getDelegate() : null;
|
||||||
|
return super.Matches(p, o);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.ibm.eNetwork.ECL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener interface for ECLScreenReco matching notifications matching IBM HoD.
|
||||||
|
*/
|
||||||
|
public interface ECLScreenNotify {
|
||||||
|
void screenMatching(ECLScreenRecoEvent event);
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package com.ibm.eNetwork.ECL;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Screen recognition facade matching IBM Host On-Demand ECLScreenReco.
|
||||||
|
*/
|
||||||
|
public class ECLScreenReco {
|
||||||
|
|
||||||
|
private final haus.nightmare.lib3270j.ecl.ECLScreenReco delegate;
|
||||||
|
private final ECLPS ps;
|
||||||
|
private final Map<ECLScreenDesc, ECLScreenNotify> notifyMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public ECLScreenReco() {
|
||||||
|
this((ECLPS) null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLScreenReco(ECLSession session) {
|
||||||
|
this(session != null ? session.GetPS() : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLScreenReco(ECLPS ps) {
|
||||||
|
this(ps, (ECLOIA) null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLScreenReco(ECLPS ps, ECLOIA oia) {
|
||||||
|
this.ps = ps;
|
||||||
|
this.delegate = new haus.nightmare.lib3270j.ecl.ECLScreenReco(ps != null ? ps.getDelegate() : null,
|
||||||
|
oia != null ? oia.getDelegate() : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLScreenReco(haus.nightmare.lib3270j.ecl.ECLScreenReco delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.ps = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterScreen(ECLScreenDesc desc, ECLScreenNotify notify) {
|
||||||
|
if (desc != null && notify != null) {
|
||||||
|
notifyMap.put(desc, notify);
|
||||||
|
if (delegate != null) {
|
||||||
|
delegate.RegisterScreen(desc, event -> notify.screenMatching(new ECLScreenRecoEvent(this, desc, ps)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerScreen(ECLScreenDesc desc, ECLScreenNotify notify) {
|
||||||
|
RegisterScreen(desc, notify);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnregisterScreen(ECLScreenDesc desc) {
|
||||||
|
if (desc != null) {
|
||||||
|
notifyMap.remove(desc);
|
||||||
|
if (delegate != null) {
|
||||||
|
delegate.UnregisterScreen(desc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterScreen(ECLScreenDesc desc) {
|
||||||
|
UnregisterScreen(desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean IsMatch(ECLScreenDesc desc) {
|
||||||
|
if (delegate != null) {
|
||||||
|
return delegate.IsMatch(desc);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean IsMatch(ECLPS ps, ECLScreenDesc desc) {
|
||||||
|
if (ps == null) return false;
|
||||||
|
if (desc == null) return true;
|
||||||
|
return desc.Matches(ps, (ECLOIA) null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public haus.nightmare.lib3270j.ecl.ECLScreenReco getDelegate() { return delegate; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.ibm.eNetwork.ECL;
|
||||||
|
|
||||||
|
import com.ibm.eNetwork.ECL.event.ECLEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event delivered when an ECLScreenDesc matches the Presentation Space.
|
||||||
|
*/
|
||||||
|
public class ECLScreenRecoEvent extends ECLEvent {
|
||||||
|
|
||||||
|
private final ECLScreenReco source;
|
||||||
|
private final ECLScreenDesc screenDesc;
|
||||||
|
private final ECLPS ps;
|
||||||
|
|
||||||
|
public ECLScreenRecoEvent(ECLScreenReco source, ECLScreenDesc screenDesc, ECLPS ps) {
|
||||||
|
super(source);
|
||||||
|
this.source = source;
|
||||||
|
this.screenDesc = screenDesc;
|
||||||
|
this.ps = ps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLScreenRecoEvent(Object source, ECLPS ps) {
|
||||||
|
super(source);
|
||||||
|
this.source = (source instanceof ECLScreenReco) ? (ECLScreenReco) source : null;
|
||||||
|
this.screenDesc = null;
|
||||||
|
this.ps = ps;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object GetSource() { return source != null ? source : super.GetSource(); }
|
||||||
|
|
||||||
|
public ECLScreenReco GetScreenReco() { return source; }
|
||||||
|
public ECLScreenReco getScreenReco() { return source; }
|
||||||
|
|
||||||
|
public ECLScreenDesc GetScreenDesc() { return screenDesc; }
|
||||||
|
public ECLScreenDesc getScreenDesc() { return screenDesc; }
|
||||||
|
|
||||||
|
public ECLPS GetPS() { return ps; }
|
||||||
|
public ECLPS getPS() { return ps; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
package com.ibm.eNetwork.ECL;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Top-level session facade matching IBM Host On-Demand ECLSession.
|
||||||
|
* Extends ECLConnection and provides access to Presentation Space (ECLPS),
|
||||||
|
* Operator Information Area (ECLOIA), File Transfer (ECLXfer), and Screen Recognition (ECLScreenReco).
|
||||||
|
*/
|
||||||
|
public class ECLSession extends ECLConnection implements ECLConstants {
|
||||||
|
|
||||||
|
public static final String SESSION_NAME = "SESSION_NAME";
|
||||||
|
public static final String SESSION_LABEL = "SESSION_LABEL";
|
||||||
|
public static final String SESSION_TYPE = "SESSION_TYPE";
|
||||||
|
public static final String SESSION_HOST = "SESSION_HOST";
|
||||||
|
public static final String SESSION_HOST_PORT = "SESSION_HOST_PORT";
|
||||||
|
public static final String SESSION_PORT = "SESSION_PORT";
|
||||||
|
public static final String SESSION_PS_SIZE = "SESSION_PS_SIZE";
|
||||||
|
public static final String SESSION_CODE_PAGE = "SESSION_CODE_PAGE";
|
||||||
|
public static final String SESSION_SSL = "SESSION_SSL";
|
||||||
|
public static final String SESSION_SECURITY_PROTOCOL = "SESSION_SECURITY_PROTOCOL";
|
||||||
|
public static final String SESSION_TLS_PROTOCOL_VERSION = "tlsProtocolVersion";
|
||||||
|
public static final String SESSION_LU_NAME = "SESSION_LU_NAME";
|
||||||
|
public static final String SESSION_TN3270E = "SESSION_TN3270E";
|
||||||
|
public static final String SESSION_AUTO_CONNECT = "SESSION_AUTO_CONNECT";
|
||||||
|
public static final String SESSION_AUTORECONNECT = "SESSION_AUTORECONNECT";
|
||||||
|
public static final String SESSION_RECONNECT_RETRIES = "SESSION_RECONNECT_RETRIES";
|
||||||
|
public static final String SESSION_KEEPALIVE = "SESSION_KEEPALIVE";
|
||||||
|
public static final String KEY_KEEPALIVE_TYPE = "keepAliveType";
|
||||||
|
public static final String KEY_KEEPALIVE_TIMEOUT = "keepAliveTimeout";
|
||||||
|
public static final String SESSION_AUTO_SYS_UNLOCK = "autoSysUnlock";
|
||||||
|
public static final String ENABLE_PASTE_FROM_EXCEL = "enablePasteFromExcel";
|
||||||
|
public static final String PASTE_TAB_OPTIONS = "pasteTabOptions";
|
||||||
|
public static final String PASTE_STOP_AT_PROTECTED_LINE = "pasteStopAtProtectedLine";
|
||||||
|
public static final String BLOCK_PASTE = "blockPaste";
|
||||||
|
public static final String PASTE_FIELD_WRAP = "pasteFieldWrap";
|
||||||
|
public static final String PASTE_LINE_WRAP = "pasteLineWrap";
|
||||||
|
public static final String PASTE_WORD_BREAK = "pasteWordBreak";
|
||||||
|
|
||||||
|
private final haus.nightmare.lib3270j.ecl.ECLSession delegate;
|
||||||
|
private final ECLPS ps;
|
||||||
|
private final ECLOIA oia;
|
||||||
|
private final ECLXfer xfer;
|
||||||
|
private final ECLScreenReco screenReco;
|
||||||
|
|
||||||
|
public ECLSession(Properties properties) throws ECLErr {
|
||||||
|
super(properties);
|
||||||
|
this.delegate = new haus.nightmare.lib3270j.ecl.ECLSession(properties);
|
||||||
|
this.ps = new ECLPS(delegate.getPS());
|
||||||
|
this.oia = new ECLOIA(delegate.getOIA());
|
||||||
|
this.xfer = new ECLXfer(delegate.getXfer());
|
||||||
|
this.screenReco = new ECLScreenReco(this.ps, this.oia);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSession() throws ECLErr {
|
||||||
|
this(new Properties());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSession(String host, int port) throws ECLErr {
|
||||||
|
super(host, port);
|
||||||
|
Properties props = new Properties();
|
||||||
|
if (host != null) props.setProperty(SESSION_HOST, host);
|
||||||
|
props.setProperty(SESSION_PORT, String.valueOf(port));
|
||||||
|
this.delegate = new haus.nightmare.lib3270j.ecl.ECLSession(props);
|
||||||
|
this.ps = new ECLPS(delegate.getPS());
|
||||||
|
this.oia = new ECLOIA(delegate.getOIA());
|
||||||
|
this.xfer = new ECLXfer(delegate.getXfer());
|
||||||
|
this.screenReco = new ECLScreenReco(this.ps, this.oia);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSession(haus.nightmare.lib3270j.ecl.ECLSession delegate) {
|
||||||
|
super(delegate);
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.ps = new ECLPS(delegate != null ? delegate.getPS() : null);
|
||||||
|
this.oia = new ECLOIA(delegate != null ? delegate.getOIA() : null);
|
||||||
|
this.xfer = new ECLXfer(delegate != null ? delegate.getXfer() : null);
|
||||||
|
this.screenReco = new ECLScreenReco(this.ps, this.oia);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLPS GetPS() {
|
||||||
|
return ps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLPS getPS() {
|
||||||
|
return ps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLOIA GetOIA() {
|
||||||
|
return oia;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLOIA getOIA() {
|
||||||
|
return oia;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLXfer GetXfer() {
|
||||||
|
return xfer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLXfer getXfer() {
|
||||||
|
return xfer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLScreenReco GetScreenReco() {
|
||||||
|
return screenReco;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLScreenReco getScreenReco() {
|
||||||
|
return screenReco;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLConnection GetConnection() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLConnection getConnection() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public haus.nightmare.lib3270j.ecl.ECLSession getDelegate() {
|
||||||
|
return delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void StartCommunication() throws IOException {
|
||||||
|
if (delegate != null) {
|
||||||
|
delegate.StartCommunication();
|
||||||
|
} else {
|
||||||
|
super.StartCommunication();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void StopCommunication() {
|
||||||
|
if (delegate != null) {
|
||||||
|
delegate.StopCommunication();
|
||||||
|
} else {
|
||||||
|
super.StopCommunication();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean StartCommunicationWithBlocking(long timeout) throws InterruptedException, ECLErr {
|
||||||
|
return StartCommunicationWithBlocking(timeout, (ECLScreenDesc) null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean startCommunicationWithBlocking(long timeout) throws InterruptedException, ECLErr {
|
||||||
|
return StartCommunicationWithBlocking(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean StartCommunicationWithBlocking(long timeout, ECLScreenDesc desc) throws InterruptedException, ECLErr {
|
||||||
|
if (delegate != null) {
|
||||||
|
try {
|
||||||
|
return delegate.StartCommunicationWithBlocking(timeout, desc);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ECLErr("ECLSession.StartCommunicationWithBlocking", "ECL0001", e.getMessage());
|
||||||
|
} catch (haus.nightmare.lib3270j.ecl.ECLErr e) {
|
||||||
|
throw new ECLErr(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean startCommunicationWithBlocking(long timeout, ECLScreenDesc desc) throws InterruptedException, ECLErr {
|
||||||
|
return StartCommunicationWithBlocking(timeout, desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean RestartCommunicationWithBlocking(long timeout) throws InterruptedException, ECLErr {
|
||||||
|
if (delegate != null) {
|
||||||
|
try {
|
||||||
|
return delegate.RestartCommunicationWithBlocking(timeout);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ECLErr("ECLSession.RestartCommunicationWithBlocking", "ECL0001", e.getMessage());
|
||||||
|
} catch (haus.nightmare.lib3270j.ecl.ECLErr e) {
|
||||||
|
throw new ECLErr(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean restartCommunicationWithBlocking(long timeout) throws InterruptedException, ECLErr {
|
||||||
|
return RestartCommunicationWithBlocking(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dispose() {
|
||||||
|
if (delegate != null) {
|
||||||
|
delegate.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.ibm.eNetwork.ECL;
|
||||||
|
|
||||||
|
import com.ibm.eNetwork.ECL.event.ECLXferListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File transfer facade matching IBM Host On-Demand ECLXfer.
|
||||||
|
*/
|
||||||
|
public class ECLXfer {
|
||||||
|
|
||||||
|
private final haus.nightmare.lib3270j.ecl.ECLXfer delegate;
|
||||||
|
|
||||||
|
public ECLXfer(ECLSession session, Boolean bidi) {
|
||||||
|
this.delegate = (session != null && session.getDelegate() != null)
|
||||||
|
? session.getDelegate().GetXfer() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLXfer(haus.nightmare.lib3270j.ecl.ECLXfer delegate) {
|
||||||
|
this.delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendFile(String localFile, String hostFile, String options) throws ECLErr {
|
||||||
|
if (delegate != null) {
|
||||||
|
int rc = delegate.SendFile(localFile, hostFile, options);
|
||||||
|
if (rc != 0) {
|
||||||
|
throw new ECLErr("ECLXfer:SendFile", "ECL0150", String.valueOf(rc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReceiveFile(String localFile, String hostFile, String options) throws ECLErr {
|
||||||
|
if (delegate != null) {
|
||||||
|
int rc = delegate.ReceiveFile(localFile, hostFile, options);
|
||||||
|
if (rc != 0) {
|
||||||
|
throw new ECLErr("ECLXfer:ReceiveFile", "ECL0150", String.valueOf(rc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Cancel() {
|
||||||
|
if (delegate != null) {
|
||||||
|
delegate.Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterXferEvent(ECLXferListener listener) {
|
||||||
|
// Can bridge listener if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnregisterXferEvent(ECLXferListener listener) {
|
||||||
|
// Can bridge listener if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
public haus.nightmare.lib3270j.ecl.ECLXfer getDelegate() { return delegate; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.ibm.eNetwork.ECL.event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base event class matching IBM Host On-Demand ECLEvent.
|
||||||
|
*/
|
||||||
|
public class ECLEvent {
|
||||||
|
private final Object originator;
|
||||||
|
|
||||||
|
public ECLEvent(Object object) {
|
||||||
|
this.originator = object;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object GetSource() {
|
||||||
|
return this.originator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getSource() {
|
||||||
|
return this.originator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return super.toString() + ": " + (this.originator != null ? this.originator.toString() : "null");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package com.ibm.eNetwork.ECL.event;
|
||||||
|
|
||||||
|
import com.ibm.eNetwork.ECL.ECLPS;
|
||||||
|
import com.ibm.eNetwork.ECL.ECLPSUpdate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Presentation Space event class matching IBM Host On-Demand ECLPSEvent.
|
||||||
|
*/
|
||||||
|
public class ECLPSEvent extends ECLEvent {
|
||||||
|
|
||||||
|
public static final int USER_EVENTS = 1;
|
||||||
|
public static final int HOST_EVENTS = 2;
|
||||||
|
public static final int ALL_EVENTS = 3;
|
||||||
|
|
||||||
|
private int type = 0;
|
||||||
|
private int start = 1;
|
||||||
|
private int end = 1;
|
||||||
|
private int startRow = 1;
|
||||||
|
private int startCol = 1;
|
||||||
|
private int endRow = 1;
|
||||||
|
private int endCol = 1;
|
||||||
|
private int oldCursorPos = 1;
|
||||||
|
private int newCursorPos = 1;
|
||||||
|
private boolean fullUpdate = true;
|
||||||
|
private boolean cursorVisible = true;
|
||||||
|
private int ringCounter = 0;
|
||||||
|
private ECLPSUpdate psUpdate = null;
|
||||||
|
private haus.nightmare.lib3270j.ecl.ECLPSEvent delegate = null;
|
||||||
|
|
||||||
|
public ECLPSEvent(Object source) {
|
||||||
|
super(source);
|
||||||
|
if (source instanceof ECLPS) {
|
||||||
|
ECLPS ps = (ECLPS) source;
|
||||||
|
this.end = ps.GetSize();
|
||||||
|
this.endRow = ps.GetRows();
|
||||||
|
this.endCol = ps.GetCols();
|
||||||
|
this.newCursorPos = ps.GetCursorPos();
|
||||||
|
} else if (source instanceof haus.nightmare.lib3270j.ecl.ECLPS) {
|
||||||
|
haus.nightmare.lib3270j.ecl.ECLPS ps = (haus.nightmare.lib3270j.ecl.ECLPS) source;
|
||||||
|
this.end = ps.GetSize();
|
||||||
|
this.endRow = ps.GetRows();
|
||||||
|
this.endCol = ps.GetCols();
|
||||||
|
this.newCursorPos = ps.GetCursorPos();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLPSEvent(Object source, int type) {
|
||||||
|
this(source);
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLPSEvent(haus.nightmare.lib3270j.ecl.ECLPSEvent delegate) {
|
||||||
|
super(delegate != null ? delegate.getSource() : null);
|
||||||
|
this.delegate = delegate;
|
||||||
|
if (delegate != null) {
|
||||||
|
this.type = delegate.getType();
|
||||||
|
this.start = delegate.getStart();
|
||||||
|
this.end = delegate.getEnd();
|
||||||
|
this.startRow = delegate.getStartRow();
|
||||||
|
this.startCol = delegate.getStartCol();
|
||||||
|
this.endRow = delegate.getEndRow();
|
||||||
|
this.endCol = delegate.getEndCol();
|
||||||
|
this.oldCursorPos = delegate.getOldCursorAddress() + 1;
|
||||||
|
this.newCursorPos = delegate.getNewCursorAddress() + 1;
|
||||||
|
this.fullUpdate = delegate.isFullUpdate();
|
||||||
|
this.cursorVisible = delegate.isCursorVisible();
|
||||||
|
this.ringCounter = delegate.getRingCounter();
|
||||||
|
if (delegate.getPSUpdate() != null) {
|
||||||
|
this.psUpdate = new ECLPSUpdate(delegate.getPSUpdate());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetType() { return type; }
|
||||||
|
public int getType() { return type; }
|
||||||
|
|
||||||
|
public ECLPS GetPS() {
|
||||||
|
Object src = GetSource();
|
||||||
|
if (src instanceof ECLPS) {
|
||||||
|
return (ECLPS) src;
|
||||||
|
} else if (src instanceof haus.nightmare.lib3270j.ecl.ECLPS) {
|
||||||
|
return new ECLPS((haus.nightmare.lib3270j.ecl.ECLPS) src);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetStart(int start) { this.start = start; }
|
||||||
|
public int GetStart() { return start; }
|
||||||
|
public int getStart() { return start; }
|
||||||
|
|
||||||
|
public int GetStartRow() { return startRow; }
|
||||||
|
public int getStartRow() { return startRow; }
|
||||||
|
|
||||||
|
public int GetStartCol() { return startCol; }
|
||||||
|
public int getStartCol() { return startCol; }
|
||||||
|
|
||||||
|
public void SetEnd(int end) { this.end = end; }
|
||||||
|
public int GetEnd() { return end; }
|
||||||
|
public int getEnd() { return end; }
|
||||||
|
|
||||||
|
public int GetEndRow() { return endRow; }
|
||||||
|
public int getEndRow() { return endRow; }
|
||||||
|
|
||||||
|
public int GetEndCol() { return endCol; }
|
||||||
|
public int getEndCol() { return endCol; }
|
||||||
|
|
||||||
|
public int GetOldCursorPos() { return oldCursorPos; }
|
||||||
|
public int getOldCursorPos() { return oldCursorPos; }
|
||||||
|
|
||||||
|
public int GetNewCursorPos() { return newCursorPos; }
|
||||||
|
public int getNewCursorPos() { return newCursorPos; }
|
||||||
|
|
||||||
|
public boolean IsFullUpdate() { return fullUpdate; }
|
||||||
|
public boolean isFullUpdate() { return fullUpdate; }
|
||||||
|
|
||||||
|
public boolean IsCursorVisible() { return cursorVisible; }
|
||||||
|
public boolean isCursorVisible() { return cursorVisible; }
|
||||||
|
|
||||||
|
public int GetRingCounter() { return ringCounter; }
|
||||||
|
public int getRingCounter() { return ringCounter; }
|
||||||
|
|
||||||
|
public ECLPSUpdate GetPSUpdate() { return psUpdate; }
|
||||||
|
public ECLPSUpdate getPSUpdate() { return psUpdate; }
|
||||||
|
|
||||||
|
public haus.nightmare.lib3270j.ecl.ECLPSEvent getDelegate() { return delegate; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.ibm.eNetwork.ECL.event;
|
||||||
|
|
||||||
|
import com.ibm.eNetwork.ECL.ECLErr;
|
||||||
|
import com.ibm.eNetwork.ECL.ECLPS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Presentation Space listener interface matching IBM Host On-Demand ECLPSListener.
|
||||||
|
*/
|
||||||
|
public interface ECLPSListener {
|
||||||
|
void PSNotifyEvent(ECLPSEvent event);
|
||||||
|
default void PSNotifyStop(ECLPS ps, int reason) {}
|
||||||
|
default void PSNotifyError(ECLPS ps, ECLErr err) {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.ibm.eNetwork.ECL.event;
|
||||||
|
|
||||||
|
import com.ibm.eNetwork.ECL.ECLXfer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File transfer event matching IBM Host On-Demand ECLXferEvent.
|
||||||
|
*/
|
||||||
|
public class ECLXferEvent extends ECLEvent {
|
||||||
|
|
||||||
|
private final String sourceFile;
|
||||||
|
private final String targetFile;
|
||||||
|
private final boolean receive;
|
||||||
|
private final long unitsDone;
|
||||||
|
private final long unitsToDo;
|
||||||
|
|
||||||
|
public ECLXferEvent(Object source, long unitsDone, String sourceFile, String targetFile, boolean receive) {
|
||||||
|
this(source, unitsDone, -1L, sourceFile, targetFile, receive);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLXferEvent(Object source, long unitsDone, long unitsToDo, String sourceFile, String targetFile, boolean receive) {
|
||||||
|
super(source);
|
||||||
|
this.unitsDone = unitsDone;
|
||||||
|
this.unitsToDo = unitsToDo;
|
||||||
|
this.sourceFile = sourceFile;
|
||||||
|
this.targetFile = targetFile;
|
||||||
|
this.receive = receive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLXfer GetECLXfer() {
|
||||||
|
Object src = GetSource();
|
||||||
|
return (src instanceof ECLXfer) ? (ECLXfer) src : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String GetSourceFile() { return sourceFile; }
|
||||||
|
public String getSourceFile() { return sourceFile; }
|
||||||
|
|
||||||
|
public String GetTargetFile() { return targetFile; }
|
||||||
|
public String getTargetFile() { return targetFile; }
|
||||||
|
|
||||||
|
public long GetBytesSent() { return unitsDone; }
|
||||||
|
public long getBytesSent() { return unitsDone; }
|
||||||
|
|
||||||
|
public long GetBytesToSend() { return unitsToDo; }
|
||||||
|
public long getBytesToSend() { return unitsToDo; }
|
||||||
|
|
||||||
|
public boolean IsReceive() { return receive; }
|
||||||
|
public boolean isReceive() { return receive; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.ibm.eNetwork.ECL.event;
|
||||||
|
|
||||||
|
import com.ibm.eNetwork.ECL.ECLErr;
|
||||||
|
import com.ibm.eNetwork.ECL.ECLXfer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File transfer listener interface matching IBM Host On-Demand ECLXferListener.
|
||||||
|
*/
|
||||||
|
public interface ECLXferListener {
|
||||||
|
void XferNotifyEvent(ECLXferEvent event);
|
||||||
|
default void XferNotifyStop(ECLXfer xfer, int reason) {}
|
||||||
|
default void XferNotifyError(ECLXfer xfer, ECLErr err) {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.ibm.eNetwork.ECL.macrovariable;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compatibility facade for IBM Host On-Demand MacroExpressionParser.
|
||||||
|
*/
|
||||||
|
public class MacroExpressionParser extends haus.nightmare.lib3270j.macro.variable.MacroExpressionParser implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public MacroExpressionParser() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.ibm.eNetwork.ECL.macrovariable;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compatibility facade for IBM Host On-Demand MacroVariables.
|
||||||
|
*/
|
||||||
|
public class MacroVariables extends haus.nightmare.lib3270j.macro.variable.MacroVariables implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public MacroVariables() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.ibm.eNetwork.ECL.screenreco;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLSDAttrib drop-in facade.
|
||||||
|
*/
|
||||||
|
public class ECLSDAttrib extends haus.nightmare.lib3270j.ecl.screenreco.ECLSDAttrib {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public ECLSDAttrib() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDAttrib(char attrib, int row, int col, int plane) {
|
||||||
|
super(attrib, row, col, plane);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDAttrib(char attrib, int row, int col, int plane, boolean optional) {
|
||||||
|
super(attrib, row, col, plane, optional);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.ibm.eNetwork.ECL.screenreco;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLSDBlock drop-in facade.
|
||||||
|
*/
|
||||||
|
public class ECLSDBlock extends haus.nightmare.lib3270j.ecl.screenreco.ECLSDBlock {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public ECLSDBlock() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDBlock(int srow, int scol, boolean caseSense, boolean optional) {
|
||||||
|
super(srow, scol, caseSense, optional);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDBlock(int srow, int scol, int erow, int ecol, boolean caseSense, boolean optional) {
|
||||||
|
super(srow, scol, erow, ecol, caseSense, optional);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDBlock(String[] textLines, int srow, int scol, boolean caseSense) {
|
||||||
|
super(textLines, srow, scol, caseSense);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.ibm.eNetwork.ECL.screenreco;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLSDBlockString drop-in facade.
|
||||||
|
*/
|
||||||
|
public class ECLSDBlockString extends haus.nightmare.lib3270j.ecl.screenreco.ECLSDBlockString {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public ECLSDBlockString() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDBlockString(String text) {
|
||||||
|
super(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.ibm.eNetwork.ECL.screenreco;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLSDChecksum drop-in facade.
|
||||||
|
*/
|
||||||
|
public class ECLSDChecksum extends haus.nightmare.lib3270j.ecl.screenreco.ECLSDChecksum {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public ECLSDChecksum() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDChecksum(int value) {
|
||||||
|
super(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDChecksum(int value, boolean optional) {
|
||||||
|
super(value, optional);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.ibm.eNetwork.ECL.screenreco;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLSDCursor drop-in facade.
|
||||||
|
*/
|
||||||
|
public class ECLSDCursor extends haus.nightmare.lib3270j.ecl.screenreco.ECLSDCursor {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public ECLSDCursor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDCursor(int row, int col) {
|
||||||
|
super(row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDCursor(int row, int col, boolean optional) {
|
||||||
|
super(row, col, optional);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.ibm.eNetwork.ECL.screenreco;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLSDFields drop-in facade.
|
||||||
|
*/
|
||||||
|
public class ECLSDFields extends haus.nightmare.lib3270j.ecl.screenreco.ECLSDFields {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public ECLSDFields() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDFields(int num) {
|
||||||
|
super(num);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDFields(int num, boolean optional) {
|
||||||
|
super(num, optional);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.ibm.eNetwork.ECL.screenreco;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLSDInputFields drop-in facade.
|
||||||
|
*/
|
||||||
|
public class ECLSDInputFields extends haus.nightmare.lib3270j.ecl.screenreco.ECLSDInputFields {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public ECLSDInputFields() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDInputFields(int num) {
|
||||||
|
super(num);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDInputFields(int num, boolean optional) {
|
||||||
|
super(num, optional);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.ibm.eNetwork.ECL.screenreco;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLSDOIA drop-in facade.
|
||||||
|
*/
|
||||||
|
public class ECLSDOIA extends haus.nightmare.lib3270j.ecl.screenreco.ECLSDOIA {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public ECLSDOIA() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDOIA(int type) {
|
||||||
|
super(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDOIA(int type, boolean optional) {
|
||||||
|
super(type, optional);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDOIA(String status, boolean optional) {
|
||||||
|
super(status, optional);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.ibm.eNetwork.ECL.screenreco;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLSDString drop-in facade.
|
||||||
|
*/
|
||||||
|
public class ECLSDString extends haus.nightmare.lib3270j.ecl.screenreco.ECLSDString {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public ECLSDString() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDString(String text) {
|
||||||
|
super(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDString(String text, int row, int col) {
|
||||||
|
super(text, row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDString(String text, int row, int col, boolean caseSense) {
|
||||||
|
super(text, row, col, caseSense);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDString(String text, int row, int col, boolean caseSense, boolean optional) {
|
||||||
|
super(text, row, col, caseSense, optional);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDString(String text, int srow, int scol, int erow, int ecol, boolean caseSense, boolean optional) {
|
||||||
|
super(text, srow, scol, erow, ecol, caseSense, optional);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDString(String text, int srow, int scol, int erow, int ecol, boolean caseSense, boolean optional, boolean wrap) {
|
||||||
|
super(text, srow, scol, erow, ecol, caseSense, optional, wrap);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.ibm.eNetwork.ECL.screenreco;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Host On-Demand ECLScreenDescriptor drop-in facade.
|
||||||
|
*/
|
||||||
|
public abstract class ECLScreenDescriptor extends haus.nightmare.lib3270j.ecl.screenreco.ECLScreenDescriptor {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
protected ECLScreenDescriptor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ECLScreenDescriptor(boolean optional) {
|
||||||
|
super(optional);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.ibm.eNetwork.beans.HOD;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import com.ibm.eNetwork.ECL.ECLSession;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compatibility facade for IBM Host On-Demand Macro bean.
|
||||||
|
*/
|
||||||
|
public class Macro extends haus.nightmare.lib3270j.macro.Macro implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public Macro() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Macro(ECLSession session) {
|
||||||
|
super(session != null ? session.getDelegate() : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Macro(haus.nightmare.lib3270j.ecl.ECLSession session) {
|
||||||
|
super(session);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.ibm.eNetwork.beans.HOD;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compatibility facade for IBM Host On-Demand MacroAction.
|
||||||
|
*/
|
||||||
|
public abstract class MacroAction extends haus.nightmare.lib3270j.macro.action.MacroAction implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.ibm.eNetwork.beans.HOD;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compatibility facade for IBM Host On-Demand MacroActions.
|
||||||
|
*/
|
||||||
|
public class MacroActions extends haus.nightmare.lib3270j.macro.MacroActions implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public MacroActions() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.ibm.eNetwork.beans.HOD;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compatibility facade for IBM Host On-Demand MacroNextScreens.
|
||||||
|
*/
|
||||||
|
public class MacroNextScreens extends haus.nightmare.lib3270j.macro.MacroNextScreens implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public MacroNextScreens() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.ibm.eNetwork.beans.HOD;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compatibility facade for IBM Host On-Demand MacroScreen.
|
||||||
|
*/
|
||||||
|
public class MacroScreen extends haus.nightmare.lib3270j.macro.MacroScreen implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public MacroScreen() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MacroScreen(String name) {
|
||||||
|
super(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.ibm.eNetwork.beans.HOD;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compatibility facade for IBM Host On-Demand MacroScreens.
|
||||||
|
*/
|
||||||
|
public class MacroScreens extends haus.nightmare.lib3270j.macro.MacroScreens implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public MacroScreens() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -69,6 +69,12 @@ public class ConnectionConfig {
|
|||||||
private int reconnectMaxRetries = 5;
|
private int reconnectMaxRetries = 5;
|
||||||
private int tcpUserTimeoutMs = 0;
|
private int tcpUserTimeoutMs = 0;
|
||||||
|
|
||||||
|
// Host operating system type (TSO, CMS, CICS)
|
||||||
|
private String hostType = "TSO";
|
||||||
|
private boolean savedConnection = false;
|
||||||
|
private String savedProfileId = null;
|
||||||
|
private String savedProfileName = null;
|
||||||
|
|
||||||
public ConnectionConfig() {}
|
public ConnectionConfig() {}
|
||||||
|
|
||||||
public ConnectionConfig(String host, int port) {
|
public ConnectionConfig(String host, int port) {
|
||||||
@@ -312,6 +318,51 @@ public class ConnectionConfig {
|
|||||||
public int getTcpUserTimeoutMs() { return tcpUserTimeoutMs; }
|
public int getTcpUserTimeoutMs() { return tcpUserTimeoutMs; }
|
||||||
public void setTcpUserTimeoutMs(int timeoutMs) { this.tcpUserTimeoutMs = Math.max(0, timeoutMs); }
|
public void setTcpUserTimeoutMs(int timeoutMs) { this.tcpUserTimeoutMs = Math.max(0, timeoutMs); }
|
||||||
|
|
||||||
|
public String getHostType() {
|
||||||
|
return hostType != null ? hostType : "TSO";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHostType(String hostType) {
|
||||||
|
this.hostType = (hostType != null && !hostType.trim().isEmpty()) ? hostType.trim().toUpperCase() : "TSO";
|
||||||
|
}
|
||||||
|
|
||||||
|
public haus.nightmare.lib3270j.ft.FTConfig.HostType getFtHostType() {
|
||||||
|
if (hostType != null) {
|
||||||
|
try {
|
||||||
|
return haus.nightmare.lib3270j.ft.FTConfig.HostType.valueOf(hostType.trim().toUpperCase());
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
return haus.nightmare.lib3270j.ft.FTConfig.HostType.TSO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFtHostType(haus.nightmare.lib3270j.ft.FTConfig.HostType ftHostType) {
|
||||||
|
this.hostType = (ftHostType != null) ? ftHostType.name() : "TSO";
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSavedConnection() {
|
||||||
|
return savedConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSavedConnection(boolean savedConnection) {
|
||||||
|
this.savedConnection = savedConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSavedProfileId() {
|
||||||
|
return savedProfileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSavedProfileId(String savedProfileId) {
|
||||||
|
this.savedProfileId = savedProfileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSavedProfileName() {
|
||||||
|
return savedProfileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSavedProfileName(String savedProfileName) {
|
||||||
|
this.savedProfileName = savedProfileName;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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"), proxy flags (e.g. "--proxy=http://proxy:8080 host:23"),
|
* plain TN3270 (e.g. "P:host:port", "plain:host:port", "non-e:host:port"), proxy flags (e.g. "--proxy=http://proxy:8080 host:23"),
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import haus.nightmare.lib3270j.telnet.TelnetFSM;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main API entry point for lib3270j.
|
* Main API entry point for lib3270j.
|
||||||
@@ -46,6 +47,7 @@ public class Telnet3270Client {
|
|||||||
|
|
||||||
private final java.util.concurrent.atomic.AtomicBoolean reconnecting = new java.util.concurrent.atomic.AtomicBoolean(false);
|
private final java.util.concurrent.atomic.AtomicBoolean reconnecting = new java.util.concurrent.atomic.AtomicBoolean(false);
|
||||||
private volatile Thread reconnectThread;
|
private volatile Thread reconnectThread;
|
||||||
|
private volatile boolean intentionallyDisconnected = false;
|
||||||
|
|
||||||
public Telnet3270Client(ConnectionConfig config) {
|
public Telnet3270Client(ConnectionConfig config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
@@ -92,6 +94,9 @@ public class Telnet3270Client {
|
|||||||
ps.notifyAlarm();
|
ps.notifyAlarm();
|
||||||
}
|
}
|
||||||
@Override public void onKeyboardUnlocked() {
|
@Override public void onKeyboardUnlocked() {
|
||||||
|
if (screenBuffer != null) {
|
||||||
|
screenBuffer.getHistoryBuffer().snapshot(screenBuffer);
|
||||||
|
}
|
||||||
ps.notifyKeyUnlocked();
|
ps.notifyKeyUnlocked();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -127,6 +132,7 @@ public class Telnet3270Client {
|
|||||||
* then returns while telnet/TN3270E negotiation continues asynchronously.
|
* then returns while telnet/TN3270E negotiation continues asynchronously.
|
||||||
*/
|
*/
|
||||||
public void connect() throws IOException {
|
public void connect() throws IOException {
|
||||||
|
intentionallyDisconnected = false;
|
||||||
log.info("Connecting to " + config.getHost() + ":" + config.getPort() +
|
log.info("Connecting to " + config.getHost() + ":" + config.getPort() +
|
||||||
" model=" + config.getModel());
|
" model=" + config.getModel());
|
||||||
|
|
||||||
@@ -190,8 +196,16 @@ public class Telnet3270Client {
|
|||||||
* Disconnect from the host.
|
* Disconnect from the host.
|
||||||
*/
|
*/
|
||||||
public void disconnect() {
|
public void disconnect() {
|
||||||
|
intentionallyDisconnected = true;
|
||||||
cancelAutoReconnect();
|
cancelAutoReconnect();
|
||||||
if (connection != null) {
|
if (connection != null) {
|
||||||
|
try {
|
||||||
|
if (fsm != null) {
|
||||||
|
fsm.sendTelnetDisconnect();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.log(Level.FINE, "Error during Telnet disconnect negotiation", e);
|
||||||
|
}
|
||||||
connection.disconnect();
|
connection.disconnect();
|
||||||
connection = null;
|
connection = null;
|
||||||
}
|
}
|
||||||
@@ -200,14 +214,20 @@ public class Telnet3270Client {
|
|||||||
|
|
||||||
public void cancelAutoReconnect() {
|
public void cancelAutoReconnect() {
|
||||||
reconnecting.set(false);
|
reconnecting.set(false);
|
||||||
if (reconnectThread != null) {
|
Thread t = reconnectThread;
|
||||||
reconnectThread.interrupt();
|
if (t != null) {
|
||||||
|
t.interrupt();
|
||||||
|
try {
|
||||||
|
t.join(200);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
reconnectThread = null;
|
reconnectThread = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initiateAutoReconnect() {
|
public void initiateAutoReconnect() {
|
||||||
if (config == null || !config.isAutoReconnect()) {
|
if (intentionallyDisconnected || config == null || !config.isAutoReconnect()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!reconnecting.compareAndSet(false, true)) {
|
if (!reconnecting.compareAndSet(false, true)) {
|
||||||
@@ -658,6 +678,9 @@ public class Telnet3270Client {
|
|||||||
public void setWordWrap(boolean b) { if (screenBuffer != null) screenBuffer.setEntryAssistWordWrap(b); if (oia != null) oia.notifyOIAChanged(); }
|
public void setWordWrap(boolean b) { if (screenBuffer != null) screenBuffer.setEntryAssistWordWrap(b); if (oia != null) oia.notifyOIAChanged(); }
|
||||||
public void toggleWordWrap() { setWordWrap(!isWordWrap()); }
|
public void toggleWordWrap() { setWordWrap(!isWordWrap()); }
|
||||||
|
|
||||||
|
public boolean isPasteWordBreak() { return inputProcessor != null && inputProcessor.isPasteWordBreak(); }
|
||||||
|
public void setPasteWordBreak(boolean b) { if (inputProcessor != null) inputProcessor.setPasteWordBreak(b); }
|
||||||
|
|
||||||
/** APL Keyboard Mode operations. */
|
/** APL Keyboard Mode operations. */
|
||||||
public boolean isAplMode() { return inputProcessor != null && inputProcessor.isAplKeyboardMode(); }
|
public boolean isAplMode() { return inputProcessor != null && inputProcessor.isAplKeyboardMode(); }
|
||||||
public void setAplMode(boolean b) { if (inputProcessor != null) inputProcessor.setAplKeyboardMode(b); }
|
public void setAplMode(boolean b) { if (inputProcessor != null) inputProcessor.setAplKeyboardMode(b); }
|
||||||
@@ -705,4 +728,8 @@ public class Telnet3270Client {
|
|||||||
public haus.nightmare.lib3270j.graphics.GocaDecoder getGocaDecoder() {
|
public haus.nightmare.lib3270j.graphics.GocaDecoder getGocaDecoder() {
|
||||||
return dsProcessor.getGocaDecoder();
|
return dsProcessor.getGocaDecoder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public haus.nightmare.lib3270j.screen.ScreenHistoryBuffer getScreenHistoryBuffer() {
|
||||||
|
return screenBuffer != null ? screenBuffer.getHistoryBuffer() : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,12 +27,16 @@ public abstract class AbstractDBCSCodePage extends AbstractCodePage {
|
|||||||
int[] sbcsMapping, String nioCharsetName) {
|
int[] sbcsMapping, String nioCharsetName) {
|
||||||
super(id, description, cpgid, cgcsgid, sbcsMapping);
|
super(id, description, cpgid, cgcsgid, sbcsMapping);
|
||||||
if (nioCharsetName != null) {
|
if (nioCharsetName != null) {
|
||||||
|
for (String candidate : nioCharsetName.split(",")) {
|
||||||
|
candidate = candidate.trim();
|
||||||
try {
|
try {
|
||||||
if (Charset.isSupported(nioCharsetName)) {
|
if (Charset.isSupported(candidate)) {
|
||||||
this.nioCharset = Charset.forName(nioCharsetName);
|
this.nioCharset = Charset.forName(candidate);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Initialize ideographic space (0x4040 <-> \u3000)
|
// Initialize ideographic space (0x4040 <-> \u3000)
|
||||||
registerDbcsPair(0x4040, '\u3000');
|
registerDbcsPair(0x4040, '\u3000');
|
||||||
}
|
}
|
||||||
@@ -250,4 +254,78 @@ public abstract class AbstractDBCSCodePage extends AbstractCodePage {
|
|||||||
public byte[] utf8WithSOSIToEbcdic(String s) {
|
public byte[] utf8WithSOSIToEbcdic(String s) {
|
||||||
return stringToEbcdic(s, true);
|
return stringToEbcdic(s, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Double-byte character set Graphic Character Set Global ID (DBGID/CGCSGID) matching HoD DBGIDTable.
|
||||||
|
*/
|
||||||
|
public int getDbCgcsgid() {
|
||||||
|
switch (getCpgid()) {
|
||||||
|
case 930:
|
||||||
|
case 939:
|
||||||
|
case 290:
|
||||||
|
return 370; // 0x0172
|
||||||
|
case 933:
|
||||||
|
return 934; // 0x03A6
|
||||||
|
case 935:
|
||||||
|
return 937; // 0x03A9
|
||||||
|
case 937:
|
||||||
|
return 935; // 0x03A7
|
||||||
|
case 1390:
|
||||||
|
case 1399:
|
||||||
|
case 1364:
|
||||||
|
case 1371:
|
||||||
|
case 1388:
|
||||||
|
return 65535; // 0xFFFF
|
||||||
|
default:
|
||||||
|
return 370;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Double-byte character set Code Page Global ID (DBCS CPGID) matching HoD DBGIDTable.
|
||||||
|
*/
|
||||||
|
public int getDbCpgid() {
|
||||||
|
switch (getCpgid()) {
|
||||||
|
case 930:
|
||||||
|
case 939:
|
||||||
|
case 290:
|
||||||
|
case 1390:
|
||||||
|
case 1399:
|
||||||
|
return 300; // 0x012C
|
||||||
|
case 933:
|
||||||
|
case 1364:
|
||||||
|
return 834; // 0x0342
|
||||||
|
case 935:
|
||||||
|
case 1388:
|
||||||
|
return 837; // 0x0345
|
||||||
|
case 937:
|
||||||
|
case 1371:
|
||||||
|
return 835; // 0x0343
|
||||||
|
default:
|
||||||
|
return 300;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Double-byte character set section start byte matching HoD DBCSSectTable.
|
||||||
|
*/
|
||||||
|
public byte getDbcsSectionStart() {
|
||||||
|
return 0x41;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Double-byte character set section end byte matching HoD DBCSSectTable.
|
||||||
|
*/
|
||||||
|
public byte getDbcsSectionEnd() {
|
||||||
|
switch (getCpgid()) {
|
||||||
|
case 290:
|
||||||
|
case 930:
|
||||||
|
case 935:
|
||||||
|
case 939:
|
||||||
|
return 0x7F;
|
||||||
|
default:
|
||||||
|
return (byte) 0xFE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -311,6 +311,12 @@ public class CodePageRegistry {
|
|||||||
register(new Cp1388());
|
register(new Cp1388());
|
||||||
register(new Cp1371());
|
register(new Cp1371());
|
||||||
|
|
||||||
|
// Japanese Katakana & Latin SBCS / DBCS Euro
|
||||||
|
register(new Cp290());
|
||||||
|
register(new Cp1027());
|
||||||
|
register(new Cp1390());
|
||||||
|
register(new Cp1399());
|
||||||
|
|
||||||
// Setup Extended Aliases
|
// Setup Extended Aliases
|
||||||
addAlias("ar", "420");
|
addAlias("ar", "420");
|
||||||
addAlias("arabic", "420");
|
addAlias("arabic", "420");
|
||||||
@@ -360,10 +366,12 @@ public class CodePageRegistry {
|
|||||||
addAlias("zh-tw-ext", "1371");
|
addAlias("zh-tw-ext", "1371");
|
||||||
|
|
||||||
// HoD Converter aliases mapping all HoD converter names to CodePage / Charset
|
// HoD Converter aliases mapping all HoD converter names to CodePage / Charset
|
||||||
addAlias("1390", "930");
|
addAlias("1390", "1390");
|
||||||
addAlias("1390jis2004", "930");
|
addAlias("1390jis2004", "1390");
|
||||||
addAlias("1399", "939");
|
addAlias("1399", "1399");
|
||||||
addAlias("1399jis2004", "939");
|
addAlias("1399jis2004", "1399");
|
||||||
|
addAlias("290", "290");
|
||||||
|
addAlias("1027", "1027");
|
||||||
addAlias("937macau", "937");
|
addAlias("937macau", "937");
|
||||||
addAlias("1364", "933");
|
addAlias("1364", "933");
|
||||||
addAlias("1379", "937");
|
addAlias("1379", "937");
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 1027 (Japanese Latin Extended SBCS).
|
||||||
|
* Conforms 1:1 to IBM Host On-Demand com.ibm.eNetwork.HOD.converters.ja.ByteToCharCp939.singleByteToChar.
|
||||||
|
* CCSID / CPGID: 1027, GCSGID: 1172.
|
||||||
|
*/
|
||||||
|
public class Cp1027 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "1027";
|
||||||
|
public static final String DESCRIPTION = "Japanese Latin Extended - EBCDIC";
|
||||||
|
public static final int CPGID = 1027;
|
||||||
|
public static final int GCSGID = 1172;
|
||||||
|
|
||||||
|
private static final String SBCS_STR =
|
||||||
|
"\u0000\u0001\u0002\u0003\u009c\t\u0086\u007f\u0097\u008d\u008e\u000b\f\r\u000e\u000f" +
|
||||||
|
"\u0010\u0011\u0012\u0013\u009d\u0085\b\u0087\u0018\u0019\u0092\u008f\u001c\u001d\u001e\u001f" +
|
||||||
|
"\u0080\u0081\u0082\u0083\u0084\n\u0017\u001b\u0088\u0089\u008a\u008b\u008c\u0005\u0006\u0007" +
|
||||||
|
"\u0090\u0091\u0016\u0093\u0094\u0095\u0096\u0004\u0098\u0099\u009a\u009b\u0014\u0015\u009e\u001a" +
|
||||||
|
" \ufffd\uff61\uff62\uff63\uff64\uff65\uff66\uff67\uff68\u00a2.<(+|" +
|
||||||
|
"&\uff69\uff6a\uff6b\uff6c\uff6d\uff6e\uff6f\uff70\uff71!$*);\u00ac" +
|
||||||
|
"-/\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79\ufffd,%_>?" +
|
||||||
|
"\uff7a\uff7b\uff7c\uff7d\uff7e\uff7f\uff80\uff81\uff82`:#@'=\"" +
|
||||||
|
"\ufffdabcdefghi\uff83\uff84\uff85\uff86\uff87\uff88" +
|
||||||
|
"\ufffdjklmnopqr\uff89\uff8a\uff8b\uff8c\uff8d\uff8e" +
|
||||||
|
"\u203e~stuvwxyz\uff8f\uff90\uff91[\uff92\uff93^" +
|
||||||
|
"\u00a3\u00a5\uff94\uff95\uff96\uff97\uff98\uff99\uff9a\uff9b\uff9c\uff9d]\uff9e\uff9f" +
|
||||||
|
"{ABCDEFGHI\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd" +
|
||||||
|
"}JKLMNOPQR\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd" +
|
||||||
|
"\\\ufffdSTUVWXYZ\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd" +
|
||||||
|
"0123456789\ufffd\ufffd\ufffd\ufffd\ufffd\u009f";
|
||||||
|
|
||||||
|
public static final int[] MAPPING = new int[256];
|
||||||
|
static {
|
||||||
|
for (int i = 0; i < 256; i++) {
|
||||||
|
MAPPING[i] = (i < SBCS_STR.length()) ? SBCS_STR.charAt(i) : i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cp1027() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 1390 (Japanese Katakana Extended Mixed DBCS with Euro support: host SBCS CP290+Euro / DBCS CP16684).
|
||||||
|
* CCSID / CPGID: 1390, GCSGID: 1172.
|
||||||
|
*/
|
||||||
|
public class Cp1390 extends AbstractDBCSCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "1390";
|
||||||
|
public static final String DESCRIPTION = "Japanese Katakana Extended Mixed DBCS (with Euro)";
|
||||||
|
public static final int CPGID = 1390;
|
||||||
|
public static final int GCSGID = 1172;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = Cp290.MAPPING.clone();
|
||||||
|
static {
|
||||||
|
// Euro symbol at 0xE1
|
||||||
|
MAPPING[0xE1] = 0x20AC;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cp1390() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, MAPPING, "x-IBM1390,IBM1390,Cp1390,x-IBM930,IBM930,Cp930");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 1399 (Japanese Latin Extended Mixed DBCS with Euro support: host SBCS CP1027+Euro / DBCS CP16684).
|
||||||
|
* CCSID / CPGID: 1399, GCSGID: 1172.
|
||||||
|
*/
|
||||||
|
public class Cp1399 extends AbstractDBCSCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "1399";
|
||||||
|
public static final String DESCRIPTION = "Japanese Latin Extended Mixed DBCS (with Euro)";
|
||||||
|
public static final int CPGID = 1399;
|
||||||
|
public static final int GCSGID = 1172;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = Cp1027.MAPPING.clone();
|
||||||
|
static {
|
||||||
|
// Euro symbol at 0xE1
|
||||||
|
MAPPING[0xE1] = 0x20AC;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cp1399() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, MAPPING, "x-IBM1399,IBM1399,Cp1399,x-IBM939,IBM939,Cp939");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 290 (Japanese Katakana Extended SBCS).
|
||||||
|
* Conforms 1:1 to IBM Host On-Demand com.ibm.eNetwork.HOD.converters.ja.ByteToCharCp290.
|
||||||
|
* CCSID / CPGID: 290, GCSGID: 1172.
|
||||||
|
*/
|
||||||
|
public class Cp290 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "290";
|
||||||
|
public static final String DESCRIPTION = "Japanese Katakana Extended - EBCDIC";
|
||||||
|
public static final int CPGID = 290;
|
||||||
|
public static final int GCSGID = 1172;
|
||||||
|
|
||||||
|
private static final String SBCS_STR =
|
||||||
|
"\u0000\u0001\u0002\u0003\u009c\t\u0086\u007f\u0097\u008d\u008e\u000b\f\r\u000e\u000f" +
|
||||||
|
"\u0010\u0011\u0012\u0013\u009d\u0085\b\u0087\u0018\u0019\u0092\u008f\u001c\u001d\u001e\u001f" +
|
||||||
|
"\u0080\u0081\u0082\u0083\u0084\n\u0017\u001b\u0088\u0089\u008a\u008b\u008c\u0005\u0006\u0007" +
|
||||||
|
"\u0090\u0091\u0016\u0093\u0094\u0095\u0096\u0004\u0098\u0099\u009a\u009b\u0014\u0015\u009e\u001a" +
|
||||||
|
" \uff61\uff62\uff63\uff64\uff65\uff66\uff67\uff68\uff69\u00a3.<(+|" +
|
||||||
|
"&\uff6a\uff6b\uff6c\uff6d\uff6e\uff6f\ufffd\uff70\ufffd!\u00a5*);\u00ac" +
|
||||||
|
"-/\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\ufffd,%_>?" +
|
||||||
|
"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000:#@'=\"" +
|
||||||
|
"\u0000\uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79\uff7a\u0000\uff7b\uff7c\uff7d\uff7e" +
|
||||||
|
"\uff7f\uff80\uff81\uff82\uff83\uff84\uff85\uff86\uff87\uff88\uff89\u0000\ufffd\uff8a\uff8b\uff8c" +
|
||||||
|
"\u0000\u203e\uff8d\uff8e\uff8f\uff90\uff91\uff92\uff93\uff94\uff95\u0000\uff96\uff97\uff98\uff99" +
|
||||||
|
"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\uff9a\uff9b\uff9c\uff9d\uff9e\uff9f" +
|
||||||
|
"\u0000ABCDEFGHI\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd" +
|
||||||
|
"\u0000JKLMNOPQR\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd" +
|
||||||
|
"$\ufffdSTUVWXYZ\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd" +
|
||||||
|
"0123456789\ufffd\ufffd\ufffd\ufffd\ufffd\u009f";
|
||||||
|
|
||||||
|
public static final int[] MAPPING = new int[256];
|
||||||
|
static {
|
||||||
|
for (int i = 0; i < 256; i++) {
|
||||||
|
MAPPING[i] = (i < SBCS_STR.length()) ? SBCS_STR.charAt(i) : i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cp290() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,6 @@ public class Cp930 extends AbstractDBCSCodePage {
|
|||||||
public static final int GCSGID = 1172;
|
public static final int GCSGID = 1172;
|
||||||
|
|
||||||
public Cp930() {
|
public Cp930() {
|
||||||
super(ID, DESCRIPTION, CPGID, GCSGID, Cp037.MAPPING, "x-IBM930");
|
super(ID, DESCRIPTION, CPGID, GCSGID, Cp290.MAPPING, "x-IBM930,IBM930,Cp930");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,6 @@ public class Cp939 extends AbstractDBCSCodePage {
|
|||||||
public static final int GCSGID = 1172;
|
public static final int GCSGID = 1172;
|
||||||
|
|
||||||
public Cp939() {
|
public Cp939() {
|
||||||
super(ID, DESCRIPTION, CPGID, GCSGID, Cp037.MAPPING, "x-IBM939");
|
super(ID, DESCRIPTION, CPGID, GCSGID, Cp1027.MAPPING, "x-IBM939,IBM939,Cp939");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,35 @@ public class EbcdicTranslator {
|
|||||||
return isDBCSCodePage();
|
return isDBCSCodePage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized int getDbCgcsgid() {
|
||||||
|
if (activeCodePage instanceof AbstractDBCSCodePage) {
|
||||||
|
return ((AbstractDBCSCodePage) activeCodePage).getDbCgcsgid();
|
||||||
|
}
|
||||||
|
return 370;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int getDbCpgid() {
|
||||||
|
if (activeCodePage instanceof AbstractDBCSCodePage) {
|
||||||
|
return ((AbstractDBCSCodePage) activeCodePage).getDbCpgid();
|
||||||
|
}
|
||||||
|
return 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized byte getDbcsSectionStart() {
|
||||||
|
if (activeCodePage instanceof AbstractDBCSCodePage) {
|
||||||
|
return ((AbstractDBCSCodePage) activeCodePage).getDbcsSectionStart();
|
||||||
|
}
|
||||||
|
return 0x41;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized byte getDbcsSectionEnd() {
|
||||||
|
if (activeCodePage instanceof AbstractDBCSCodePage) {
|
||||||
|
return ((AbstractDBCSCodePage) activeCodePage).getDbcsSectionEnd();
|
||||||
|
}
|
||||||
|
return 0x7F;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Custom Character Translation Overrides
|
// Custom Character Translation Overrides
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|||||||
+229
-11
@@ -53,12 +53,16 @@ public class DataStreamProcessor {
|
|||||||
private boolean unlockPending = false;
|
private boolean unlockPending = false;
|
||||||
private boolean unlockSysPending = false;
|
private boolean unlockSysPending = false;
|
||||||
private boolean rcvdRead = false;
|
private boolean rcvdRead = false;
|
||||||
|
private boolean qreplyPending = false;
|
||||||
|
|
||||||
// Modal SA (set attribute) character attributes
|
// Modal SA (set attribute) character attributes
|
||||||
private byte currentFg = 0;
|
private byte currentFg = 0;
|
||||||
private byte currentBg = 0;
|
private byte currentBg = 0;
|
||||||
private byte currentGr = 0;
|
private byte currentGr = 0;
|
||||||
private byte currentCs = 0;
|
private byte currentCs = 0;
|
||||||
|
private byte currentOl = 0;
|
||||||
|
private byte currentTr = 0;
|
||||||
|
private boolean inDBCS = false;
|
||||||
|
|
||||||
/** Functional interface for sending output back through the telnet stack. */
|
/** Functional interface for sending output back through the telnet stack. */
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
@@ -180,11 +184,13 @@ public class DataStreamProcessor {
|
|||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case CMD_W:
|
case CMD_W:
|
||||||
case SNA_CMD_W:
|
case SNA_CMD_W:
|
||||||
|
qreplyPending = false;
|
||||||
programSymbolManager.commitStagedSymbols();
|
programSymbolManager.commitStagedSymbols();
|
||||||
processWrite(data, offset, length, false);
|
processWrite(data, offset, length, false);
|
||||||
break;
|
break;
|
||||||
case CMD_EW:
|
case CMD_EW:
|
||||||
case SNA_CMD_EW:
|
case SNA_CMD_EW:
|
||||||
|
qreplyPending = false;
|
||||||
programSymbolManager.commitStagedSymbols();
|
programSymbolManager.commitStagedSymbols();
|
||||||
if (log.isLoggable(Level.FINE)) {
|
if (log.isLoggable(Level.FINE)) {
|
||||||
log.fine(">>> ERASE/WRITE: clearing screen (default size)");
|
log.fine(">>> ERASE/WRITE: clearing screen (default size)");
|
||||||
@@ -205,6 +211,7 @@ public class DataStreamProcessor {
|
|||||||
break;
|
break;
|
||||||
case CMD_EWA:
|
case CMD_EWA:
|
||||||
case SNA_CMD_EWA:
|
case SNA_CMD_EWA:
|
||||||
|
qreplyPending = false;
|
||||||
programSymbolManager.commitStagedSymbols();
|
programSymbolManager.commitStagedSymbols();
|
||||||
if (log.isLoggable(Level.FINE)) {
|
if (log.isLoggable(Level.FINE)) {
|
||||||
log.fine(">>> ERASE/WRITE ALTERNATE: clearing screen (alt size)");
|
log.fine(">>> ERASE/WRITE ALTERNATE: clearing screen (alt size)");
|
||||||
@@ -233,7 +240,12 @@ public class DataStreamProcessor {
|
|||||||
case SNA_CMD_RM:
|
case SNA_CMD_RM:
|
||||||
rcvdRead = true;
|
rcvdRead = true;
|
||||||
programSymbolManager.commitStagedSymbols();
|
programSymbolManager.commitStagedSymbols();
|
||||||
|
if (qreplyPending) {
|
||||||
|
qreplyPending = false;
|
||||||
|
sendAllQueryReplies();
|
||||||
|
} else {
|
||||||
processReadModified(false);
|
processReadModified(false);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case CMD_RMA:
|
case CMD_RMA:
|
||||||
case SNA_CMD_RMA:
|
case SNA_CMD_RMA:
|
||||||
@@ -243,13 +255,19 @@ public class DataStreamProcessor {
|
|||||||
break;
|
break;
|
||||||
case CMD_EAU:
|
case CMD_EAU:
|
||||||
case SNA_CMD_EAU:
|
case SNA_CMD_EAU:
|
||||||
|
qreplyPending = false;
|
||||||
programSymbolManager.commitStagedSymbols();
|
programSymbolManager.commitStagedSymbols();
|
||||||
|
if (inputProcessor != null) {
|
||||||
|
inputProcessor.setLastAid(AID_NO);
|
||||||
|
}
|
||||||
log.info(">>> EAU: erasing all unprotected fields");
|
log.info(">>> EAU: erasing all unprotected fields");
|
||||||
screen.eraseAllUnprotected();
|
screen.eraseAllUnprotected();
|
||||||
currentFg = 0;
|
currentFg = 0;
|
||||||
currentBg = 0;
|
currentBg = 0;
|
||||||
currentGr = 0;
|
currentGr = 0;
|
||||||
currentCs = 0;
|
currentCs = 0;
|
||||||
|
currentOl = 0;
|
||||||
|
currentTr = 0;
|
||||||
break;
|
break;
|
||||||
case CMD_WSF:
|
case CMD_WSF:
|
||||||
case SNA_CMD_WSF:
|
case SNA_CMD_WSF:
|
||||||
@@ -275,6 +293,7 @@ public class DataStreamProcessor {
|
|||||||
cmd == CMD_EWA || cmd == SNA_CMD_EWA);
|
cmd == CMD_EWA || cmd == SNA_CMD_EWA);
|
||||||
if (!isWriteCmd && keyboardRestore && inputProcessor != null && !contentionResolution) {
|
if (!isWriteCmd && keyboardRestore && inputProcessor != null && !contentionResolution) {
|
||||||
inputProcessor.setKeyboardLocked(false);
|
inputProcessor.setKeyboardLocked(false);
|
||||||
|
inputProcessor.setLastAid(AID_NO);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug: dump non-empty screen lines
|
// Debug: dump non-empty screen lines
|
||||||
@@ -374,10 +393,14 @@ public class DataStreamProcessor {
|
|||||||
if (kbdRestore) {
|
if (kbdRestore) {
|
||||||
unlockPending = true;
|
unlockPending = true;
|
||||||
unlockSysPending = true;
|
unlockSysPending = true;
|
||||||
|
if (inputProcessor != null) {
|
||||||
|
inputProcessor.setLastAid(AID_NO);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!contentionResolution && kbdRestore && inputProcessor != null) {
|
if (!contentionResolution && kbdRestore && inputProcessor != null) {
|
||||||
inputProcessor.setKeyboardLocked(false);
|
inputProcessor.setKeyboardLocked(false);
|
||||||
|
inputProcessor.setLastAid(AID_NO);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resetMdt) {
|
if (resetMdt) {
|
||||||
@@ -392,6 +415,9 @@ public class DataStreamProcessor {
|
|||||||
currentBg = 0;
|
currentBg = 0;
|
||||||
currentGr = 0;
|
currentGr = 0;
|
||||||
currentCs = 0;
|
currentCs = 0;
|
||||||
|
currentOl = 0;
|
||||||
|
currentTr = 0;
|
||||||
|
inDBCS = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eraseFirst) {
|
if (eraseFirst) {
|
||||||
@@ -401,6 +427,9 @@ public class DataStreamProcessor {
|
|||||||
currentBg = 0;
|
currentBg = 0;
|
||||||
currentGr = 0;
|
currentGr = 0;
|
||||||
currentCs = 0;
|
currentCs = 0;
|
||||||
|
currentOl = 0;
|
||||||
|
currentTr = 0;
|
||||||
|
inDBCS = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process orders and data starting at byte 2
|
// Process orders and data starting at byte 2
|
||||||
@@ -431,6 +460,7 @@ public class DataStreamProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case ORDER_SF: { // Start Field
|
case ORDER_SF: { // Start Field
|
||||||
|
inDBCS = false;
|
||||||
if (pos + 1 >= end) {
|
if (pos + 1 >= end) {
|
||||||
pos = end;
|
pos = end;
|
||||||
break;
|
break;
|
||||||
@@ -452,6 +482,7 @@ public class DataStreamProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case ORDER_SFE: { // Start Field Extended
|
case ORDER_SFE: { // Start Field Extended
|
||||||
|
inDBCS = false;
|
||||||
if (pos + 1 >= end) {
|
if (pos + 1 >= end) {
|
||||||
pos = end;
|
pos = end;
|
||||||
break;
|
break;
|
||||||
@@ -497,6 +528,8 @@ public class DataStreamProcessor {
|
|||||||
currentBg = 0;
|
currentBg = 0;
|
||||||
currentGr = 0;
|
currentGr = 0;
|
||||||
currentCs = 0;
|
currentCs = 0;
|
||||||
|
currentOl = 0;
|
||||||
|
currentTr = 0;
|
||||||
break;
|
break;
|
||||||
case XA_FOREGROUND:
|
case XA_FOREGROUND:
|
||||||
currentFg = (byte) attrValue;
|
currentFg = (byte) attrValue;
|
||||||
@@ -521,6 +554,13 @@ public class DataStreamProcessor {
|
|||||||
case XA_CHARSET:
|
case XA_CHARSET:
|
||||||
currentCs = (byte) attrValue;
|
currentCs = (byte) attrValue;
|
||||||
break;
|
break;
|
||||||
|
case XA_OUTLINING:
|
||||||
|
case 0x84:
|
||||||
|
currentOl = (byte) attrValue;
|
||||||
|
break;
|
||||||
|
case XA_TRANSPARENCY:
|
||||||
|
currentTr = (byte) attrValue;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
pos += 3;
|
pos += 3;
|
||||||
lastWasOrder = true;
|
lastWasOrder = true;
|
||||||
@@ -582,6 +622,44 @@ public class DataStreamProcessor {
|
|||||||
if (toAddr >= size)
|
if (toAddr >= size)
|
||||||
toAddr = toAddr % size;
|
toAddr = toAddr % size;
|
||||||
|
|
||||||
|
boolean isDbcsSession = (translator != null && translator.isDBCS());
|
||||||
|
if (isDbcsSession && (inDBCS || currentCs == ExtendedAttribute.CS_DBCS) && pos + 4 < end) {
|
||||||
|
int fillChar2 = data[pos + 4] & 0xFF;
|
||||||
|
char uni = (translator.getCodePage() != null) ? translator.getCodePage().dbcsToUnicode(fillChar, fillChar2) : '?';
|
||||||
|
do {
|
||||||
|
ExtendedAttribute ea1 = screen.getCell(baddr);
|
||||||
|
ea1.fa = 0;
|
||||||
|
ea1.ec = (byte) fillChar;
|
||||||
|
ea1.ucs4 = uni;
|
||||||
|
ea1.fg = currentFg;
|
||||||
|
ea1.bg = currentBg;
|
||||||
|
ea1.gr = (byte) currentGr;
|
||||||
|
ea1.cs = ExtendedAttribute.CS_DBCS;
|
||||||
|
ea1.db = ExtendedAttribute.DB_LEFT;
|
||||||
|
ea1.ol = currentOl;
|
||||||
|
ea1.tr = currentTr;
|
||||||
|
baddr = (baddr + 1) % size;
|
||||||
|
if (baddr == toAddr) break;
|
||||||
|
|
||||||
|
ExtendedAttribute ea2 = screen.getCell(baddr);
|
||||||
|
ea2.fa = 0;
|
||||||
|
ea2.ec = (byte) fillChar2;
|
||||||
|
ea2.ucs4 = uni;
|
||||||
|
ea2.fg = currentFg;
|
||||||
|
ea2.bg = currentBg;
|
||||||
|
ea2.gr = (byte) currentGr;
|
||||||
|
ea2.cs = ExtendedAttribute.CS_DBCS;
|
||||||
|
ea2.db = ExtendedAttribute.DB_RIGHT;
|
||||||
|
ea2.ol = currentOl;
|
||||||
|
ea2.tr = currentTr;
|
||||||
|
baddr = (baddr + 1) % size;
|
||||||
|
} while (baddr != toAddr);
|
||||||
|
screen.setBufferAddress(baddr);
|
||||||
|
pos += 5;
|
||||||
|
lastWasOrder = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle GE (graphic escape) prefix
|
// Handle GE (graphic escape) prefix
|
||||||
byte fillCs = currentCs;
|
byte fillCs = currentCs;
|
||||||
char fillUcs4 = 0;
|
char fillUcs4 = 0;
|
||||||
@@ -605,6 +683,9 @@ public class DataStreamProcessor {
|
|||||||
ea.bg = currentBg;
|
ea.bg = currentBg;
|
||||||
ea.gr = (byte) currentGr;
|
ea.gr = (byte) currentGr;
|
||||||
ea.cs = fillCs;
|
ea.cs = fillCs;
|
||||||
|
ea.db = ExtendedAttribute.DB_NONE;
|
||||||
|
ea.ol = currentOl;
|
||||||
|
ea.tr = currentTr;
|
||||||
baddr = (baddr + 1) % size;
|
baddr = (baddr + 1) % size;
|
||||||
} while (baddr != toAddr);
|
} while (baddr != toAddr);
|
||||||
screen.setBufferAddress(baddr);
|
screen.setBufferAddress(baddr);
|
||||||
@@ -633,6 +714,8 @@ public class DataStreamProcessor {
|
|||||||
ea.bg = 0;
|
ea.bg = 0;
|
||||||
ea.gr = 0;
|
ea.gr = 0;
|
||||||
ea.cs = 0;
|
ea.cs = 0;
|
||||||
|
ea.ol = 0;
|
||||||
|
ea.tr = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
baddr = (baddr + 1) % size;
|
baddr = (baddr + 1) % size;
|
||||||
@@ -657,6 +740,8 @@ public class DataStreamProcessor {
|
|||||||
ea.fg = currentFg;
|
ea.fg = currentFg;
|
||||||
ea.bg = currentBg;
|
ea.bg = currentBg;
|
||||||
ea.gr = (byte) currentGr;
|
ea.gr = (byte) currentGr;
|
||||||
|
ea.ol = currentOl;
|
||||||
|
ea.tr = currentTr;
|
||||||
baddr = (baddr + 1) % size;
|
baddr = (baddr + 1) % size;
|
||||||
screen.setBufferAddress(baddr);
|
screen.setBufferAddress(baddr);
|
||||||
pos += 2;
|
pos += 2;
|
||||||
@@ -665,6 +750,98 @@ public class DataStreamProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
|
boolean isDbcsSession = (translator != null && translator.isDBCS());
|
||||||
|
if (isDbcsSession && b == 0x0E) { // Shift-Out (enter DBCS mode)
|
||||||
|
inDBCS = true;
|
||||||
|
ExtendedAttribute ea = screen.getCell(baddr);
|
||||||
|
ea.fa = 0;
|
||||||
|
ea.ec = (byte) 0x0E;
|
||||||
|
ea.ucs4 = ' ';
|
||||||
|
ea.cs = ExtendedAttribute.CS_BASE;
|
||||||
|
ea.db = ExtendedAttribute.DB_SO;
|
||||||
|
ea.fg = currentFg;
|
||||||
|
ea.bg = currentBg;
|
||||||
|
ea.gr = (byte) currentGr;
|
||||||
|
ea.ol = currentOl;
|
||||||
|
ea.tr = currentTr;
|
||||||
|
baddr = (baddr + 1) % size;
|
||||||
|
screen.setBufferAddress(baddr);
|
||||||
|
pos += 1;
|
||||||
|
lastWasOrder = false;
|
||||||
|
break;
|
||||||
|
} else if (isDbcsSession && b == 0x0F) { // Shift-In (exit DBCS mode)
|
||||||
|
inDBCS = false;
|
||||||
|
ExtendedAttribute ea = screen.getCell(baddr);
|
||||||
|
ea.fa = 0;
|
||||||
|
ea.ec = (byte) 0x0F;
|
||||||
|
ea.ucs4 = ' ';
|
||||||
|
ea.cs = ExtendedAttribute.CS_BASE;
|
||||||
|
ea.db = ExtendedAttribute.DB_SI;
|
||||||
|
ea.fg = currentFg;
|
||||||
|
ea.bg = currentBg;
|
||||||
|
ea.gr = (byte) currentGr;
|
||||||
|
ea.ol = currentOl;
|
||||||
|
ea.tr = currentTr;
|
||||||
|
baddr = (baddr + 1) % size;
|
||||||
|
screen.setBufferAddress(baddr);
|
||||||
|
pos += 1;
|
||||||
|
lastWasOrder = false;
|
||||||
|
break;
|
||||||
|
} else if (isDbcsSession && (inDBCS || currentCs == ExtendedAttribute.CS_DBCS)) {
|
||||||
|
if (pos + 1 < end) {
|
||||||
|
int b2 = data[pos + 1] & 0xFF;
|
||||||
|
if (b2 == 0x0F) { // Orphaned single byte before SI
|
||||||
|
ExtendedAttribute ea = screen.getCell(baddr);
|
||||||
|
ea.fa = 0;
|
||||||
|
ea.ec = (byte) b;
|
||||||
|
ea.ucs4 = translator.ebcdicToUnicode(b);
|
||||||
|
ea.fg = currentFg;
|
||||||
|
ea.bg = currentBg;
|
||||||
|
ea.gr = (byte) currentGr;
|
||||||
|
ea.cs = currentCs;
|
||||||
|
ea.ol = currentOl;
|
||||||
|
ea.tr = currentTr;
|
||||||
|
ea.db = ExtendedAttribute.DB_NONE;
|
||||||
|
baddr = (baddr + 1) % size;
|
||||||
|
screen.setBufferAddress(baddr);
|
||||||
|
pos += 1;
|
||||||
|
lastWasOrder = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
char uni = (translator.getCodePage() != null) ? translator.getCodePage().dbcsToUnicode(b, b2) : '?';
|
||||||
|
ExtendedAttribute ea1 = screen.getCell(baddr);
|
||||||
|
ea1.fa = 0;
|
||||||
|
ea1.ec = (byte) b;
|
||||||
|
ea1.ucs4 = uni;
|
||||||
|
ea1.fg = currentFg;
|
||||||
|
ea1.bg = currentBg;
|
||||||
|
ea1.gr = (byte) currentGr;
|
||||||
|
ea1.cs = ExtendedAttribute.CS_DBCS;
|
||||||
|
ea1.db = ExtendedAttribute.DB_LEFT;
|
||||||
|
ea1.ol = currentOl;
|
||||||
|
ea1.tr = currentTr;
|
||||||
|
|
||||||
|
int nextAddr = (baddr + 1) % size;
|
||||||
|
ExtendedAttribute ea2 = screen.getCell(nextAddr);
|
||||||
|
ea2.fa = 0;
|
||||||
|
ea2.ec = (byte) b2;
|
||||||
|
ea2.ucs4 = uni;
|
||||||
|
ea2.fg = currentFg;
|
||||||
|
ea2.bg = currentBg;
|
||||||
|
ea2.gr = (byte) currentGr;
|
||||||
|
ea2.cs = ExtendedAttribute.CS_DBCS;
|
||||||
|
ea2.db = ExtendedAttribute.DB_RIGHT;
|
||||||
|
ea2.ol = currentOl;
|
||||||
|
ea2.tr = currentTr;
|
||||||
|
|
||||||
|
baddr = (baddr + 2) % size;
|
||||||
|
screen.setBufferAddress(baddr);
|
||||||
|
pos += 2;
|
||||||
|
lastWasOrder = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Regular data byte (EBCDIC character)
|
// Regular data byte (EBCDIC character)
|
||||||
// Format control codes display as nulls/blanks per 3270 spec
|
// Format control codes display as nulls/blanks per 3270 spec
|
||||||
boolean isFCOrder = (b == FCORDER_NULL || b == FCORDER_FF ||
|
boolean isFCOrder = (b == FCORDER_NULL || b == FCORDER_FF ||
|
||||||
@@ -676,13 +853,18 @@ public class DataStreamProcessor {
|
|||||||
|
|
||||||
if (isFCOrder) {
|
if (isFCOrder) {
|
||||||
ea.ec = 0; // Display as null/blank
|
ea.ec = 0; // Display as null/blank
|
||||||
|
ea.ucs4 = 0;
|
||||||
} else {
|
} else {
|
||||||
ea.ec = (byte) b;
|
ea.ec = (byte) b;
|
||||||
|
ea.ucs4 = (translator != null) ? translator.ebcdicToUnicode(b) : (char) b;
|
||||||
}
|
}
|
||||||
ea.fg = currentFg;
|
ea.fg = currentFg;
|
||||||
ea.bg = currentBg;
|
ea.bg = currentBg;
|
||||||
ea.gr = (byte) currentGr;
|
ea.gr = (byte) currentGr;
|
||||||
ea.cs = currentCs;
|
ea.cs = currentCs;
|
||||||
|
ea.db = ExtendedAttribute.DB_NONE;
|
||||||
|
ea.ol = currentOl;
|
||||||
|
ea.tr = currentTr;
|
||||||
|
|
||||||
baddr = (baddr + 1) % size;
|
baddr = (baddr + 1) % size;
|
||||||
screen.setBufferAddress(baddr);
|
screen.setBufferAddress(baddr);
|
||||||
@@ -760,13 +942,26 @@ public class DataStreamProcessor {
|
|||||||
// ========== Read Buffer ==========
|
// ========== Read Buffer ==========
|
||||||
|
|
||||||
public void processReadBuffer() {
|
public void processReadBuffer() {
|
||||||
|
int aid = (inputProcessor != null && inputProcessor.getLastAid() != 0)
|
||||||
|
? inputProcessor.getLastAid()
|
||||||
|
: AID_NO;
|
||||||
|
processReadBuffer(aid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processReadBuffer(int aid) {
|
||||||
|
if (inputProcessor != null) {
|
||||||
|
inputProcessor.setLastAid(AID_NO);
|
||||||
|
byte[] data = inputProcessor.buildReadBufferInboundData(aid);
|
||||||
|
if (outputSender != null) {
|
||||||
|
outputSender.send3270Data(data);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
outputPos = 0;
|
outputPos = 0;
|
||||||
|
|
||||||
int size = screen.getRows() * screen.getCols();
|
int size = screen.getRows() * screen.getCols();
|
||||||
|
|
||||||
int aid = (inputProcessor != null && inputProcessor.getLastAid() != 0)
|
|
||||||
? inputProcessor.getLastAid()
|
|
||||||
: AID_NO;
|
|
||||||
if (inputProcessor != null) {
|
if (inputProcessor != null) {
|
||||||
inputProcessor.setLastAid(AID_NO);
|
inputProcessor.setLastAid(AID_NO);
|
||||||
}
|
}
|
||||||
@@ -783,7 +978,7 @@ public class DataStreamProcessor {
|
|||||||
// Buffer contents depending on Reply Mode
|
// Buffer contents depending on Reply Mode
|
||||||
if (mode == SF_SRM_XFIELD || mode == SF_SRM_CHAR) {
|
if (mode == SF_SRM_XFIELD || mode == SF_SRM_CHAR) {
|
||||||
// Extended Field or Character Mode
|
// Extended Field or Character Mode
|
||||||
byte curFg = 0, curBg = 0, curGr = 0, curCs = 0;
|
byte curFg = 0, curBg = 0, curGr = 0, curCs = 0, curTr = 0;
|
||||||
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()) {
|
||||||
@@ -794,6 +989,7 @@ public class DataStreamProcessor {
|
|||||||
if (ea.gr != 0) count++;
|
if (ea.gr != 0) count++;
|
||||||
if (ea.cs != 0) count++;
|
if (ea.cs != 0) count++;
|
||||||
if (ea.ol != 0) count++;
|
if (ea.ol != 0) count++;
|
||||||
|
if (ea.tr != 0) count++;
|
||||||
|
|
||||||
outputWrite(ORDER_SFE);
|
outputWrite(ORDER_SFE);
|
||||||
outputWrite(count);
|
outputWrite(count);
|
||||||
@@ -812,6 +1008,7 @@ public class DataStreamProcessor {
|
|||||||
}
|
}
|
||||||
if (ea.cs != 0) { outputWrite(XA_CHARSET); outputWrite(ea.cs & 0xFF); }
|
if (ea.cs != 0) { outputWrite(XA_CHARSET); outputWrite(ea.cs & 0xFF); }
|
||||||
if (ea.ol != 0) { outputWrite(XA_OUTLINING); outputWrite(ea.ol & 0xFF); }
|
if (ea.ol != 0) { outputWrite(XA_OUTLINING); outputWrite(ea.ol & 0xFF); }
|
||||||
|
if (ea.tr != 0) { outputWrite(XA_TRANSPARENCY); outputWrite(ea.tr & 0xFF); }
|
||||||
} else {
|
} else {
|
||||||
if (mode == SF_SRM_CHAR) {
|
if (mode == SF_SRM_CHAR) {
|
||||||
// In character mode, output SA if character attributes differ
|
// In character mode, output SA if character attributes differ
|
||||||
@@ -837,6 +1034,10 @@ public class DataStreamProcessor {
|
|||||||
outputWrite(ORDER_SA); outputWrite(XA_CHARSET); outputWrite(ea.cs & 0xFF);
|
outputWrite(ORDER_SA); outputWrite(XA_CHARSET); outputWrite(ea.cs & 0xFF);
|
||||||
curCs = ea.cs;
|
curCs = ea.cs;
|
||||||
}
|
}
|
||||||
|
if (ea.tr != curTr) {
|
||||||
|
outputWrite(ORDER_SA); outputWrite(XA_TRANSPARENCY); outputWrite(ea.tr & 0xFF);
|
||||||
|
curTr = ea.tr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
outputWrite(ea.ec & 0xFF);
|
outputWrite(ea.ec & 0xFF);
|
||||||
}
|
}
|
||||||
@@ -860,12 +1061,18 @@ public class DataStreamProcessor {
|
|||||||
// ========== Read Modified ==========
|
// ========== Read Modified ==========
|
||||||
|
|
||||||
public void processReadModified(boolean all) {
|
public void processReadModified(boolean all) {
|
||||||
|
int aid = (inputProcessor != null && inputProcessor.isKeyboardLocked() && inputProcessor.getLastAid() != 0 && inputProcessor.getLastAid() != AID_NO)
|
||||||
|
? inputProcessor.getLastAid()
|
||||||
|
: AID_NO;
|
||||||
|
processReadModified(aid, all);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processReadModified(int aid, boolean all) {
|
||||||
if (ftDft != null && ftDft.readModified()) {
|
if (ftDft != null && ftDft.readModified()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputProcessor != null) {
|
if (inputProcessor != null) {
|
||||||
int aid = (inputProcessor.getLastAid() != 0) ? inputProcessor.getLastAid() : AID_NO;
|
|
||||||
inputProcessor.setLastAid(AID_NO);
|
inputProcessor.setLastAid(AID_NO);
|
||||||
byte[] data = inputProcessor.buildReadModifiedInboundData(aid, all);
|
byte[] data = inputProcessor.buildReadModifiedInboundData(aid, all);
|
||||||
if (outputSender != null) {
|
if (outputSender != null) {
|
||||||
@@ -876,9 +1083,6 @@ public class DataStreamProcessor {
|
|||||||
|
|
||||||
outputPos = 0;
|
outputPos = 0;
|
||||||
|
|
||||||
int aid = (inputProcessor != null && inputProcessor.getLastAid() != 0)
|
|
||||||
? inputProcessor.getLastAid()
|
|
||||||
: AID_NO;
|
|
||||||
if (inputProcessor != null) {
|
if (inputProcessor != null) {
|
||||||
inputProcessor.setLastAid(AID_NO);
|
inputProcessor.setLastAid(AID_NO);
|
||||||
}
|
}
|
||||||
@@ -995,6 +1199,7 @@ public class DataStreamProcessor {
|
|||||||
byte[] psData = new byte[fieldLen - 3];
|
byte[] psData = new byte[fieldLen - 3];
|
||||||
System.arraycopy(data, pos + 3, psData, 0, psData.length);
|
System.arraycopy(data, pos + 3, psData, 0, psData.length);
|
||||||
programSymbolManager.loadps(psData);
|
programSymbolManager.loadps(psData);
|
||||||
|
notifyScreenUpdated();
|
||||||
}
|
}
|
||||||
break;
|
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
|
||||||
@@ -1040,9 +1245,11 @@ public class DataStreamProcessor {
|
|||||||
log.info("ReadPartition Query — sending base query replies");
|
log.info("ReadPartition Query — sending base query replies");
|
||||||
graphicsPlane.clear();
|
graphicsPlane.clear();
|
||||||
gocaDecoder.resetDefaults();
|
gocaDecoder.resetDefaults();
|
||||||
|
qreplyPending = true;
|
||||||
sendAllQueryReplies();
|
sendAllQueryReplies();
|
||||||
break;
|
break;
|
||||||
case SF_RP_QLIST:
|
case SF_RP_QLIST:
|
||||||
|
qreplyPending = true;
|
||||||
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));
|
||||||
@@ -1065,14 +1272,16 @@ public class DataStreamProcessor {
|
|||||||
sendAllQueryReplies();
|
sendAllQueryReplies();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case CMD_RMA:
|
||||||
case SNA_CMD_RMA:
|
case SNA_CMD_RMA:
|
||||||
processReadModified(true);
|
processReadModified(AID_QREPLY, true);
|
||||||
break;
|
break;
|
||||||
case SNA_CMD_RB:
|
case SNA_CMD_RB:
|
||||||
processReadBuffer();
|
processReadBuffer(AID_QREPLY);
|
||||||
break;
|
break;
|
||||||
|
case CMD_RM:
|
||||||
case SNA_CMD_RM:
|
case SNA_CMD_RM:
|
||||||
processReadModified(false);
|
processReadModified(AID_QREPLY, false);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
log.fine("Unknown ReadPartition type: " + String.format("0x%02x", type));
|
log.fine("Unknown ReadPartition type: " + String.format("0x%02x", type));
|
||||||
@@ -1305,6 +1514,7 @@ public class DataStreamProcessor {
|
|||||||
byte[] psData = new byte[fieldLen - 4];
|
byte[] psData = new byte[fieldLen - 4];
|
||||||
System.arraycopy(data, offset + 4, psData, 0, psData.length);
|
System.arraycopy(data, offset + 4, psData, 0, psData.length);
|
||||||
programSymbolManager.loadps(psData);
|
programSymbolManager.loadps(psData);
|
||||||
|
notifyScreenUpdated();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJDATA_SUB: { // 0x0F: Object Control / Data Unit
|
case haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJDATA_SUB: { // 0x0F: Object Control / Data Unit
|
||||||
@@ -1502,6 +1712,14 @@ public class DataStreamProcessor {
|
|||||||
processReadModified(true);
|
processReadModified(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isQreplyPending() {
|
||||||
|
return qreplyPending;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQreplyPending(boolean qreplyPending) {
|
||||||
|
this.qreplyPending = qreplyPending;
|
||||||
|
}
|
||||||
|
|
||||||
public void processWriteStructuredField(byte[] data) {
|
public void processWriteStructuredField(byte[] data) {
|
||||||
if (data != null && data.length > 0) {
|
if (data != null && data.length > 0) {
|
||||||
processWriteStructuredField(data, 0, data.length);
|
processWriteStructuredField(data, 0, data.length);
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ public class QueryReplyBuilder {
|
|||||||
QR_DDM, // 0x95 - Distributed Data Management (file transfer)
|
QR_DDM, // 0x95 - Distributed Data Management (file transfer)
|
||||||
QR_AUXDA, // 0x99 - Auxiliary Devices
|
QR_AUXDA, // 0x99 - Auxiliary Devices
|
||||||
QR_IMP_PART, // 0xa6 - Implicit Partition Sizes
|
QR_IMP_PART, // 0xa6 - Implicit Partition Sizes
|
||||||
|
QR_TRANSPARENCY, // 0xa8 - Background Transparency
|
||||||
};
|
};
|
||||||
|
|
||||||
// Vector graphics query reply codes matching HOD QueryReply3270Constants.java QR_3270_WITHOUT_DCBS_SUMMARY_STRING
|
// Vector graphics query reply codes matching HOD QueryReply3270Constants.java QR_3270_WITHOUT_DCBS_SUMMARY_STRING
|
||||||
@@ -218,6 +219,9 @@ public class QueryReplyBuilder {
|
|||||||
// Implicit Partition (0xA6)
|
// Implicit Partition (0xA6)
|
||||||
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
||||||
|
|
||||||
|
// Background Transparency (0xA8)
|
||||||
|
appendQueryReply(out, QR_TRANSPARENCY, STATIC_QR_TRANSPARENCY);
|
||||||
|
|
||||||
log.info("Built " + out.size() + " bytes of base query replies (graphicsMode=" + graphicsMode + ")");
|
log.info("Built " + out.size() + " bytes of base query replies (graphicsMode=" + graphicsMode + ")");
|
||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
@@ -245,9 +249,9 @@ public class QueryReplyBuilder {
|
|||||||
appendQueryReply(out, QR_DDM, buildDdm(4096));
|
appendQueryReply(out, QR_DDM, buildDdm(4096));
|
||||||
appendQueryReply(out, QR_AUXDA, STATIC_QR_AUXDA);
|
appendQueryReply(out, QR_AUXDA, STATIC_QR_AUXDA);
|
||||||
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
||||||
|
appendQueryReply(out, QR_TRANSPARENCY, STATIC_QR_TRANSPARENCY); // 0xA8
|
||||||
|
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
if (graphicsMode.isVectorGraphicsEnabled()) {
|
||||||
appendQueryReply(out, QR_TRANSPARENCY, STATIC_QR_TRANSPARENCY); // 0xA8
|
|
||||||
appendQueryReply(out, QR_SEGMENT, STATIC_QR_SEGMENT); // 0xB0
|
appendQueryReply(out, QR_SEGMENT, STATIC_QR_SEGMENT); // 0xB0
|
||||||
appendQueryReply(out, QR_PROCEDURE, STATIC_QR_PROCEDURE); // 0xB1
|
appendQueryReply(out, QR_PROCEDURE, STATIC_QR_PROCEDURE); // 0xB1
|
||||||
appendQueryReply(out, QR_LINETYPE, STATIC_QR_LINETYPE); // 0xB2
|
appendQueryReply(out, QR_LINETYPE, STATIC_QR_LINETYPE); // 0xB2
|
||||||
@@ -317,11 +321,7 @@ public class QueryReplyBuilder {
|
|||||||
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
||||||
break;
|
break;
|
||||||
case QR_TRANSPARENCY: // 0xA8
|
case QR_TRANSPARENCY: // 0xA8
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
|
||||||
appendQueryReply(out, QR_TRANSPARENCY, STATIC_QR_TRANSPARENCY);
|
appendQueryReply(out, QR_TRANSPARENCY, STATIC_QR_TRANSPARENCY);
|
||||||
} else {
|
|
||||||
appendQueryReply(out, QR_NULL, new byte[0]);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case QR_SEGMENT: // 0xB0
|
case QR_SEGMENT: // 0xB0
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
if (graphicsMode.isVectorGraphicsEnabled()) {
|
||||||
@@ -390,10 +390,17 @@ public class QueryReplyBuilder {
|
|||||||
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());
|
boolean isDbcs = (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS());
|
||||||
|
boolean wroteDbcs = false;
|
||||||
for (int code : codes) {
|
for (int code : codes) {
|
||||||
|
if (isDbcs && !wroteDbcs && code == QR_DDM) {
|
||||||
|
out.write(QR_OUTLINING);
|
||||||
|
out.write(QR_DBCS_ASIA);
|
||||||
|
wroteDbcs = true;
|
||||||
|
}
|
||||||
out.write(code);
|
out.write(code);
|
||||||
if (isDbcs && code == QR_OUTLINING) {
|
if (isDbcs && code == QR_OUTLINING && !wroteDbcs) {
|
||||||
out.write(QR_DBCS_ASIA); // 0x91
|
out.write(QR_DBCS_ASIA); // 0x91
|
||||||
|
wroteDbcs = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
@@ -441,7 +448,7 @@ public class QueryReplyBuilder {
|
|||||||
|
|
||||||
public int getCharWidth(int rows) {
|
public int getCharWidth(int rows) {
|
||||||
if (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS()) {
|
if (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS()) {
|
||||||
return 12;
|
return 12; // 0x0C
|
||||||
}
|
}
|
||||||
return SW_3279_2; // 9
|
return SW_3279_2; // 9
|
||||||
}
|
}
|
||||||
@@ -452,6 +459,9 @@ public class QueryReplyBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getCharHeight(int rows) {
|
public int getCharHeight(int rows) {
|
||||||
|
if (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS()) {
|
||||||
|
return 24; // 0x18
|
||||||
|
}
|
||||||
return graphicsMode.isVectorGraphicsEnabled() ? 0x10 : SH_3279_2;
|
return graphicsMode.isVectorGraphicsEnabled() ? 0x10 : SH_3279_2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -477,13 +487,58 @@ public class QueryReplyBuilder {
|
|||||||
|
|
||||||
int cgcsgid = 0x02B9;
|
int cgcsgid = 0x02B9;
|
||||||
int cpgid = 0x0025;
|
int cpgid = 0x0025;
|
||||||
|
boolean isDbcs = false;
|
||||||
if (screen != null && screen.getTranslator() != null) {
|
if (screen != null && screen.getTranslator() != null) {
|
||||||
cgcsgid = screen.getTranslator().getCgcsgid();
|
cgcsgid = screen.getTranslator().getCgcsgid();
|
||||||
cpgid = screen.getTranslator().getCpgid();
|
cpgid = screen.getTranslator().getCpgid();
|
||||||
|
isDbcs = screen.getTranslator().isDBCS();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (graphicsMode == GraphicsMode.PROGRAMMED_SYMBOLS) {
|
if (isDbcs) {
|
||||||
// Programmed Symbols only mode (3279 PS with LoadPS 0x0A, flags1 = 0xA2 for GE + PS + CGCSGID)
|
int dbCgcsgid = screen.getTranslator().getDbCgcsgid();
|
||||||
|
int dbCpgid = screen.getTranslator().getDbCpgid();
|
||||||
|
byte secStart = screen.getTranslator().getDbcsSectionStart();
|
||||||
|
byte secEnd = screen.getTranslator().getDbcsSectionEnd();
|
||||||
|
|
||||||
|
// 42-byte DBCS Character Sets QR payload (total 46 bytes structured field, matching HoD DS3270.java xxxqrcs)
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream(42);
|
||||||
|
out.write(0x8E); // flags: GE | DBCS / 2-byte charsets (0x8E)
|
||||||
|
out.write(0x00); // flags 2
|
||||||
|
out.write(charW); // SDW (12)
|
||||||
|
out.write(charH); // SDH (24)
|
||||||
|
out.write(0x00); out.write(0x00); out.write(0x00); out.write(0x00); // 4 reserved bytes
|
||||||
|
out.write(0x0B); // DL = 11 bytes per descriptor
|
||||||
|
|
||||||
|
// Descriptor 1: Base SBCS (11 bytes)
|
||||||
|
out.write(0x00); out.write(0x00); out.write(0x00);
|
||||||
|
out.write(0x00); out.write(0x00);
|
||||||
|
out.write(0x00); out.write(0x00);
|
||||||
|
out.write((cgcsgid >> 8) & 0xFF); out.write(cgcsgid & 0xFF);
|
||||||
|
out.write((cpgid >> 8) & 0xFF); out.write(cpgid & 0xFF);
|
||||||
|
|
||||||
|
// Descriptor 2: DBCS Set 1 (11 bytes)
|
||||||
|
out.write(0x01); // Set ID = 1
|
||||||
|
out.write(0x00); // Flags
|
||||||
|
out.write(0x80); // Local ID = 0x80 (DBCS)
|
||||||
|
out.write((charW * 2) & 0xFF); // Double cell width (24)
|
||||||
|
out.write(charH & 0xFF); // Cell height (24)
|
||||||
|
out.write(secStart); // Section start (e.g. 0x41)
|
||||||
|
out.write(secEnd); // Section end (e.g. 0xFE or 0x7F)
|
||||||
|
out.write((dbCgcsgid >> 8) & 0xFF); out.write(dbCgcsgid & 0xFF);
|
||||||
|
out.write((dbCpgid >> 8) & 0xFF); out.write(dbCpgid & 0xFF);
|
||||||
|
|
||||||
|
// Descriptor 3: APL / Text (11 bytes)
|
||||||
|
out.write(0x01); out.write(0x00); out.write(0xF1);
|
||||||
|
out.write(0x00); out.write(0x00);
|
||||||
|
out.write(0x00); out.write(0x00);
|
||||||
|
out.write(0x03); out.write(0xC3); // CGCSGID 963
|
||||||
|
out.write(0x01); out.write(0x36); // CPGID 310
|
||||||
|
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (graphicsMode.isProgrammedSymbolsEnabled()) {
|
||||||
|
// Programmed Symbols mode (3279 PS / 3179G 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
|
||||||
@@ -531,6 +586,7 @@ public class QueryReplyBuilder {
|
|||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public byte[] buildColor() {
|
public byte[] buildColor() {
|
||||||
return STATIC_QR_COLOR.clone();
|
return STATIC_QR_COLOR.clone();
|
||||||
}
|
}
|
||||||
@@ -681,16 +737,18 @@ public class QueryReplyBuilder {
|
|||||||
public byte[] buildGrSymbolSet() {
|
public byte[] buildGrSymbolSet() {
|
||||||
int charW = getCharWidth();
|
int charW = getCharWidth();
|
||||||
int charH = getCharHeight();
|
int charH = getCharHeight();
|
||||||
int cgcsgid = 0x02B9;
|
if (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS()) {
|
||||||
int cpgid = 0x0025;
|
return new byte[]{
|
||||||
if (screen != null && screen.getTranslator() != null) {
|
0x00, 0x00, (byte) charW, (byte) charH, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x12,
|
||||||
cgcsgid = screen.getTranslator().getCgcsgid();
|
0x01, 0x00, 0x00, (byte) 0xF0, (byte) 0xC1, (byte) 0xC1, 0x00, 0x00, (byte) charW, (byte) charH,
|
||||||
cpgid = screen.getTranslator().getCpgid();
|
0x00, 0x01, 0x4C, 0x01, 0x22, 0x00, 0x00, 0x12, 0x01, 0x00, 0x20, (byte) 0xF8, (byte) 0xC1, (byte) 0xC1,
|
||||||
|
0x00, 0x00, 0x18, 0x18, 0x00, 0x01, 0x72, 0x01, 0x2C, 0x00, (byte) 0xFF
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return new byte[]{
|
return new byte[]{
|
||||||
0x00, 0x00, (byte) charW, (byte) charH, 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) ((cgcsgid >> 8) & 0xFF), (byte) (cgcsgid & 0xFF), 0x00, 0x00,
|
0x01, 0x00, 0x00, (byte) 0xF0, (byte) 0xC1, (byte) 0xC1, 0x00, 0x00,
|
||||||
(byte) charW, (byte) charH, 0x00, 0x00, (byte) 0xF0, (byte) ((cgcsgid >> 8) & 0xFF), (byte) (cgcsgid & 0xFF), 0x00, 0x00
|
(byte) charW, (byte) charH, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,34 @@ public class ECLConnection {
|
|||||||
if (props != null) {
|
if (props != null) {
|
||||||
this.properties.putAll(props);
|
this.properties.putAll(props);
|
||||||
convertData(this.properties);
|
convertData(this.properties);
|
||||||
|
|
||||||
|
String h = this.properties.getProperty(ECLSession.SESSION_HOST);
|
||||||
|
if (h == null) h = this.properties.getProperty("host");
|
||||||
|
if (h == null) h = this.properties.getProperty("Host");
|
||||||
|
if (h == null) h = this.properties.getProperty("SESSION_PRINT_SESSION_HOST");
|
||||||
|
if (h != null && !h.trim().isEmpty()) this.host = h.trim();
|
||||||
|
|
||||||
|
String p = this.properties.getProperty(ECLSession.SESSION_PORT);
|
||||||
|
if (p == null) p = this.properties.getProperty("port");
|
||||||
|
if (p == null) p = this.properties.getProperty("Port");
|
||||||
|
if (p == null) p = this.properties.getProperty("SESSION_HOST_PORT");
|
||||||
|
if (p == null) p = this.properties.getProperty("SESSION_PRINT_SESSION_HOST_PORT");
|
||||||
|
if (p != null && !p.trim().isEmpty()) {
|
||||||
|
try { this.port = Integer.parseInt(p.trim()); } catch (NumberFormatException ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
String cp = this.properties.getProperty(ECLSession.SESSION_CODE_PAGE);
|
||||||
|
if (cp == null) cp = this.properties.getProperty("code_page");
|
||||||
|
if (cp == null) cp = this.properties.getProperty("codepage");
|
||||||
|
if (cp == null) cp = this.properties.getProperty("CodePage");
|
||||||
|
if (cp != null && !cp.trim().isEmpty()) this.codePage = cp.trim();
|
||||||
|
|
||||||
|
String lu = this.properties.getProperty(ECLSession.SESSION_LU_NAME);
|
||||||
|
if (lu == null) lu = this.properties.getProperty("lu_name");
|
||||||
|
if (lu == null) lu = this.properties.getProperty("luname");
|
||||||
|
if (lu == null) lu = this.properties.getProperty("SESSION_PRINT_SESSION_LU_NAME");
|
||||||
|
if (lu != null && !lu.trim().isEmpty()) this.luName = lu.trim();
|
||||||
|
|
||||||
String asu = this.properties.getProperty(ECLSession.SESSION_AUTO_SYS_UNLOCK);
|
String asu = this.properties.getProperty(ECLSession.SESSION_AUTO_SYS_UNLOCK);
|
||||||
if (asu != null) {
|
if (asu != null) {
|
||||||
this.autoSysUnlock = "true".equalsIgnoreCase(asu) || "1".equals(asu);
|
this.autoSysUnlock = "true".equalsIgnoreCase(asu) || "1".equals(asu);
|
||||||
|
|||||||
@@ -5,13 +5,27 @@ package haus.nightmare.lib3270j.ecl;
|
|||||||
*/
|
*/
|
||||||
public interface ECLConstants {
|
public interface ECLConstants {
|
||||||
|
|
||||||
// Presentation space logical planes
|
// Presentation space logical planes (Bitmask & ordinal definitions matching IBM Host On-Demand)
|
||||||
int PLANE_TEXT = 1; // Unicode/EBCDIC characters
|
int PLANE_TEXT = 1; // Unicode/EBCDIC characters
|
||||||
int PLANE_COLOR = 2; // Color attributes
|
int PLANE_COLOR = 2; // Color attributes
|
||||||
int PLANE_HILITE = 3; // Extended highlighting
|
int PLANE_HILITE = 3; // Extended highlighting
|
||||||
int PLANE_EXTENDED = 4; // Extended character sets / outlining
|
int PLANE_EXTENDED = 4; // Extended character sets / outlining
|
||||||
int PLANE_FIELD = 5; // Field attribute definition bytes
|
int PLANE_FIELD = 5; // Field attribute definition bytes
|
||||||
int PLANE_DBCS = 6; // Double-byte character plane
|
int PLANE_DBCS = 6; // Double-byte character plane (lib3270j convention)
|
||||||
|
int PLANE_GRID = 8; // Grid lines / outlining plane
|
||||||
|
|
||||||
|
// Canonical IBM Host On-Demand ECLConstants plane bitmasks
|
||||||
|
int TEXT_PLANE = 1;
|
||||||
|
int COLOR_PLANE = 2;
|
||||||
|
int DBCS_PLANE = 4;
|
||||||
|
int GRID_PLANE = 8;
|
||||||
|
int FIELD_PLANE = 16;
|
||||||
|
int EXFIELD_PLANE = 32;
|
||||||
|
int NLS_PLANE = 64;
|
||||||
|
int HOST_PLANE = 128;
|
||||||
|
int RAWTEXT_PLANE = 256;
|
||||||
|
int COLOR_ATTRIBUTES_PLANE = 512;
|
||||||
|
int ALL_PLANES = 319;
|
||||||
|
|
||||||
// Color definitions (IBM 3279 standard 16-color palette)
|
// Color definitions (IBM 3279 standard 16-color palette)
|
||||||
char COLOR_NEUTRAL_BLACK = 0;
|
char COLOR_NEUTRAL_BLACK = 0;
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ public class ECLErr extends Exception {
|
|||||||
private String id = "";
|
private String id = "";
|
||||||
private String text = "";
|
private String text = "";
|
||||||
|
|
||||||
|
private int reasonCode = 0;
|
||||||
|
|
||||||
public ECLErr() {
|
public ECLErr() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
@@ -20,6 +22,21 @@ public class ECLErr extends Exception {
|
|||||||
this.text = text;
|
this.text = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ECLErr(String location, String msgNum, int reasonCode) {
|
||||||
|
super(formatMsg(location, msgNum, "Reason " + reasonCode));
|
||||||
|
this.tag = location;
|
||||||
|
this.id = msgNum;
|
||||||
|
this.text = "Reason " + reasonCode;
|
||||||
|
this.reasonCode = reasonCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLErr(String location, String msgNum) {
|
||||||
|
super(formatMsg(location, msgNum, ""));
|
||||||
|
this.tag = location;
|
||||||
|
this.id = msgNum;
|
||||||
|
this.text = "";
|
||||||
|
}
|
||||||
|
|
||||||
public ECLErr(String tag, String id, String text) {
|
public ECLErr(String tag, String id, String text) {
|
||||||
super(formatMsg(tag, id, text));
|
super(formatMsg(tag, id, text));
|
||||||
this.tag = tag;
|
this.tag = tag;
|
||||||
@@ -27,11 +44,30 @@ public class ECLErr extends Exception {
|
|||||||
this.text = text;
|
this.text = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ECLErr(String tag, String id, String text, String extra) {
|
public ECLErr(String location, String msgNum, String insert1, int reasonCode) {
|
||||||
super(formatMsg(tag, id, text + " " + extra));
|
super(formatMsg(location, msgNum, insert1 + " (Reason " + reasonCode + ")"));
|
||||||
this.tag = tag;
|
this.tag = location;
|
||||||
this.id = id;
|
this.id = msgNum;
|
||||||
this.text = text + " " + extra;
|
this.text = insert1 + " (Reason " + reasonCode + ")";
|
||||||
|
this.reasonCode = reasonCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLErr(String location, String msgNum, String insert1, String insert2, boolean log) {
|
||||||
|
super(formatMsg(location, msgNum, insert1 + " " + insert2));
|
||||||
|
this.tag = location;
|
||||||
|
this.id = msgNum;
|
||||||
|
this.text = insert1 + " " + insert2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLErr(String location, String msgNum, String insert1, String insert2) {
|
||||||
|
this(location, msgNum, insert1, insert2, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLErr(String location, String msgNum, String insert1, String insert2, String insert3) {
|
||||||
|
super(formatMsg(location, msgNum, insert1 + " " + insert2 + " " + insert3));
|
||||||
|
this.tag = location;
|
||||||
|
this.id = msgNum;
|
||||||
|
this.text = insert1 + " " + insert2 + " " + insert3;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ECLErr(Throwable cause) {
|
public ECLErr(Throwable cause) {
|
||||||
@@ -57,14 +93,33 @@ public class ECLErr extends Exception {
|
|||||||
public String getID() { return id; }
|
public String getID() { return id; }
|
||||||
public String GetID() { return id; }
|
public String GetID() { return id; }
|
||||||
|
|
||||||
|
public String getLocation() { return tag; }
|
||||||
|
public String GetLocation() { return tag; }
|
||||||
|
|
||||||
|
public int getReasonCode() { return reasonCode; }
|
||||||
|
public int GetReasonCode() { return reasonCode; }
|
||||||
|
|
||||||
|
public int getMsgNumber() {
|
||||||
|
if (id != null && id.length() >= 7 && id.startsWith("ECL")) {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(id.substring(3, 7));
|
||||||
|
} catch (NumberFormatException ignored) {}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
public int GetMsgNumber() { return getMsgNumber(); }
|
||||||
|
|
||||||
public String getText() { return text; }
|
public String getText() { return text; }
|
||||||
public String GetText() { return text; }
|
public String GetText() { return text; }
|
||||||
|
|
||||||
|
public String getMsgText() { return getMessage(); }
|
||||||
|
public String GetMsgText() { return getMessage(); }
|
||||||
|
|
||||||
public String getErrorText() { return getMessage(); }
|
public String getErrorText() { return getMessage(); }
|
||||||
public String GetErrorText() { return getMessage(); }
|
public String GetErrorText() { return getMessage(); }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "ECLErr[tag=" + tag + ", id=" + id + ", text=" + text + "]";
|
return "ECLErr[tag=" + tag + ", id=" + id + ", text=" + text + ", reasonCode=" + reasonCode + "]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ public class ECLField {
|
|||||||
this.attribute = attribute;
|
this.attribute = attribute;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ECLPS getPS() { return ps; }
|
||||||
|
public ECLPS GetPS() { return ps; }
|
||||||
|
public byte getAttributeByte() { return attribute; }
|
||||||
|
|
||||||
/** Buffer address of the field attribute character. */
|
/** Buffer address of the field attribute character. */
|
||||||
public int getStart() { return startPos; }
|
public int getStart() { return startPos; }
|
||||||
public int GetStart() { return getStart(); }
|
public int GetStart() { return getStart(); }
|
||||||
@@ -154,6 +158,8 @@ public class ECLField {
|
|||||||
return ps.getString(dataStart, length);
|
return ps.getString(dataStart, length);
|
||||||
}
|
}
|
||||||
public String GetText() { return getText(); }
|
public String GetText() { return getText(); }
|
||||||
|
public String getString() { return getText(); }
|
||||||
|
public String GetString() { return getText(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the text contents of this field.
|
* Set the text contents of this field.
|
||||||
@@ -162,7 +168,13 @@ public class ECLField {
|
|||||||
if (isProtected() || length <= 0) return;
|
if (isProtected() || length <= 0) return;
|
||||||
ps.setText(text, dataStart);
|
ps.setText(text, dataStart);
|
||||||
}
|
}
|
||||||
public void SetText(String text) { setText(text); }
|
public void SetText(String text) throws ECLErr {
|
||||||
|
if (isProtected()) {
|
||||||
|
throw new ECLErr("ECLField:SetText", "ECL0015", "Field is protected");
|
||||||
|
}
|
||||||
|
setText(text);
|
||||||
|
}
|
||||||
|
public void SetString(String text) throws ECLErr { SetText(text); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get selector light pen type.
|
* Get selector light pen type.
|
||||||
@@ -283,6 +295,54 @@ public class ECLField {
|
|||||||
}
|
}
|
||||||
public void Erase() { erase(); }
|
public void Erase() { erase(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether this field contains any DBCS characters or SO/SI delimiters.
|
||||||
|
*/
|
||||||
|
public boolean isDBCS() {
|
||||||
|
if (ps == null || length <= 0) return false;
|
||||||
|
ScreenBuffer sb = ps.getScreenBuffer();
|
||||||
|
if (sb == null) return false;
|
||||||
|
int size = sb.getRows() * sb.getCols();
|
||||||
|
if (size <= 0) return false;
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
int addr = (dataStart + i) % size;
|
||||||
|
ExtendedAttribute ea = sb.getCell(addr);
|
||||||
|
if (ea != null && (ea.db == ExtendedAttribute.DB_LEFT || ea.db == ExtendedAttribute.DB_RIGHT
|
||||||
|
|| ea.db == ExtendedAttribute.DB_SO || ea.db == ExtendedAttribute.DB_SI)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public boolean IsDBCS() { return isDBCS(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether this field contains only DBCS characters (excluding nulls / spaces).
|
||||||
|
*/
|
||||||
|
public boolean isDBCSOnly() {
|
||||||
|
if (ps == null || length <= 0) return false;
|
||||||
|
ScreenBuffer sb = ps.getScreenBuffer();
|
||||||
|
if (sb == null) return false;
|
||||||
|
int size = sb.getRows() * sb.getCols();
|
||||||
|
if (size <= 0) return false;
|
||||||
|
boolean hasDBCS = false;
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
int addr = (dataStart + i) % size;
|
||||||
|
ExtendedAttribute ea = sb.getCell(addr);
|
||||||
|
if (ea != null) {
|
||||||
|
if (ea.db == ExtendedAttribute.DB_LEFT || ea.db == ExtendedAttribute.DB_RIGHT
|
||||||
|
|| ea.db == ExtendedAttribute.DB_SO || ea.db == ExtendedAttribute.DB_SI) {
|
||||||
|
hasDBCS = true;
|
||||||
|
} else if (ea.ec != 0 && ea.ec != 0x40 && ea.ucs4 != 0 && ea.ucs4 != ' ') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hasDBCS;
|
||||||
|
}
|
||||||
|
public boolean IsDBCSOnly() { return isDBCSOnly(); }
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|||||||
@@ -7,15 +7,17 @@ import java.util.List;
|
|||||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||||
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
|
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
|
||||||
|
|
||||||
|
import java.util.Vector;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages the collection of all 3270 fields currently present in the Presentation Space.
|
* Manages the collection of all 3270 fields currently present in the Presentation Space.
|
||||||
* Conforms to IBM ECL ECLFieldList specification.
|
* Conforms to IBM ECL ECLFieldList specification.
|
||||||
*/
|
*/
|
||||||
public class ECLFieldList {
|
public class ECLFieldList extends Vector<ECLField> {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private final ECLPS ps;
|
private final ECLPS ps;
|
||||||
private final ScreenBuffer screen;
|
private final ScreenBuffer screen;
|
||||||
private final List<ECLField> fields = new ArrayList<>();
|
|
||||||
|
|
||||||
public ECLFieldList(ECLPS ps, ScreenBuffer screen) {
|
public ECLFieldList(ECLPS ps, ScreenBuffer screen) {
|
||||||
this.ps = ps;
|
this.ps = ps;
|
||||||
@@ -23,11 +25,27 @@ public class ECLFieldList {
|
|||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ECLFieldList(ECLPS ps) {
|
||||||
|
this(ps, ps != null ? ps.getScreenBuffer() : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLField getField(int index) {
|
||||||
|
return (index >= 0 && index < size()) ? get(index) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLField GetField(int index) {
|
||||||
|
return getField(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLPS getPS() { return ps; }
|
||||||
|
public ScreenBuffer getScreenBuffer() { return screen; }
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refresh the field list by scanning the presentation space buffer.
|
* Refresh the field list by scanning the presentation space buffer.
|
||||||
*/
|
*/
|
||||||
public synchronized void refresh() {
|
public synchronized void refresh() {
|
||||||
fields.clear();
|
clear();
|
||||||
if (screen == null || !screen.isFormatted()) {
|
if (screen == null || !screen.isFormatted()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -66,35 +84,35 @@ public class ECLFieldList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
byte faVal = screen.getCell(startPos).fa;
|
byte faVal = screen.getCell(startPos).fa;
|
||||||
fields.add(new ECLField(ps, startPos, dataStart, endPos, len, faVal));
|
add(new ECLField(ps, startPos, dataStart, endPos, len, faVal));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Refresh() { refresh(); }
|
public void Refresh() { refresh(); }
|
||||||
|
|
||||||
public synchronized int getFieldCount() {
|
public synchronized int getFieldCount() {
|
||||||
return fields.size();
|
return size();
|
||||||
}
|
}
|
||||||
public int GetFieldCount() { return getFieldCount(); }
|
public int GetFieldCount() { return getFieldCount(); }
|
||||||
|
|
||||||
public synchronized List<ECLField> getFields() {
|
public synchronized List<ECLField> getFields() {
|
||||||
return Collections.unmodifiableList(new ArrayList<>(fields));
|
return Collections.unmodifiableList(new ArrayList<>(this));
|
||||||
}
|
}
|
||||||
public List<ECLField> GetFields() { return getFields(); }
|
public List<ECLField> GetFields() { return getFields(); }
|
||||||
|
|
||||||
public synchronized ECLField getFirstField() {
|
public synchronized ECLField getFirstField() {
|
||||||
if (fields.isEmpty()) return null;
|
if (isEmpty()) return null;
|
||||||
return fields.get(0);
|
return get(0);
|
||||||
}
|
}
|
||||||
public ECLField GetFirstField() { return getFirstField(); }
|
public ECLField GetFirstField() { return getFirstField(); }
|
||||||
|
|
||||||
public synchronized ECLField getNextField(ECLField prev) {
|
public synchronized ECLField getNextField(ECLField prev) {
|
||||||
if (prev == null || fields.isEmpty()) return getFirstField();
|
if (prev == null || isEmpty()) return getFirstField();
|
||||||
for (int i = 0; i < fields.size(); i++) {
|
for (int i = 0; i < size(); i++) {
|
||||||
ECLField f = fields.get(i);
|
ECLField f = get(i);
|
||||||
if (f.equals(prev) || f.getStart() == prev.getStart()) {
|
if (f.equals(prev) || f.getStart() == prev.getStart()) {
|
||||||
if (i + 1 < fields.size()) {
|
if (i + 1 < size()) {
|
||||||
return fields.get(i + 1);
|
return get(i + 1);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -107,12 +125,12 @@ public class ECLFieldList {
|
|||||||
* Find the field that contains the specified buffer position.
|
* Find the field that contains the specified buffer position.
|
||||||
*/
|
*/
|
||||||
public synchronized ECLField findField(int pos) {
|
public synchronized ECLField findField(int pos) {
|
||||||
if (fields.isEmpty() || screen == null) return null;
|
if (isEmpty() || screen == null) return null;
|
||||||
int size = screen.getRows() * screen.getCols();
|
int size = screen.getRows() * screen.getCols();
|
||||||
if (size <= 0) return null;
|
if (size <= 0) return null;
|
||||||
pos = ((pos % size) + size) % size;
|
pos = ((pos % size) + size) % size;
|
||||||
|
|
||||||
for (ECLField f : fields) {
|
for (ECLField f : this) {
|
||||||
int start = f.getStart();
|
int start = f.getStart();
|
||||||
int end = f.getEnd();
|
int end = f.getEnd();
|
||||||
if (start <= end) {
|
if (start <= end) {
|
||||||
@@ -140,14 +158,14 @@ public class ECLFieldList {
|
|||||||
* Get the field preceding the given field in the field list.
|
* Get the field preceding the given field in the field list.
|
||||||
*/
|
*/
|
||||||
public synchronized ECLField getPreviousField(ECLField next) {
|
public synchronized ECLField getPreviousField(ECLField next) {
|
||||||
if (next == null || fields.isEmpty()) return null;
|
if (next == null || isEmpty()) return null;
|
||||||
for (int i = 0; i < fields.size(); i++) {
|
for (int i = 0; i < size(); i++) {
|
||||||
ECLField f = fields.get(i);
|
ECLField f = get(i);
|
||||||
if (f.equals(next) || f.getStart() == next.getStart()) {
|
if (f.equals(next) || f.getStart() == next.getStart()) {
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
return fields.get(i - 1);
|
return get(i - 1);
|
||||||
} else {
|
} else {
|
||||||
return fields.get(fields.size() - 1);
|
return get(size() - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,29 +213,29 @@ public class ECLFieldList {
|
|||||||
* Find field containing the given text string.
|
* Find field containing the given text string.
|
||||||
*/
|
*/
|
||||||
public synchronized ECLField findField(String text, int startPos) {
|
public synchronized ECLField findField(String text, int startPos) {
|
||||||
if (text == null || text.isEmpty() || fields.isEmpty()) return null;
|
if (text == null || text.isEmpty() || isEmpty()) return null;
|
||||||
int size = screen.getRows() * screen.getCols();
|
int size = screen.getRows() * screen.getCols();
|
||||||
if (size <= 0) return null;
|
if (size <= 0) return null;
|
||||||
startPos = ((startPos % size) + size) % size;
|
startPos = ((startPos % size) + size) % size;
|
||||||
|
|
||||||
// Find starting index in field list
|
// Find starting index in field list
|
||||||
int startIdx = 0;
|
int startIdx = 0;
|
||||||
for (int i = 0; i < fields.size(); i++) {
|
for (int i = 0; i < size(); i++) {
|
||||||
if (fields.get(i).getStart() >= startPos) {
|
if (get(i).getStart() >= startPos) {
|
||||||
startIdx = i;
|
startIdx = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = startIdx; i < fields.size(); i++) {
|
for (int i = startIdx; i < size(); i++) {
|
||||||
ECLField f = fields.get(i);
|
ECLField f = get(i);
|
||||||
if (f.getText().contains(text)) {
|
if (f.getText().contains(text)) {
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Wrap around search to beginning of field list
|
// Wrap around search to beginning of field list
|
||||||
for (int i = 0; i < startIdx; i++) {
|
for (int i = 0; i < startIdx; i++) {
|
||||||
ECLField f = fields.get(i);
|
ECLField f = get(i);
|
||||||
if (f.getText().contains(text)) {
|
if (f.getText().contains(text)) {
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
@@ -265,18 +283,18 @@ public class ECLFieldList {
|
|||||||
public boolean MatchAttributes(ECLField f, int mask) { return matchAttributes(f, mask); }
|
public boolean MatchAttributes(ECLField f, int mask) { return matchAttributes(f, mask); }
|
||||||
|
|
||||||
public synchronized ECLField locateField(int attrMask, ECLField prev) {
|
public synchronized ECLField locateField(int attrMask, ECLField prev) {
|
||||||
if (fields.isEmpty()) return null;
|
if (isEmpty()) return null;
|
||||||
int startIndex = 0;
|
int startIndex = 0;
|
||||||
if (prev != null) {
|
if (prev != null) {
|
||||||
int idx = fields.indexOf(prev);
|
int idx = indexOf(prev);
|
||||||
if (idx >= 0 && idx + 1 < fields.size()) {
|
if (idx >= 0 && idx + 1 < size()) {
|
||||||
startIndex = idx + 1;
|
startIndex = idx + 1;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (int i = startIndex; i < fields.size(); i++) {
|
for (int i = startIndex; i < size(); i++) {
|
||||||
ECLField f = fields.get(i);
|
ECLField f = get(i);
|
||||||
if (matchAttributes(f, attrMask)) {
|
if (matchAttributes(f, attrMask)) {
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package haus.nightmare.lib3270j.ecl;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.locks.Condition;
|
import java.util.concurrent.locks.Condition;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
@@ -109,6 +110,8 @@ public class ECLOIA implements ECLConstants {
|
|||||||
session != null && session.getClient() != null ? session.getClient().getTelnetFSM() : null);
|
session != null && session.getClient() != null ? session.getClient().getTelnetFSM() : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final List<com.ibm.eNetwork.ECL.ECLOIANotify> hodOiaNotifies = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
public void RegisterOIAEvent(haus.nightmare.lib3270j.ecl.ECLOIANotify listener) {
|
public void RegisterOIAEvent(haus.nightmare.lib3270j.ecl.ECLOIANotify listener) {
|
||||||
if (listener != null && !listeners.contains(listener)) {
|
if (listener != null && !listeners.contains(listener)) {
|
||||||
listeners.add(listener);
|
listeners.add(listener);
|
||||||
@@ -132,6 +135,29 @@ public class ECLOIA implements ECLConstants {
|
|||||||
UnregisterOIAEvent(listener);
|
UnregisterOIAEvent(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void RegisterOIAEvent(com.ibm.eNetwork.ECL.ECLOIANotify notify) {
|
||||||
|
if (notify != null && !hodOiaNotifies.contains(notify)) {
|
||||||
|
hodOiaNotifies.add(notify);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerOIAEvent(com.ibm.eNetwork.ECL.ECLOIANotify notify) {
|
||||||
|
RegisterOIAEvent(notify);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnregisterOIAEvent(com.ibm.eNetwork.ECL.ECLOIANotify notify) {
|
||||||
|
if (notify != null) {
|
||||||
|
hodOiaNotifies.remove(notify);
|
||||||
|
try {
|
||||||
|
notify.NotifyStop(new com.ibm.eNetwork.ECL.ECLOIA(this), STOP_UNREGISTER);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterOIAEvent(com.ibm.eNetwork.ECL.ECLOIANotify notify) {
|
||||||
|
UnregisterOIAEvent(notify);
|
||||||
|
}
|
||||||
|
|
||||||
public void RegisterOIAEvent(ECLOIAListener listener) {
|
public void RegisterOIAEvent(ECLOIAListener listener) {
|
||||||
if (listener != null && !oiaListeners.contains(listener)) {
|
if (listener != null && !oiaListeners.contains(listener)) {
|
||||||
oiaListeners.add(listener);
|
oiaListeners.add(listener);
|
||||||
@@ -166,6 +192,14 @@ public class ECLOIA implements ECLConstants {
|
|||||||
l.OIANotifyError(this, err);
|
l.OIANotifyError(this, err);
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {}
|
||||||
}
|
}
|
||||||
|
for (com.ibm.eNetwork.ECL.ECLOIANotify l : hodOiaNotifies) {
|
||||||
|
try {
|
||||||
|
com.ibm.eNetwork.ECL.ECLErr ferr = (err instanceof com.ibm.eNetwork.ECL.ECLErr)
|
||||||
|
? (com.ibm.eNetwork.ECL.ECLErr) err
|
||||||
|
: new com.ibm.eNetwork.ECL.ECLErr(err.getMessage());
|
||||||
|
l.NotifyError(new com.ibm.eNetwork.ECL.ECLOIA(this), ferr);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyOIAStop(int reason) {
|
public void notifyOIAStop(int reason) {
|
||||||
@@ -179,6 +213,11 @@ public class ECLOIA implements ECLConstants {
|
|||||||
l.OIANotifyStop(this, reason);
|
l.OIANotifyStop(this, reason);
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {}
|
||||||
}
|
}
|
||||||
|
for (com.ibm.eNetwork.ECL.ECLOIANotify l : hodOiaNotifies) {
|
||||||
|
try {
|
||||||
|
l.NotifyStop(new com.ibm.eNetwork.ECL.ECLOIA(this), reason);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ReentrantLock oiaLock = new ReentrantLock();
|
private final ReentrantLock oiaLock = new ReentrantLock();
|
||||||
@@ -210,6 +249,11 @@ public class ECLOIA implements ECLConstants {
|
|||||||
}
|
}
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {}
|
||||||
}
|
}
|
||||||
|
for (com.ibm.eNetwork.ECL.ECLOIANotify l : hodOiaNotifies) {
|
||||||
|
try {
|
||||||
|
l.OIANotifyEvent(new com.ibm.eNetwork.ECL.ECLOIAEvent(event));
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isInsertMode() {
|
public boolean isInsertMode() {
|
||||||
@@ -267,6 +311,11 @@ public class ECLOIA implements ECLConstants {
|
|||||||
public void setInputInhibited(int reason) {
|
public void setInputInhibited(int reason) {
|
||||||
if (this.inhibitOverride != reason) {
|
if (this.inhibitOverride != reason) {
|
||||||
this.inhibitOverride = reason;
|
this.inhibitOverride = reason;
|
||||||
|
if (reason == INHIBIT_NOT_INHIBITED && inputProcessor != null && inputProcessor.isKeyboardLocked()) {
|
||||||
|
inputProcessor.setKeyboardLocked(false);
|
||||||
|
} else if (reason == INHIBIT_SYSTEM_LOCK && inputProcessor != null && !inputProcessor.isKeyboardLocked()) {
|
||||||
|
inputProcessor.setKeyboardLocked(true);
|
||||||
|
}
|
||||||
notifyOIAChanged();
|
notifyOIAChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -424,6 +473,14 @@ public class ECLOIA implements ECLConstants {
|
|||||||
return getInputInhibited();
|
return getInputInhibited();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isKeyLocked() {
|
||||||
|
return getInputInhibited() != INHIBIT_NOT_INHIBITED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean IsKeyLocked() {
|
||||||
|
return isKeyLocked();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Block until the keyboard becomes unlocked and ready for user input, or timeout expires.
|
* Block until the keyboard becomes unlocked and ready for user input, or timeout expires.
|
||||||
* @return true if keyboard unlocked, false if timeout occurred.
|
* @return true if keyboard unlocked, false if timeout occurred.
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import haus.nightmare.lib3270j.input.InputProcessor;
|
|||||||
import haus.nightmare.lib3270j.listener.ScreenUpdateListener;
|
import haus.nightmare.lib3270j.listener.ScreenUpdateListener;
|
||||||
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
|
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
|
||||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||||
|
import haus.nightmare.lib3270j.ecl.util.SendKeyTokenizer;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.locks.Condition;
|
import java.util.concurrent.locks.Condition;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
@@ -155,21 +156,95 @@ public class ECLPS implements ECLConstants {
|
|||||||
public ECLPSTHAIServices GetECLPSTHAIServices() { return thaiServices; }
|
public ECLPSTHAIServices GetECLPSTHAIServices() { return thaiServices; }
|
||||||
public ECLPSTHAIServices getTHAIServices() { return thaiServices; }
|
public ECLPSTHAIServices getTHAIServices() { return thaiServices; }
|
||||||
|
|
||||||
public int getSize() { return screen.getRows() * screen.getCols(); }
|
public int getSize() { return screen != null ? screen.getRows() * screen.getCols() : 0; }
|
||||||
public int getRows() { return screen.getRows(); }
|
public int getRows() { return screen != null ? screen.getRows() : 0; }
|
||||||
public int getCols() { return screen.getCols(); }
|
public int getCols() { return screen != null ? screen.getCols() : 0; }
|
||||||
public int getCursorPos(){ return screen.getCursorAddress(); }
|
public int GetSize() { return getSize(); }
|
||||||
public int getCursorRow(){ return screen.getCursorRow(); }
|
public int GetRows() { return getRows(); }
|
||||||
public int getCursorCol(){ return screen.getCursorCol(); }
|
public int GetCols() { return getCols(); }
|
||||||
|
public int GetSizeRows() { return getRows(); }
|
||||||
|
public int GetSizeCols() { return getCols(); }
|
||||||
|
|
||||||
|
public int getCursorPos(){ return screen != null ? screen.getCursorAddress() : 0; }
|
||||||
|
public int getCursorRow(){ return screen != null ? screen.getCursorRow() : 0; }
|
||||||
|
public int getCursorCol(){ return screen != null ? screen.getCursorCol() : 0; }
|
||||||
|
|
||||||
|
public synchronized int GetCursorPos() {
|
||||||
|
return (screen != null) ? screen.getCursorAddress() + 1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int GetCursorRow() {
|
||||||
|
return (screen != null) ? screen.getCursorRow() + 1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int GetCursorCol() {
|
||||||
|
return (screen != null) ? screen.getCursorCol() + 1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
public void setCursorPos(int pos) {
|
public void setCursorPos(int pos) {
|
||||||
|
if (screen != null) {
|
||||||
screen.setCursorAddress(pos);
|
screen.setCursorAddress(pos);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setCursorPos(int row, int col) {
|
public void setCursorPos(int row, int col) {
|
||||||
|
if (screen != null) {
|
||||||
int pos = screen.rowColToAddress(row, col);
|
int pos = screen.rowColToAddress(row, col);
|
||||||
screen.setCursorAddress(pos);
|
screen.setCursorAddress(pos);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void SetCursorPos(int pos) throws com.ibm.eNetwork.ECL.ECLErr {
|
||||||
|
if (pos < 1 || pos > getSize()) {
|
||||||
|
throw new com.ibm.eNetwork.ECL.ECLErr("ECLPS:SetCursorPos", "ECL0010", "1", String.valueOf(pos));
|
||||||
|
}
|
||||||
|
setCursorPos(pos - 1);
|
||||||
|
notifyCursorMoved(0, pos - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void SetCursorPos(int row, int col) throws com.ibm.eNetwork.ECL.ECLErr {
|
||||||
|
SetCursorPos(ConvertRowColToPos(row, col));
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int ConvertPosToRow(int pos) throws com.ibm.eNetwork.ECL.ECLErr {
|
||||||
|
if (pos < 1 || pos > getSize()) {
|
||||||
|
throw new com.ibm.eNetwork.ECL.ECLErr("ECLPS:ConvertPosToRow", "ECL0010", "1", String.valueOf(pos));
|
||||||
|
}
|
||||||
|
int cols = getCols();
|
||||||
|
return (cols > 0) ? ((pos - 1) / cols + 1) : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int ConvertPosToCol(int pos) throws com.ibm.eNetwork.ECL.ECLErr {
|
||||||
|
if (pos < 1 || pos > getSize()) {
|
||||||
|
throw new com.ibm.eNetwork.ECL.ECLErr("ECLPS:ConvertPosToCol", "ECL0010", "1", String.valueOf(pos));
|
||||||
|
}
|
||||||
|
int cols = getCols();
|
||||||
|
return (cols > 0) ? ((pos - 1) % cols + 1) : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int ConvertRowColToPos(int row, int col) throws com.ibm.eNetwork.ECL.ECLErr {
|
||||||
|
if (row < 1 || row > getRows()) {
|
||||||
|
throw new com.ibm.eNetwork.ECL.ECLErr("ECLPS:ConvertRowColToPos", "ECL0010", "\"row\"", String.valueOf(row));
|
||||||
|
}
|
||||||
|
if (col < 1 || col > getCols()) {
|
||||||
|
throw new com.ibm.eNetwork.ECL.ECLErr("ECLPS:ConvertRowColToPos", "ECL0010", "\"col\"", String.valueOf(col));
|
||||||
|
}
|
||||||
|
return (row - 1) * getCols() + col;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void ConvertPosToRowCol(int pos, int[] rowCol) throws com.ibm.eNetwork.ECL.ECLErr {
|
||||||
|
if (rowCol == null || rowCol.length < 2) {
|
||||||
|
throw new com.ibm.eNetwork.ECL.ECLErr("ECLPS:ConvertPosToRowCol", "ECL0010", "\"rowCol\"", "invalid array");
|
||||||
|
}
|
||||||
|
rowCol[0] = ConvertPosToRow(pos);
|
||||||
|
rowCol[1] = ConvertPosToCol(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int[] ConvertPosToRowCol(int pos) throws com.ibm.eNetwork.ECL.ECLErr {
|
||||||
|
int[] rc = new int[2];
|
||||||
|
ConvertPosToRowCol(pos, rc);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy presentation data from a specific plane into a destination buffer.
|
* Copy presentation data from a specific plane into a destination buffer.
|
||||||
@@ -189,10 +264,12 @@ public class ECLPS implements ECLConstants {
|
|||||||
case PLANE_TEXT:
|
case PLANE_TEXT:
|
||||||
if (ea.isFieldAttribute()) {
|
if (ea.isFieldAttribute()) {
|
||||||
destBuffer[i] = ' ';
|
destBuffer[i] = ' ';
|
||||||
|
} else if (ea.db == ExtendedAttribute.DB_SO || ea.db == ExtendedAttribute.DB_SI) {
|
||||||
|
destBuffer[i] = ' ';
|
||||||
} else if (ea.ucs4 != 0) {
|
} else if (ea.ucs4 != 0) {
|
||||||
destBuffer[i] = (char) ea.ucs4;
|
destBuffer[i] = (char) ea.ucs4;
|
||||||
} else if (ea.ec != 0) {
|
} else if (ea.ec != 0) {
|
||||||
destBuffer[i] = translator.ebcdicToUnicode(ea.ec & 0xFF);
|
destBuffer[i] = (translator != null) ? translator.ebcdicToUnicode(ea.ec & 0xFF) : (char) (ea.ec & 0xFF);
|
||||||
} else {
|
} else {
|
||||||
destBuffer[i] = ' ';
|
destBuffer[i] = ' ';
|
||||||
}
|
}
|
||||||
@@ -203,12 +280,43 @@ public class ECLPS implements ECLConstants {
|
|||||||
case PLANE_HILITE:
|
case PLANE_HILITE:
|
||||||
destBuffer[i] = (char) (ea.gr & 0xFF);
|
destBuffer[i] = (char) (ea.gr & 0xFF);
|
||||||
break;
|
break;
|
||||||
case PLANE_EXTENDED:
|
case DBCS_PLANE: // 4 (HoD DBCS_PLANE) or PLANE_EXTENDED for SBCS
|
||||||
|
if (translator != null && translator.isDBCS()) {
|
||||||
|
if (ea.db == ExtendedAttribute.DB_LEFT) {
|
||||||
|
destBuffer[i] = (char) 0xC0; // DB_DBHIGH (first half of DBCS)
|
||||||
|
} else if (ea.db == ExtendedAttribute.DB_RIGHT) {
|
||||||
|
destBuffer[i] = (char) 0x80; // DB_DBCS (second half of DBCS)
|
||||||
|
} else if (ea.db == ExtendedAttribute.DB_SO) {
|
||||||
|
destBuffer[i] = (char) 0x20; // DB_SO
|
||||||
|
} else if (ea.db == ExtendedAttribute.DB_SI) {
|
||||||
|
destBuffer[i] = (char) 0x10; // DB_SI
|
||||||
|
} else {
|
||||||
|
destBuffer[i] = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
destBuffer[i] = (char) (ea.cs & 0xFF);
|
destBuffer[i] = (char) (ea.cs & 0xFF);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case PLANE_FIELD:
|
case PLANE_FIELD:
|
||||||
|
case FIELD_PLANE: // 16
|
||||||
destBuffer[i] = ea.isFieldAttribute() ? (char) (ea.fa & 0xFF) : 0;
|
destBuffer[i] = ea.isFieldAttribute() ? (char) (ea.fa & 0xFF) : 0;
|
||||||
break;
|
break;
|
||||||
|
case PLANE_DBCS: // 6
|
||||||
|
if (ea.db == ExtendedAttribute.DB_LEFT) {
|
||||||
|
destBuffer[i] = (char) 0xC0;
|
||||||
|
} else if (ea.db == ExtendedAttribute.DB_RIGHT) {
|
||||||
|
destBuffer[i] = (char) 0x80;
|
||||||
|
} else if (ea.db == ExtendedAttribute.DB_SO) {
|
||||||
|
destBuffer[i] = (char) 0x20;
|
||||||
|
} else if (ea.db == ExtendedAttribute.DB_SI) {
|
||||||
|
destBuffer[i] = (char) 0x10;
|
||||||
|
} else {
|
||||||
|
destBuffer[i] = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GRID_PLANE: // 8 (HoD GRID_PLANE / Outlining)
|
||||||
|
destBuffer[i] = (char) (ea.ol & 0xFF);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
destBuffer[i] = ' ';
|
destBuffer[i] = ' ';
|
||||||
break;
|
break;
|
||||||
@@ -217,6 +325,107 @@ public class ECLPS implements ECLConstants {
|
|||||||
return copyLen;
|
return copyLen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized boolean IsDBCSChar(int pos) {
|
||||||
|
if (screen == null) return false;
|
||||||
|
int size = getSize();
|
||||||
|
if (size <= 0) return false;
|
||||||
|
pos = ((pos % size) + size) % size;
|
||||||
|
ExtendedAttribute ea = screen.getCell(pos);
|
||||||
|
return ea != null && (ea.db == ExtendedAttribute.DB_LEFT || ea.db == ExtendedAttribute.DB_RIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean isDBCSChar(int pos) {
|
||||||
|
return IsDBCSChar(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean IsDBCSChar(int row, int col) {
|
||||||
|
if (screen == null) return false;
|
||||||
|
return IsDBCSChar(screen.rowColToAddress(row, col));
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean isDBCSChar(int row, int col) {
|
||||||
|
return IsDBCSChar(row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean IsDBCS1stChar(int pos) {
|
||||||
|
if (screen == null) return false;
|
||||||
|
int size = getSize();
|
||||||
|
if (size <= 0) return false;
|
||||||
|
pos = ((pos % size) + size) % size;
|
||||||
|
ExtendedAttribute ea = screen.getCell(pos);
|
||||||
|
return ea != null && ea.db == ExtendedAttribute.DB_LEFT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean isDBCS1stChar(int pos) {
|
||||||
|
return IsDBCS1stChar(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean IsDBCS1stChar(int row, int col) {
|
||||||
|
if (screen == null) return false;
|
||||||
|
return IsDBCS1stChar(screen.rowColToAddress(row, col));
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean isDBCS1stChar(int row, int col) {
|
||||||
|
return IsDBCS1stChar(row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean IsDBCS2ndChar(int pos) {
|
||||||
|
if (screen == null) return false;
|
||||||
|
int size = getSize();
|
||||||
|
if (size <= 0) return false;
|
||||||
|
pos = ((pos % size) + size) % size;
|
||||||
|
ExtendedAttribute ea = screen.getCell(pos);
|
||||||
|
return ea != null && ea.db == ExtendedAttribute.DB_RIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean isDBCS2ndChar(int pos) {
|
||||||
|
return IsDBCS2ndChar(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean IsDBCS2ndChar(int row, int col) {
|
||||||
|
if (screen == null) return false;
|
||||||
|
return IsDBCS2ndChar(screen.rowColToAddress(row, col));
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean isDBCS2ndChar(int row, int col) {
|
||||||
|
return IsDBCS2ndChar(row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean IsSOChar(int pos) {
|
||||||
|
if (screen == null) return false;
|
||||||
|
int size = getSize();
|
||||||
|
if (size <= 0) return false;
|
||||||
|
pos = ((pos % size) + size) % size;
|
||||||
|
ExtendedAttribute ea = screen.getCell(pos);
|
||||||
|
return ea != null && (ea.db == ExtendedAttribute.DB_SO || (ea.ec & 0xFF) == 0x0E);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean isSOChar(int pos) {
|
||||||
|
return IsSOChar(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean IsSIChar(int pos) {
|
||||||
|
if (screen == null) return false;
|
||||||
|
int size = getSize();
|
||||||
|
if (size <= 0) return false;
|
||||||
|
pos = ((pos % size) + size) % size;
|
||||||
|
ExtendedAttribute ea = screen.getCell(pos);
|
||||||
|
return ea != null && (ea.db == ExtendedAttribute.DB_SI || (ea.ec & 0xFF) == 0x0F);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean isSIChar(int pos) {
|
||||||
|
return IsSIChar(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean IsSOSIChar(int pos) {
|
||||||
|
return IsSOChar(pos) || IsSIChar(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean isSOSIChar(int pos) {
|
||||||
|
return IsSOSIChar(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public synchronized int GetScreenRect(char[] cArray, int len, int sRow, int sCol, int eRow, int eCol, int plane) {
|
public synchronized int GetScreenRect(char[] cArray, int len, int sRow, int sCol, int eRow, int eCol, int plane) {
|
||||||
if (cArray == null || len <= 0 || screen == null) return 0;
|
if (cArray == null || len <= 0 || screen == null) return 0;
|
||||||
int minR = Math.max(1, Math.min(sRow, eRow));
|
int minR = Math.max(1, Math.min(sRow, eRow));
|
||||||
@@ -277,6 +486,60 @@ public class ECLPS implements ECLConstants {
|
|||||||
return getString(pos, length);
|
return getString(pos, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized int GetString(char[] buffer, int len, int pos) throws com.ibm.eNetwork.ECL.ECLErr {
|
||||||
|
return GetString(buffer, len, pos, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int GetString(char[] buffer, int len, int pos, int length) throws com.ibm.eNetwork.ECL.ECLErr {
|
||||||
|
if (buffer == null) {
|
||||||
|
throw new com.ibm.eNetwork.ECL.ECLErr("ECLPS:GetString", "ECL0011", "1");
|
||||||
|
}
|
||||||
|
int size = getSize();
|
||||||
|
if (pos < 1 || pos > size) {
|
||||||
|
throw new com.ibm.eNetwork.ECL.ECLErr("ECLPS:GetString", "ECL0010", "\"position\"", String.valueOf(pos));
|
||||||
|
}
|
||||||
|
if (len <= 0) {
|
||||||
|
throw new com.ibm.eNetwork.ECL.ECLErr("ECLPS:GetString", "ECL0010", "\"bufferLength\"", String.valueOf(len));
|
||||||
|
}
|
||||||
|
if (length <= 0) {
|
||||||
|
throw new com.ibm.eNetwork.ECL.ECLErr("ECLPS:GetString", "ECL0010", "\"length\"", String.valueOf(length));
|
||||||
|
}
|
||||||
|
int copyCount = Math.min(Math.min(buffer.length, len), Math.min(length, size - pos + 1));
|
||||||
|
char[] temp = new char[copyCount];
|
||||||
|
getPlane(PLANE_TEXT, temp, pos - 1, copyCount);
|
||||||
|
System.arraycopy(temp, 0, buffer, 0, copyCount);
|
||||||
|
return copyCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int GetString(char[] buffer, int len, int row, int col, int length) throws com.ibm.eNetwork.ECL.ECLErr {
|
||||||
|
return GetString(buffer, len, ConvertRowColToPos(row, col), length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized String GetString(int pos, int length) throws com.ibm.eNetwork.ECL.ECLErr {
|
||||||
|
int size = getSize();
|
||||||
|
if (pos < 1 || pos > size) {
|
||||||
|
throw new com.ibm.eNetwork.ECL.ECLErr("ECLPS:GetString", "ECL0010", "\"pos\"", String.valueOf(pos));
|
||||||
|
}
|
||||||
|
return getString(pos - 1, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized String GetString(int row, int col, int length) throws com.ibm.eNetwork.ECL.ECLErr {
|
||||||
|
return GetString(ConvertRowColToPos(row, col), length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int GetScreen(char[] buffer, int len, int row, int col, int length, int plane) {
|
||||||
|
if (buffer == null || len <= 0 || length <= 0 || screen == null) return 0;
|
||||||
|
int r = (row > 0) ? row - 1 : 0;
|
||||||
|
int c = (col > 0) ? col - 1 : 0;
|
||||||
|
int pos = screen.rowColToAddress(r, c);
|
||||||
|
int copyCount = Math.min(Math.min(buffer.length, len), length);
|
||||||
|
return getPlane(plane, buffer, pos, copyCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getScreen(char[] buffer, int len, int row, int col, int length, int plane) {
|
||||||
|
return GetScreen(buffer, len, row, col, length, plane);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Insert text directly into unprotected fields in the presentation space starting at pos.
|
* Insert text directly into unprotected fields in the presentation space starting at pos.
|
||||||
*/
|
*/
|
||||||
@@ -319,6 +582,34 @@ public class ECLPS implements ECLConstants {
|
|||||||
setText(text, p);
|
setText(text, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert text at current cursor position.
|
||||||
|
*/
|
||||||
|
public void SetText(String text) {
|
||||||
|
SetText(text, GetCursorPos());
|
||||||
|
}
|
||||||
|
public void setText(String text) {
|
||||||
|
SetText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void SetString(String text, int pos) throws com.ibm.eNetwork.ECL.ECLErr {
|
||||||
|
if (text == null) {
|
||||||
|
throw new com.ibm.eNetwork.ECL.ECLErr("ECLPS:SetString", "ECL0010", "\"text\"", "null");
|
||||||
|
}
|
||||||
|
if (pos < 1 || pos > getSize()) {
|
||||||
|
throw new com.ibm.eNetwork.ECL.ECLErr("ECLPS:SetString", "ECL0010", "\"pos\"", String.valueOf(pos));
|
||||||
|
}
|
||||||
|
SetText(text, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void SetString(String text, int row, int col) throws com.ibm.eNetwork.ECL.ECLErr {
|
||||||
|
SetString(text, ConvertRowColToPos(row, col));
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void SetString(String text) throws com.ibm.eNetwork.ECL.ECLErr {
|
||||||
|
SetString(text, GetCursorPos());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search for a string in the presentation space (0-based indexing).
|
* Search for a string in the presentation space (0-based indexing).
|
||||||
* Returns 0-based position, or -1 if not found.
|
* Returns 0-based position, or -1 if not found.
|
||||||
@@ -500,6 +791,86 @@ public class ECLPS implements ECLConstants {
|
|||||||
return SearchPSExt(text, startRow, startCol, endRow, endCol, dir, ignoreCase, wrap);
|
return SearchPSExt(text, startRow, startCol, endRow, endCol, dir, ignoreCase, wrap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== IBM HoD SearchText / SearchString (1-based API) ==========
|
||||||
|
|
||||||
|
public synchronized int SearchText(String text) {
|
||||||
|
return SearchText(text, 1, SEARCH_FORWARD);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int searchText(String text) {
|
||||||
|
return SearchText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int SearchText(String text, int dir) {
|
||||||
|
return dir == SEARCH_BACKWARD ? SearchText(text, getSize(), dir) : SearchText(text, 1, dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int searchText(String text, int dir) {
|
||||||
|
return SearchText(text, dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int SearchText(String text, int arg1, int arg2) {
|
||||||
|
int startPos;
|
||||||
|
int dir;
|
||||||
|
if (arg1 > 2) {
|
||||||
|
startPos = arg1;
|
||||||
|
dir = arg2;
|
||||||
|
} else if (arg2 > 2) {
|
||||||
|
dir = arg1;
|
||||||
|
startPos = arg2;
|
||||||
|
} else {
|
||||||
|
dir = arg1;
|
||||||
|
startPos = arg2;
|
||||||
|
}
|
||||||
|
if (text == null || text.isEmpty() || screen == null) return 0;
|
||||||
|
int size = getSize();
|
||||||
|
if (size <= 0) return 0;
|
||||||
|
if (startPos < 1) startPos = 1;
|
||||||
|
if (startPos > size) startPos = size;
|
||||||
|
|
||||||
|
char[] fullScreen = new char[size];
|
||||||
|
getPlane(PLANE_TEXT, fullScreen, 0, size);
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
if (fullScreen[i] == '\0') fullScreen[i] = ' ';
|
||||||
|
}
|
||||||
|
String screenText = new String(fullScreen);
|
||||||
|
int idx = (dir == SEARCH_BACKWARD) ? screenText.lastIndexOf(text, startPos - 1)
|
||||||
|
: screenText.indexOf(text, startPos - 1);
|
||||||
|
return idx == -1 ? 0 : idx + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int searchText(String text, int arg1, int arg2) {
|
||||||
|
return SearchText(text, arg1, arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int SearchText(String text, int row, int col, int dir) throws com.ibm.eNetwork.ECL.ECLErr {
|
||||||
|
return SearchText(text, ConvertRowColToPos(row, col), dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int searchText(String text, int row, int col, int dir) throws com.ibm.eNetwork.ECL.ECLErr {
|
||||||
|
return SearchText(text, row, col, dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int SearchString(String text) {
|
||||||
|
return SearchText(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int searchString(String text, int dir) {
|
||||||
|
return SearchText(text, dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int SearchString(String text, int dir) {
|
||||||
|
return SearchText(text, dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int SearchString(String text, int arg1, int arg2) {
|
||||||
|
return SearchText(text, arg1, arg2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int SearchString(String text, int row, int col, int dir) throws com.ibm.eNetwork.ECL.ECLErr {
|
||||||
|
return SearchText(text, row, col, dir);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean matchesAt(String screenText, String target, int pos, int size) {
|
private boolean matchesAt(String screenText, String target, int pos, int size) {
|
||||||
int len = target.length();
|
int len = target.length();
|
||||||
for (int j = 0; j < len; j++) {
|
for (int j = 0; j < len; j++) {
|
||||||
@@ -608,13 +979,37 @@ public class ECLPS implements ECLConstants {
|
|||||||
|
|
||||||
public void SetPasteStopAtProtectedLine(boolean val) { setPasteStopAtProtectedLine(val); }
|
public void SetPasteStopAtProtectedLine(boolean val) { setPasteStopAtProtectedLine(val); }
|
||||||
|
|
||||||
|
private boolean pasteWordBreak = true;
|
||||||
|
|
||||||
|
public boolean isPasteWordBreak() {
|
||||||
|
if (session != null && session.getProperties() != null) {
|
||||||
|
String p = session.getProperties().getProperty(ECLSession.PASTE_WORD_BREAK);
|
||||||
|
if (p != null) return Boolean.parseBoolean(p);
|
||||||
|
}
|
||||||
|
return inputProcessor != null ? inputProcessor.isPasteWordBreak() : pasteWordBreak;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean IsPasteWordBreak() { return isPasteWordBreak(); }
|
||||||
|
|
||||||
|
public void setPasteWordBreak(boolean val) {
|
||||||
|
this.pasteWordBreak = val;
|
||||||
|
if (session != null && session.getProperties() != null) {
|
||||||
|
session.getProperties().setProperty(ECLSession.PASTE_WORD_BREAK, String.valueOf(val));
|
||||||
|
}
|
||||||
|
if (inputProcessor != null) {
|
||||||
|
inputProcessor.setPasteWordBreak(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPasteWordBreak(boolean val) { setPasteWordBreak(val); }
|
||||||
|
|
||||||
public synchronized int pasteFromExcel(String text, int row, int col) {
|
public synchronized int pasteFromExcel(String text, int row, int col) {
|
||||||
if (text == null || text.isEmpty() || screen == null) return 0;
|
if (text == null || text.isEmpty() || screen == null) return 0;
|
||||||
if (row >= 0 && col >= 0) {
|
if (row >= 0 && col >= 0) {
|
||||||
setCursorPos(row, col);
|
setCursorPos(row, col);
|
||||||
}
|
}
|
||||||
if (inputProcessor != null) {
|
if (inputProcessor != null) {
|
||||||
return inputProcessor.pasteText(text, true, isPasteStopAtProtectedLine(), isBlockPaste());
|
return inputProcessor.pasteText(text, true, isPasteStopAtProtectedLine(), isBlockPaste(), isPasteWordBreak());
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -630,7 +1025,7 @@ public class ECLPS implements ECLConstants {
|
|||||||
if (text == null || text.isEmpty() || screen == null) return 0;
|
if (text == null || text.isEmpty() || screen == null) return 0;
|
||||||
if (inputProcessor != null && (text.contains("\t") || isEnablePasteFromExcel() || isPasteStopAtProtectedLine() || isBlockPaste())) {
|
if (inputProcessor != null && (text.contains("\t") || isEnablePasteFromExcel() || isPasteStopAtProtectedLine() || isBlockPaste())) {
|
||||||
setCursorPos(row, col);
|
setCursorPos(row, col);
|
||||||
return inputProcessor.pasteText(text, isEnablePasteFromExcel(), isPasteStopAtProtectedLine(), isBlockPaste());
|
return inputProcessor.pasteText(text, isEnablePasteFromExcel(), isPasteStopAtProtectedLine(), isBlockPaste(), isPasteWordBreak());
|
||||||
}
|
}
|
||||||
int rows = screen.getRows();
|
int rows = screen.getRows();
|
||||||
int cols = screen.getCols();
|
int cols = screen.getCols();
|
||||||
@@ -920,6 +1315,10 @@ public class ECLPS implements ECLConstants {
|
|||||||
// ========== ECLPS Event Listener Management ==========
|
// ========== ECLPS Event Listener Management ==========
|
||||||
|
|
||||||
private final java.util.List<ECLPSListener> psListeners = new java.util.concurrent.CopyOnWriteArrayList<>();
|
private final java.util.List<ECLPSListener> psListeners = new java.util.concurrent.CopyOnWriteArrayList<>();
|
||||||
|
private final java.util.List<com.ibm.eNetwork.ECL.ECLPSNotify> psNotifies = new java.util.concurrent.CopyOnWriteArrayList<>();
|
||||||
|
private final java.util.Map<com.ibm.eNetwork.ECL.ECLPSNotify, Integer> notifyEventTypes = new java.util.concurrent.ConcurrentHashMap<>();
|
||||||
|
private final java.util.Map<com.ibm.eNetwork.ECL.ECLPSNotify, Object> notifyDescriptors = new java.util.concurrent.ConcurrentHashMap<>();
|
||||||
|
private final java.util.List<com.ibm.eNetwork.ECL.event.ECLPSListener> hodPsListeners = new java.util.concurrent.CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
public boolean isCursorVisible() { return cursorVisible; }
|
public boolean isCursorVisible() { return cursorVisible; }
|
||||||
public boolean GetCursorVisible() { return cursorVisible; }
|
public boolean GetCursorVisible() { return cursorVisible; }
|
||||||
@@ -1007,6 +1406,111 @@ public class ECLPS implements ECLConstants {
|
|||||||
UnregisterPSEvent(listener);
|
UnregisterPSEvent(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void RegisterPSEvent(com.ibm.eNetwork.ECL.ECLPSNotify notify) {
|
||||||
|
RegisterPSEvent(notify, ALL_EVENTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerPSEvent(com.ibm.eNetwork.ECL.ECLPSNotify notify) {
|
||||||
|
RegisterPSEvent(notify);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterPSEvent(com.ibm.eNetwork.ECL.ECLPSNotify notify, int eventType) {
|
||||||
|
if (notify != null) {
|
||||||
|
notifyEventTypes.put(notify, eventType);
|
||||||
|
if (!psNotifies.contains(notify)) {
|
||||||
|
psNotifies.add(notify);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerPSEvent(com.ibm.eNetwork.ECL.ECLPSNotify notify, int eventType) {
|
||||||
|
RegisterPSEvent(notify, eventType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterPSEvent(com.ibm.eNetwork.ECL.ECLPSNotify notify, ECLScreenDesc desc) {
|
||||||
|
RegisterPSEvent(notify, desc, ALL_EVENTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerPSEvent(com.ibm.eNetwork.ECL.ECLPSNotify notify, ECLScreenDesc desc) {
|
||||||
|
RegisterPSEvent(notify, desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterPSEvent(com.ibm.eNetwork.ECL.ECLPSNotify notify, ECLScreenDesc desc, int eventType) {
|
||||||
|
if (notify != null) {
|
||||||
|
if (desc != null) {
|
||||||
|
notifyDescriptors.put(notify, desc);
|
||||||
|
} else {
|
||||||
|
notifyDescriptors.remove(notify);
|
||||||
|
}
|
||||||
|
notifyEventTypes.put(notify, eventType);
|
||||||
|
if (!psNotifies.contains(notify)) {
|
||||||
|
psNotifies.add(notify);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerPSEvent(com.ibm.eNetwork.ECL.ECLPSNotify notify, ECLScreenDesc desc, int eventType) {
|
||||||
|
RegisterPSEvent(notify, desc, eventType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnregisterPSEvent(com.ibm.eNetwork.ECL.ECLPSNotify notify) {
|
||||||
|
if (notify != null) {
|
||||||
|
psNotifies.remove(notify);
|
||||||
|
notifyDescriptors.remove(notify);
|
||||||
|
notifyEventTypes.remove(notify);
|
||||||
|
try {
|
||||||
|
notify.NotifyStop(new com.ibm.eNetwork.ECL.ECLPS(this), STOP_UNREGISTER);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterPSEvent(com.ibm.eNetwork.ECL.ECLPSNotify notify) {
|
||||||
|
UnregisterPSEvent(notify);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterPSEvent(com.ibm.eNetwork.ECL.event.ECLPSListener listener) {
|
||||||
|
if (listener != null && !hodPsListeners.contains(listener)) {
|
||||||
|
hodPsListeners.add(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerPSEvent(com.ibm.eNetwork.ECL.event.ECLPSListener listener) {
|
||||||
|
RegisterPSEvent(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnregisterPSEvent(com.ibm.eNetwork.ECL.event.ECLPSListener listener) {
|
||||||
|
if (listener != null) {
|
||||||
|
hodPsListeners.remove(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterPSEvent(com.ibm.eNetwork.ECL.event.ECLPSListener listener) {
|
||||||
|
UnregisterPSEvent(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String[] HACL_MNEMONICS = new String[]{
|
||||||
|
"[attn]", "[backspace]", "[backtab]", "[clear]", "[down]", "[left]", "[right]", "[up]",
|
||||||
|
"[delete]", "[enter]", "[tab]", "[eof]", "[eraseeof]", "[erasefld]", "[erinp]",
|
||||||
|
"[pf1]", "[pf2]", "[pf3]", "[pf4]", "[pf5]", "[pf6]", "[pf7]", "[pf8]", "[pf9]",
|
||||||
|
"[pf10]", "[pf11]", "[pf12]", "[pf13]", "[pf14]", "[pf15]", "[pf16]", "[pf17]",
|
||||||
|
"[pf18]", "[pf19]", "[pf20]", "[pf21]", "[pf22]", "[pf23]", "[pf24]",
|
||||||
|
"[newline]", "[pa1]", "[pa2]", "[pa3]", "[pagedn]", "[pageup]", "[sysreq]",
|
||||||
|
"[wordleft]", "[wordright]", "[backtabword]", "[tabword]", "[previousword]",
|
||||||
|
"[nextword]", "[deleteword]", "[insert]", "[home]", "[reset]", "[dup]",
|
||||||
|
"[fieldmark]", "[fldext]", "[field+]", "[field-]", "[test]", "[help]",
|
||||||
|
"[bof]", "[cursel]", "[selfld]", "[deselfld]", "[+cr]", "[altview]",
|
||||||
|
"[printhost]", "[screenrev]", "[copy]", "[paste]", "[cut]", "[markleft]",
|
||||||
|
"[markright]", "[markup]", "[markdown]", "[unmark]", "[enterreset]"
|
||||||
|
};
|
||||||
|
|
||||||
|
public String[] GetSendKeyMnemonics() {
|
||||||
|
return HACL_MNEMONICS.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getSendKeyMnemonics() {
|
||||||
|
return GetSendKeyMnemonics();
|
||||||
|
}
|
||||||
|
|
||||||
public void dispatchEvent(ECLPSEvent event) {
|
public void dispatchEvent(ECLPSEvent event) {
|
||||||
notifyPSEvent(event);
|
notifyPSEvent(event);
|
||||||
}
|
}
|
||||||
@@ -1045,6 +1549,26 @@ public class ECLPS implements ECLConstants {
|
|||||||
}
|
}
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {}
|
||||||
}
|
}
|
||||||
|
for (com.ibm.eNetwork.ECL.ECLPSNotify n : psNotifies) {
|
||||||
|
Object d = notifyDescriptors.get(n);
|
||||||
|
if (d instanceof ECLScreenDesc && !((ECLScreenDesc) d).Matches(this, session != null ? session.GetOIA() : null)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int filter = notifyEventTypes.getOrDefault(n, ALL_EVENTS);
|
||||||
|
int evtCategory = event.GetType();
|
||||||
|
if (filter != ALL_EVENTS && (filter & evtCategory) == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
n.NotifyEvent(new com.ibm.eNetwork.ECL.ECLPS(this));
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
for (com.ibm.eNetwork.ECL.event.ECLPSListener hl : hodPsListeners) {
|
||||||
|
try {
|
||||||
|
com.ibm.eNetwork.ECL.event.ECLPSEvent facadeEvt = new com.ibm.eNetwork.ECL.event.ECLPSEvent(event);
|
||||||
|
hl.PSNotifyEvent(facadeEvt);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyPSError(ECLErr err) {
|
public void notifyPSError(ECLErr err) {
|
||||||
@@ -1053,6 +1577,19 @@ public class ECLPS implements ECLConstants {
|
|||||||
l.PSNotifyError(this, err);
|
l.PSNotifyError(this, err);
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {}
|
||||||
}
|
}
|
||||||
|
com.ibm.eNetwork.ECL.ECLErr ferr = (err instanceof com.ibm.eNetwork.ECL.ECLErr)
|
||||||
|
? (com.ibm.eNetwork.ECL.ECLErr) err
|
||||||
|
: new com.ibm.eNetwork.ECL.ECLErr(err != null ? err.getMessage() : "");
|
||||||
|
for (com.ibm.eNetwork.ECL.ECLPSNotify n : psNotifies) {
|
||||||
|
try {
|
||||||
|
n.NotifyError(new com.ibm.eNetwork.ECL.ECLPS(this), ferr);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
for (com.ibm.eNetwork.ECL.event.ECLPSListener hl : hodPsListeners) {
|
||||||
|
try {
|
||||||
|
hl.PSNotifyError(new com.ibm.eNetwork.ECL.ECLPS(this), ferr);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyPSStop(int reason) {
|
public void notifyPSStop(int reason) {
|
||||||
@@ -1061,6 +1598,16 @@ public class ECLPS implements ECLConstants {
|
|||||||
l.PSNotifyStop(this, reason);
|
l.PSNotifyStop(this, reason);
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {}
|
||||||
}
|
}
|
||||||
|
for (com.ibm.eNetwork.ECL.ECLPSNotify n : psNotifies) {
|
||||||
|
try {
|
||||||
|
n.NotifyStop(new com.ibm.eNetwork.ECL.ECLPS(this), reason);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
for (com.ibm.eNetwork.ECL.event.ECLPSListener hl : hodPsListeners) {
|
||||||
|
try {
|
||||||
|
hl.PSNotifyStop(new com.ibm.eNetwork.ECL.ECLPS(this), reason);
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyPSUpdate(int startRow, int startCol, int endRow, int endCol, boolean full) {
|
public void notifyPSUpdate(int startRow, int startCol, int endRow, int endCol, boolean full) {
|
||||||
@@ -1128,6 +1675,25 @@ public class ECLPS implements ECLConstants {
|
|||||||
sendKeys(keys);
|
sendKeys(keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void sendKeys(String keys, int pos) {
|
||||||
|
if (pos >= 1 && pos <= getSize()) {
|
||||||
|
setCursorPos(pos - 1);
|
||||||
|
}
|
||||||
|
sendKeys(keys);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendKeys(String keys, int pos) {
|
||||||
|
sendKeys(keys, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendKeysNoAid(String string, boolean bl) {
|
||||||
|
sendKeys(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendKeysNoAid(String string, int row, int col, boolean bl) {
|
||||||
|
sendKeys(string, row, col);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send keystrokes with configurable inter-character or inter-mnemonic delay.
|
* Send keystrokes with configurable inter-character or inter-mnemonic delay.
|
||||||
*/
|
*/
|
||||||
@@ -1154,6 +1720,17 @@ public class ECLPS implements ECLConstants {
|
|||||||
sendCharacters(keys, row, col, delayMs);
|
sendCharacters(keys, row, col, delayMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void sendCharacters(String keys, int pos, int delayMs) {
|
||||||
|
if (pos >= 1 && pos <= getSize()) {
|
||||||
|
setCursorPos(pos - 1);
|
||||||
|
}
|
||||||
|
sendCharacters(keys, delayMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendCharacters(String keys, int pos, int delayMs) {
|
||||||
|
sendCharacters(keys, pos, delayMs);
|
||||||
|
}
|
||||||
|
|
||||||
public void sendCharacters(String keys, int delayMs) {
|
public void sendCharacters(String keys, int delayMs) {
|
||||||
if (keys == null || keys.isEmpty()) return;
|
if (keys == null || keys.isEmpty()) return;
|
||||||
if (delayMs <= 0) {
|
if (delayMs <= 0) {
|
||||||
@@ -1161,37 +1738,36 @@ public class ECLPS implements ECLConstants {
|
|||||||
return;
|
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 {
|
try {
|
||||||
|
SendKeyTokenizer tokenizer = new SendKeyTokenizer(keys);
|
||||||
|
while (tokenizer.hasMoreTokens()) {
|
||||||
|
String token = tokenizer.nextToken();
|
||||||
|
if (token == null) break;
|
||||||
|
|
||||||
|
if (token.length() == 1) {
|
||||||
|
if (inputProcessor != null) inputProcessor.typeCharacter(token.charAt(0));
|
||||||
|
if (delayMs > 0 && tokenizer.hasMoreTokens()) {
|
||||||
Thread.sleep(delayMs);
|
Thread.sleep(delayMs);
|
||||||
|
}
|
||||||
|
} else if (token.startsWith("[") && token.endsWith("]")) {
|
||||||
|
if (inputProcessor != null) inputProcessor.executeMnemonicToken(token);
|
||||||
|
if (delayMs > 0 && tokenizer.hasMoreTokens()) {
|
||||||
|
Thread.sleep(delayMs);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < token.length(); i++) {
|
||||||
|
if (inputProcessor != null) inputProcessor.typeCharacter(token.charAt(i));
|
||||||
|
if (delayMs > 0 && (i < token.length() - 1 || tokenizer.hasMoreTokens())) {
|
||||||
|
Thread.sleep(delayMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
break;
|
} catch (ECLErr e) {
|
||||||
}
|
if (inputProcessor != null) {
|
||||||
|
sendKeys(keys);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,6 +196,14 @@ public class ECLPSEvent extends EventObject {
|
|||||||
return getECLPSUpdate();
|
return getECLPSUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ECLPSUpdate getPSUpdate() {
|
||||||
|
return getECLPSUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLPSUpdate GetPSUpdate() {
|
||||||
|
return getECLPSUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
public ECLPS getPS() {
|
public ECLPS getPS() {
|
||||||
return (getSource() instanceof ECLPS) ? (ECLPS) getSource() : null;
|
return (getSource() instanceof ECLPS) ? (ECLPS) getSource() : null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,279 +1,487 @@
|
|||||||
package haus.nightmare.lib3270j.ecl;
|
package haus.nightmare.lib3270j.ecl;
|
||||||
|
|
||||||
import haus.nightmare.lib3270j.Telnet3270Client;
|
import java.io.Serializable;
|
||||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
import java.io.StringReader;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
import org.xml.sax.InputSource;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||||
|
import haus.nightmare.lib3270j.ecl.screenreco.ECLSDAttrib;
|
||||||
|
import haus.nightmare.lib3270j.ecl.screenreco.ECLSDBlock;
|
||||||
|
import haus.nightmare.lib3270j.ecl.screenreco.ECLSDChecksum;
|
||||||
|
import haus.nightmare.lib3270j.ecl.screenreco.ECLSDCursor;
|
||||||
|
import haus.nightmare.lib3270j.ecl.screenreco.ECLSDFields;
|
||||||
|
import haus.nightmare.lib3270j.ecl.screenreco.ECLSDInputFields;
|
||||||
|
import haus.nightmare.lib3270j.ecl.screenreco.ECLSDOIA;
|
||||||
|
import haus.nightmare.lib3270j.ecl.screenreco.ECLSDString;
|
||||||
|
import haus.nightmare.lib3270j.ecl.screenreco.ECLScreenDescriptor;
|
||||||
|
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Screen descriptor matching engine for IBM Host On-Demand ECL automation.
|
* Screen descriptor composite matching engine for IBM Host On-Demand ECL automation.
|
||||||
* Encapsulates criteria for matching host screens (strings, rectangular regions, cursor positions,
|
* Encapsulates criteria for matching host screens (strings, rectangular regions, cursor positions,
|
||||||
* field counts, and OIA status).
|
* field counts, OIA status, checksums, and attributes) and supports HACL XML schema serialization.
|
||||||
*/
|
*/
|
||||||
public class ECLScreenDesc {
|
public class ECLScreenDesc implements Serializable, Cloneable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private final List<ScreenCondition> conditions = new ArrayList<>();
|
private final List<ECLScreenDescriptor> descriptors = new CopyOnWriteArrayList<>();
|
||||||
|
private String name = "";
|
||||||
private interface ScreenCondition {
|
private boolean active = true;
|
||||||
boolean matches(ECLPS ps, ECLOIA oia);
|
private boolean match = false;
|
||||||
}
|
public boolean useOIAInhibitStatus = false;
|
||||||
|
|
||||||
public ECLScreenDesc() {}
|
public ECLScreenDesc() {}
|
||||||
|
|
||||||
/**
|
public ECLScreenDesc(String xml) {
|
||||||
* Clear all conditions from this descriptor.
|
if (xml != null && !xml.trim().isEmpty()) {
|
||||||
*/
|
setXML(xml);
|
||||||
public synchronized void Clear() {
|
}
|
||||||
conditions.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void clear() {
|
// ========== Descriptor Management ==========
|
||||||
Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public void AddDescriptor(ECLScreenDescriptor descriptor) {
|
||||||
* Add a condition that the given string must appear anywhere on the presentation space.
|
if (descriptor != null) {
|
||||||
*/
|
descriptors.add(descriptor);
|
||||||
public synchronized void AddString(String text) {
|
}
|
||||||
|
}
|
||||||
|
public void addDescriptor(ECLScreenDescriptor descriptor) { AddDescriptor(descriptor); }
|
||||||
|
|
||||||
|
public void RemoveDescriptor(ECLScreenDescriptor descriptor) {
|
||||||
|
if (descriptor != null) {
|
||||||
|
descriptors.remove(descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void removeDescriptor(ECLScreenDescriptor descriptor) { RemoveDescriptor(descriptor); }
|
||||||
|
|
||||||
|
public List<ECLScreenDescriptor> GetDescriptors() {
|
||||||
|
return Collections.unmodifiableList(new ArrayList<>(descriptors));
|
||||||
|
}
|
||||||
|
public List<ECLScreenDescriptor> getDescriptors() { return GetDescriptors(); }
|
||||||
|
|
||||||
|
public ECLScreenDescriptor Get(int index) {
|
||||||
|
return descriptors.get(index);
|
||||||
|
}
|
||||||
|
public ECLScreenDescriptor get(int index) { return Get(index); }
|
||||||
|
|
||||||
|
public int Size() { return descriptors.size(); }
|
||||||
|
public int size() { return Size(); }
|
||||||
|
public int getConditionCount() { return Size(); }
|
||||||
|
|
||||||
|
public void Clear() { descriptors.clear(); }
|
||||||
|
public void clear() { Clear(); }
|
||||||
|
|
||||||
|
public String GetName() { return name; }
|
||||||
|
public String getName() { return name; }
|
||||||
|
public void SetName(String name) { this.name = (name != null) ? name : ""; }
|
||||||
|
public void setName(String name) { SetName(name); }
|
||||||
|
|
||||||
|
public boolean IsActive() { return active; }
|
||||||
|
public boolean isActive() { return active; }
|
||||||
|
public void SetActive(boolean active) { this.active = active; }
|
||||||
|
public void setActive(boolean active) { SetActive(active); }
|
||||||
|
|
||||||
|
public boolean IsMatch() { return match; }
|
||||||
|
public boolean isMatch() { return match; }
|
||||||
|
public void SetMatch(boolean match) { this.match = match; }
|
||||||
|
public void setMatch(boolean match) { SetMatch(match); }
|
||||||
|
|
||||||
|
// ========== Programmatic Add* Methods ==========
|
||||||
|
|
||||||
|
public void AddString(String text) {
|
||||||
if (text == null || text.isEmpty()) return;
|
if (text == null || text.isEmpty()) return;
|
||||||
conditions.add((ps, oia) -> {
|
AddDescriptor(new ECLSDString(text));
|
||||||
if (ps == null) return false;
|
|
||||||
return ps.searchString(text) >= 0;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
public void addString(String text) { AddString(text); }
|
||||||
|
|
||||||
public synchronized void addString(String text) {
|
public void AddString(String text, int row, int col) {
|
||||||
AddString(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a condition that text must appear at 1-based (row, col) position (case-insensitive by default).
|
|
||||||
*/
|
|
||||||
public synchronized void AddString(String text, int row, int col) {
|
|
||||||
AddString(text, row, col, true);
|
AddString(text, row, col, true);
|
||||||
}
|
}
|
||||||
|
public void addString(String text, int row, int col) { AddString(text, row, col); }
|
||||||
|
|
||||||
public synchronized void addString(String text, int row, int col) {
|
public void AddString(String text, int row, int col, boolean caseSense) {
|
||||||
AddString(text, row, col, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a condition that text must appear at (row, col) with case sensitivity control.
|
|
||||||
* Note: 1-based indexing conforming to IBM ECL specification (row 1..rows, col 1..cols).
|
|
||||||
*/
|
|
||||||
public synchronized void AddString(String text, int row, int col, boolean caseSense) {
|
|
||||||
if (text == null || text.isEmpty()) return;
|
if (text == null || text.isEmpty()) return;
|
||||||
conditions.add((ps, oia) -> {
|
AddDescriptor(new ECLSDString(text, row, col, caseSense));
|
||||||
if (ps == null) return false;
|
|
||||||
int r = (row > 0) ? row - 1 : 0;
|
|
||||||
int c = (col > 0) ? col - 1 : 0;
|
|
||||||
String onScreen = ps.getString(r, c, text.length());
|
|
||||||
return caseSense ? text.equals(onScreen) : text.equalsIgnoreCase(onScreen);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
public void addString(String text, int row, int col, boolean caseSense) { AddString(text, row, col, caseSense); }
|
||||||
|
|
||||||
public synchronized void addString(String text, int row, int col, boolean caseSense) {
|
public void AddString(String text, int pos, boolean caseSense) {
|
||||||
AddString(text, row, col, caseSense);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a condition that text must appear at a 1-based linear buffer position.
|
|
||||||
*/
|
|
||||||
public synchronized void AddString(String text, int pos, boolean caseSense) {
|
|
||||||
if (text == null || text.isEmpty()) return;
|
if (text == null || text.isEmpty()) return;
|
||||||
conditions.add((ps, oia) -> {
|
AddDescriptor(new ECLSDString(text, pos, caseSense));
|
||||||
if (ps == null) return false;
|
|
||||||
int p0 = (pos > 0) ? pos - 1 : 0;
|
|
||||||
String onScreen = ps.getString(p0, text.length());
|
|
||||||
return caseSense ? text.equals(onScreen) : text.equalsIgnoreCase(onScreen);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
public void addString(String text, int pos, boolean caseSense) { AddString(text, pos, caseSense); }
|
||||||
|
|
||||||
public synchronized void addString(String text, int pos, boolean caseSense) {
|
public void AddStringInRect(String text, int sRow, int sCol, int eRow, int eCol, boolean caseSense) {
|
||||||
AddString(text, pos, caseSense);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a condition that text must appear within a rectangular region.
|
|
||||||
*/
|
|
||||||
public synchronized void AddStringInRect(String text, int sRow, int sCol, int eRow, int eCol, boolean caseSense) {
|
|
||||||
if (text == null || text.isEmpty()) return;
|
if (text == null || text.isEmpty()) return;
|
||||||
conditions.add((ps, oia) -> {
|
AddDescriptor(new ECLSDString(text, sRow, sCol, eRow, eCol, caseSense, false, false));
|
||||||
if (ps == null) return false;
|
|
||||||
int sr = (sRow > 0) ? sRow - 1 : 0;
|
|
||||||
int sc = (sCol > 0) ? sCol - 1 : 0;
|
|
||||||
int er = (eRow > 0) ? eRow - 1 : 0;
|
|
||||||
int ec = (eCol > 0) ? eCol - 1 : 0;
|
|
||||||
String block = ps.copyString(sr, sc, er, ec);
|
|
||||||
if (block == null) return false;
|
|
||||||
return caseSense ? block.contains(text) : block.toLowerCase().contains(text.toLowerCase());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
public void addStringInRect(String text, int sRow, int sCol, int eRow, int eCol, boolean caseSense) {
|
||||||
public synchronized void addStringInRect(String text, int sRow, int sCol, int eRow, int eCol, boolean caseSense) {
|
|
||||||
AddStringInRect(text, sRow, sCol, eRow, eCol, caseSense);
|
AddStringInRect(text, sRow, sCol, eRow, eCol, caseSense);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void AddCursorPos(int row, int col) {
|
||||||
* Add a condition that the cursor must be located at 1-based (row, col).
|
AddDescriptor(new ECLSDCursor(row, col));
|
||||||
*/
|
|
||||||
public synchronized void AddCursorPos(int row, int col) {
|
|
||||||
conditions.add((ps, oia) -> {
|
|
||||||
if (ps == null) return false;
|
|
||||||
int r = (row > 0) ? row - 1 : 0;
|
|
||||||
int c = (col > 0) ? col - 1 : 0;
|
|
||||||
return ps.getCursorRow() == r && ps.getCursorCol() == c;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
public void addCursorPos(int row, int col) { AddCursorPos(row, col); }
|
||||||
|
|
||||||
public synchronized void addCursorPos(int row, int col) {
|
public void AddCursorPos(int pos) {
|
||||||
AddCursorPos(row, col);
|
AddDescriptor(new ECLSDCursor(pos));
|
||||||
}
|
}
|
||||||
|
public void addCursorPos(int pos) { AddCursorPos(pos); }
|
||||||
|
|
||||||
/**
|
public void AddNumFields(int count) {
|
||||||
* Add a condition that the cursor must be located at 1-based linear buffer position.
|
AddDescriptor(new ECLSDFields(count));
|
||||||
*/
|
|
||||||
public synchronized void AddCursorPos(int pos) {
|
|
||||||
conditions.add((ps, oia) -> {
|
|
||||||
if (ps == null) return false;
|
|
||||||
int p0 = (pos > 0) ? pos - 1 : 0;
|
|
||||||
return ps.getCursorPos() == p0;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
public void addNumFields(int count) { AddNumFields(count); }
|
||||||
|
|
||||||
public synchronized void addCursorPos(int pos) {
|
public void AddNumInputFields(int count) {
|
||||||
AddCursorPos(pos);
|
AddDescriptor(new ECLSDInputFields(count));
|
||||||
}
|
}
|
||||||
|
public void addNumInputFields(int count) { AddNumInputFields(count); }
|
||||||
|
|
||||||
/**
|
public void AddOIAStatus(int status) {
|
||||||
* Add a condition on total field count on the formatted screen.
|
ECLSDOIA oia = new ECLSDOIA();
|
||||||
*/
|
oia.setExplicitInhibitStatus(status);
|
||||||
public synchronized void AddNumFields(int count) {
|
AddDescriptor(oia);
|
||||||
conditions.add((ps, oia) -> {
|
|
||||||
if (ps == null) return false;
|
|
||||||
return ps.getFieldList().getFieldCount() == count;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
public void addOIAStatus(int status) { AddOIAStatus(status); }
|
||||||
|
|
||||||
public synchronized void addNumFields(int count) {
|
public void AddOIAInhibitStatus(int status) {
|
||||||
AddNumFields(count);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a condition on number of unprotected input fields on the screen.
|
|
||||||
*/
|
|
||||||
public synchronized void AddNumInputFields(int count) {
|
|
||||||
conditions.add((ps, oia) -> {
|
|
||||||
if (ps == null) return false;
|
|
||||||
int inputCount = 0;
|
|
||||||
for (ECLField f : ps.getFieldList().getFields()) {
|
|
||||||
if (!f.isProtected()) {
|
|
||||||
inputCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return inputCount == count;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void addNumInputFields(int count) {
|
|
||||||
AddNumInputFields(count);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a condition on OIA inhibit status.
|
|
||||||
*/
|
|
||||||
public synchronized void AddOIAStatus(int status) {
|
|
||||||
conditions.add((ps, oia) -> {
|
|
||||||
if (oia == null) return true;
|
|
||||||
return oia.getInputInhibited() == status;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void addOIAStatus(int status) {
|
|
||||||
AddOIAStatus(status);
|
AddOIAStatus(status);
|
||||||
}
|
}
|
||||||
|
public void addOIAInhibitStatus(int status) { AddOIAInhibitStatus(status); }
|
||||||
|
|
||||||
/**
|
public void AddAttrib(char c, int row, int col, int plane) {
|
||||||
* Check if the current screen state matches all conditions in this descriptor.
|
AddDescriptor(new ECLSDAttrib(c, row, col, plane));
|
||||||
*/
|
|
||||||
public synchronized boolean Matches(ECLPS ps, ECLOIA oia) {
|
|
||||||
if (conditions.isEmpty()) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
for (ScreenCondition cond : conditions) {
|
public void addAttrib(char c, int row, int col, int plane) { AddAttrib(c, row, col, plane); }
|
||||||
if (!cond.matches(ps, oia)) {
|
|
||||||
return false;
|
public void AddAttrib(char c, int row, int col, int plane, boolean optional) {
|
||||||
|
AddDescriptor(new ECLSDAttrib(c, row, col, plane, optional));
|
||||||
}
|
}
|
||||||
}
|
public void addAttrib(char c, int row, int col, int plane, boolean optional) {
|
||||||
return true;
|
AddAttrib(c, row, col, plane, optional);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean matches(ECLPS ps, ECLOIA oia) {
|
public void AddFieldAttr(int pos, int attr) {
|
||||||
return Matches(ps, oia);
|
AddDescriptor(new ECLScreenDescriptor() {
|
||||||
}
|
private static final long serialVersionUID = 1L;
|
||||||
|
@Override
|
||||||
public boolean Matches(ECLSession session) {
|
protected boolean checkMatch(ECLPS ps, ECLOIA oia) {
|
||||||
if (session == null) return false;
|
|
||||||
return Matches(session.GetPS(), session.GetOIA());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean matches(ECLSession session) {
|
|
||||||
return Matches(session);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean Matches(Telnet3270Client client) {
|
|
||||||
if (client == null) return false;
|
|
||||||
return Matches(client.getPS(), client.getOIA());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean matches(Telnet3270Client client) {
|
|
||||||
return Matches(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a condition on field attributes at a 1-based buffer position.
|
|
||||||
*/
|
|
||||||
public synchronized void AddFieldAttr(int pos, int attr) {
|
|
||||||
conditions.add((ps, oia) -> {
|
|
||||||
if (ps == null) return false;
|
if (ps == null) return false;
|
||||||
int p0 = (pos > 0) ? pos - 1 : 0;
|
int p0 = (pos > 0) ? pos - 1 : 0;
|
||||||
ECLField f = ps.getField(p0);
|
ECLField f = ps.getField(p0);
|
||||||
if (f == null) return false;
|
if (f == null) return false;
|
||||||
return ps.getFieldList().matchAttributes(f, attr);
|
return ps.getFieldList().matchAttributes(f, attr);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public String format(int indent, boolean full) {
|
||||||
|
return "<attrib pos=\"" + pos + "\" attr=\"" + attr + "\" />";
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public synchronized void addFieldAttr(int pos, int attr) {
|
public void addFieldAttr(int pos, int attr) { AddFieldAttr(pos, attr); }
|
||||||
AddFieldAttr(pos, attr);
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized void AddFieldAttr(int row, int col, int attr) {
|
public void AddFieldAttr(int row, int col, int attr) {
|
||||||
conditions.add((ps, oia) -> {
|
AddDescriptor(new ECLScreenDescriptor() {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
@Override
|
||||||
|
protected boolean checkMatch(ECLPS ps, ECLOIA oia) {
|
||||||
if (ps == null) return false;
|
if (ps == null) return false;
|
||||||
int r = (row > 0) ? row - 1 : 0;
|
int r = (row > 0) ? row - 1 : 0;
|
||||||
int c = (col > 0) ? col - 1 : 0;
|
int c = (col > 0) ? col - 1 : 0;
|
||||||
ECLField f = ps.getField(r, c);
|
ECLField f = ps.getField(r, c);
|
||||||
if (f == null) return false;
|
if (f == null) return false;
|
||||||
return ps.getFieldList().matchAttributes(f, attr);
|
return ps.getFieldList().matchAttributes(f, attr);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public String format(int indent, boolean full) {
|
||||||
|
return "<attrib row=\"" + row + "\" col=\"" + col + "\" attr=\"" + attr + "\" />";
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public synchronized void addFieldAttr(int row, int col, int attr) {
|
public void addFieldAttr(int row, int col, int attr) { AddFieldAttr(row, col, attr); }
|
||||||
AddFieldAttr(row, col, attr);
|
|
||||||
|
public void AddChecksum(int checksum) {
|
||||||
|
AddDescriptor(new ECLSDChecksum(checksum));
|
||||||
|
}
|
||||||
|
public void addChecksum(int checksum) { AddChecksum(checksum); }
|
||||||
|
|
||||||
|
public void AddChecksum(int checksum, boolean optional) {
|
||||||
|
AddDescriptor(new ECLSDChecksum(checksum, optional));
|
||||||
|
}
|
||||||
|
public void addChecksum(int checksum, boolean optional) { AddChecksum(checksum, optional); }
|
||||||
|
|
||||||
|
public void AddBlock(String[] strings, int sRow, int sCol, boolean caseSense) {
|
||||||
|
AddDescriptor(new ECLSDBlock(strings, sRow, sCol, caseSense));
|
||||||
|
}
|
||||||
|
public void addBlock(String[] strings, int sRow, int sCol, boolean caseSense) {
|
||||||
|
AddBlock(strings, sRow, sCol, caseSense);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean useOIAInhibitStatus = false;
|
public void AddBlock(ECLSDBlock block) {
|
||||||
|
AddDescriptor(block);
|
||||||
|
}
|
||||||
|
public void addBlock(ECLSDBlock block) { AddBlock(block); }
|
||||||
|
|
||||||
|
// ========== Matching Evaluation ==========
|
||||||
|
|
||||||
|
public synchronized boolean Matches(ECLPS ps, ECLOIA oia) {
|
||||||
|
if (descriptors.isEmpty()) {
|
||||||
|
this.match = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (ps == null) {
|
||||||
|
this.match = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasNonOptional = false;
|
||||||
|
boolean allNonOptionalMatch = true;
|
||||||
|
boolean hasOptional = false;
|
||||||
|
boolean anyOptionalMatch = false;
|
||||||
|
|
||||||
|
for (ECLScreenDescriptor desc : descriptors) {
|
||||||
|
boolean m = desc.matches(ps, oia);
|
||||||
|
if (desc.isOptional()) {
|
||||||
|
hasOptional = true;
|
||||||
|
if (m) anyOptionalMatch = true;
|
||||||
|
} else {
|
||||||
|
hasNonOptional = true;
|
||||||
|
if (!m) allNonOptionalMatch = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean res;
|
||||||
|
if (hasNonOptional) {
|
||||||
|
res = allNonOptionalMatch;
|
||||||
|
} else if (hasOptional) {
|
||||||
|
res = anyOptionalMatch;
|
||||||
|
} else {
|
||||||
|
res = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.match = res;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matches(ECLPS ps, ECLOIA oia) { return Matches(ps, oia); }
|
||||||
|
|
||||||
|
public boolean Matches(ECLSession session) {
|
||||||
|
if (session == null) return false;
|
||||||
|
return Matches(session.GetPS(), session.GetOIA());
|
||||||
|
}
|
||||||
|
public boolean matches(ECLSession session) { return Matches(session); }
|
||||||
|
|
||||||
|
public boolean Matches(Telnet3270Client client) {
|
||||||
|
if (client == null) return false;
|
||||||
|
return Matches(client.getPS(), client.getOIA());
|
||||||
|
}
|
||||||
|
public boolean matches(Telnet3270Client client) { return Matches(client); }
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a raw ScreenBuffer matches all criteria in this screen descriptor.
|
|
||||||
*/
|
|
||||||
public boolean Matches(ScreenBuffer buf) {
|
public boolean Matches(ScreenBuffer buf) {
|
||||||
if (buf == null) return false;
|
if (buf == null) return false;
|
||||||
return Matches(new ECLPS(buf, null, null), null);
|
return Matches(new ECLPS(buf, null, null), null);
|
||||||
}
|
}
|
||||||
public boolean matches(ScreenBuffer buf) {
|
public boolean matches(ScreenBuffer buf) { return Matches(buf); }
|
||||||
return Matches(buf);
|
|
||||||
|
// ========== XML Serialization & Parsing ==========
|
||||||
|
|
||||||
|
public String getXML() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("<description>\n");
|
||||||
|
for (ECLScreenDescriptor desc : descriptors) {
|
||||||
|
sb.append(" ").append(desc.format(0, false)).append("\n");
|
||||||
|
}
|
||||||
|
sb.append("</description>");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
public String GetXML() { return getXML(); }
|
||||||
|
|
||||||
|
public void setXML(String xml) {
|
||||||
|
Clear();
|
||||||
|
if (xml == null || xml.trim().isEmpty()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized int getConditionCount() {
|
try {
|
||||||
return conditions.size();
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||||
|
try {
|
||||||
|
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
try {
|
||||||
|
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
try {
|
||||||
|
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
|
||||||
|
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||||
|
Document doc = builder.parse(new InputSource(new StringReader(xml.trim())));
|
||||||
|
Element root = doc.getDocumentElement();
|
||||||
|
|
||||||
|
Element descElement = root;
|
||||||
|
if (!root.getTagName().equalsIgnoreCase("description")) {
|
||||||
|
NodeList list = root.getElementsByTagName("description");
|
||||||
|
if (list.getLength() > 0) {
|
||||||
|
descElement = (Element) list.item(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NodeList children = descElement.getChildNodes();
|
||||||
|
for (int i = 0; i < children.getLength(); i++) {
|
||||||
|
Node node = children.item(i);
|
||||||
|
if (node.getNodeType() != Node.ELEMENT_NODE) continue;
|
||||||
|
Element elem = (Element) node;
|
||||||
|
String tag = elem.getTagName().toLowerCase();
|
||||||
|
|
||||||
|
boolean optional = "true".equalsIgnoreCase(elem.getAttribute("optional"));
|
||||||
|
boolean invertmatch = "true".equalsIgnoreCase(elem.getAttribute("invertmatch"));
|
||||||
|
String hostid = elem.getAttribute("hostid");
|
||||||
|
|
||||||
|
if (tag.equals("string")) {
|
||||||
|
String value = elem.getAttribute("value");
|
||||||
|
int row = parseIntAttr(elem, "row", 0);
|
||||||
|
int col = parseIntAttr(elem, "col", 0);
|
||||||
|
int erow = parseIntAttr(elem, "erow", -1);
|
||||||
|
int ecol = parseIntAttr(elem, "ecol", -1);
|
||||||
|
boolean casesense = "true".equalsIgnoreCase(elem.getAttribute("casesense"));
|
||||||
|
boolean wrap = "true".equalsIgnoreCase(elem.getAttribute("wrap"));
|
||||||
|
|
||||||
|
ECLSDString s;
|
||||||
|
if (row > 0 && col > 0) {
|
||||||
|
if (erow > 0 || ecol > 0) {
|
||||||
|
s = new ECLSDString(value, row, col, erow, ecol, casesense, optional, wrap);
|
||||||
|
} else {
|
||||||
|
s = new ECLSDString(value, row, col, casesense, optional);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s = new ECLSDString(value);
|
||||||
|
s.setCaseSense(casesense);
|
||||||
|
s.setOptional(optional);
|
||||||
|
}
|
||||||
|
s.setInvertMatch(invertmatch);
|
||||||
|
s.setHostid(hostid);
|
||||||
|
AddDescriptor(s);
|
||||||
|
} else if (tag.equals("cursor")) {
|
||||||
|
int row = parseIntAttr(elem, "row", 1);
|
||||||
|
int col = parseIntAttr(elem, "col", 1);
|
||||||
|
int pos = parseIntAttr(elem, "position", -1);
|
||||||
|
ECLSDCursor c;
|
||||||
|
if (pos > 0) {
|
||||||
|
c = new ECLSDCursor(pos, optional);
|
||||||
|
} else {
|
||||||
|
c = new ECLSDCursor(row, col, optional);
|
||||||
|
}
|
||||||
|
c.setInvertMatch(invertmatch);
|
||||||
|
c.setHostid(hostid);
|
||||||
|
AddDescriptor(c);
|
||||||
|
} else if (tag.equals("numfields") || tag.equals("fields")) {
|
||||||
|
int num = parseIntAttr(elem, elem.hasAttribute("number") ? "number" : "value", 0);
|
||||||
|
ECLSDFields f = new ECLSDFields(num, optional);
|
||||||
|
f.setInvertMatch(invertmatch);
|
||||||
|
f.setHostid(hostid);
|
||||||
|
AddDescriptor(f);
|
||||||
|
} else if (tag.equals("numinputfields") || tag.equals("inputfields")) {
|
||||||
|
int num = parseIntAttr(elem, elem.hasAttribute("number") ? "number" : "value", 0);
|
||||||
|
ECLSDInputFields inf = new ECLSDInputFields(num, optional);
|
||||||
|
inf.setInvertMatch(invertmatch);
|
||||||
|
inf.setHostid(hostid);
|
||||||
|
AddDescriptor(inf);
|
||||||
|
} else if (tag.equals("oia")) {
|
||||||
|
String status = elem.hasAttribute("status") ? elem.getAttribute("status") :
|
||||||
|
(elem.hasAttribute("input") ? elem.getAttribute("input") : "NOTINHIBITED");
|
||||||
|
ECLSDOIA oia = new ECLSDOIA(status, optional);
|
||||||
|
oia.setInvertMatch(invertmatch);
|
||||||
|
oia.setHostid(hostid);
|
||||||
|
AddDescriptor(oia);
|
||||||
|
} else if (tag.equals("checksum")) {
|
||||||
|
int value = parseIntAttr(elem, "value", 0);
|
||||||
|
ECLSDChecksum cs = new ECLSDChecksum(value, optional);
|
||||||
|
cs.setInvertMatch(invertmatch);
|
||||||
|
cs.setHostid(hostid);
|
||||||
|
AddDescriptor(cs);
|
||||||
|
} else if (tag.equals("attrib")) {
|
||||||
|
String valStr = elem.getAttribute("value");
|
||||||
|
char val = ECLSDAttrib.StringToAttrib(valStr);
|
||||||
|
int row = parseIntAttr(elem, "row", 1);
|
||||||
|
int col = parseIntAttr(elem, "col", 1);
|
||||||
|
int plane = ECLSDAttrib.StringToPlane(elem.getAttribute("plane"));
|
||||||
|
ECLSDAttrib attr = new ECLSDAttrib(val, row, col, plane, optional);
|
||||||
|
attr.setInvertMatch(invertmatch);
|
||||||
|
attr.setHostid(hostid);
|
||||||
|
AddDescriptor(attr);
|
||||||
|
} else if (tag.equals("block")) {
|
||||||
|
int row = parseIntAttr(elem, "row", 1);
|
||||||
|
int col = parseIntAttr(elem, "col", 1);
|
||||||
|
int erow = parseIntAttr(elem, "erow", -1);
|
||||||
|
int ecol = parseIntAttr(elem, "ecol", -1);
|
||||||
|
boolean casesense = "true".equalsIgnoreCase(elem.getAttribute("casesense"));
|
||||||
|
|
||||||
|
ECLSDBlock blk;
|
||||||
|
if (erow > 0 || ecol > 0) {
|
||||||
|
blk = new ECLSDBlock(row, col, erow, ecol, casesense, optional);
|
||||||
|
} else {
|
||||||
|
blk = new ECLSDBlock(row, col, casesense, optional);
|
||||||
|
}
|
||||||
|
blk.setInvertMatch(invertmatch);
|
||||||
|
blk.setHostid(hostid);
|
||||||
|
|
||||||
|
NodeList subList = elem.getElementsByTagName("string");
|
||||||
|
for (int j = 0; j < subList.getLength(); j++) {
|
||||||
|
Element strElem = (Element) subList.item(j);
|
||||||
|
blk.addString(strElem.getAttribute("value"));
|
||||||
|
}
|
||||||
|
AddDescriptor(blk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to parse ECLScreenDesc XML: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void SetXML(String xml) { setXML(xml); }
|
||||||
|
|
||||||
|
private static int parseIntAttr(Element elem, String attrName, int defaultVal) {
|
||||||
|
if (!elem.hasAttribute(attrName)) return defaultVal;
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(elem.getAttribute(attrName).trim());
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return defaultVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object clone() {
|
||||||
|
try {
|
||||||
|
ECLScreenDesc copy = (ECLScreenDesc) super.clone();
|
||||||
|
copy.descriptors.clear();
|
||||||
|
for (ECLScreenDescriptor d : descriptors) {
|
||||||
|
copy.descriptors.add((ECLScreenDescriptor) d.clone());
|
||||||
|
}
|
||||||
|
return copy;
|
||||||
|
} catch (CloneNotSupportedException e) {
|
||||||
|
ECLScreenDesc copy = new ECLScreenDesc();
|
||||||
|
copy.SetName(this.name);
|
||||||
|
copy.SetActive(this.active);
|
||||||
|
for (ECLScreenDescriptor d : descriptors) {
|
||||||
|
copy.AddDescriptor((ECLScreenDescriptor) d.clone());
|
||||||
|
}
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getXML();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,21 +11,28 @@ public class ECLScreenReco {
|
|||||||
|
|
||||||
private final ECLSession session;
|
private final ECLSession session;
|
||||||
private final ECLPS ps;
|
private final ECLPS ps;
|
||||||
|
private final ECLOIA oia;
|
||||||
private final Map<ECLScreenDesc, ECLScreenNotify> notifyMap = new ConcurrentHashMap<>();
|
private final Map<ECLScreenDesc, ECLScreenNotify> notifyMap = new ConcurrentHashMap<>();
|
||||||
private final Map<ECLScreenDesc, ECLPSListener> listenerMap = new ConcurrentHashMap<>();
|
private final Map<ECLScreenDesc, ECLPSListener> listenerMap = new ConcurrentHashMap<>();
|
||||||
private final ECLPSListener internalListener;
|
private final ECLPSListener internalListener;
|
||||||
|
private final ECLOIANotify internalOiaListener;
|
||||||
|
|
||||||
public ECLScreenReco() {
|
public ECLScreenReco() {
|
||||||
this((ECLPS) null);
|
this((ECLPS) null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ECLScreenReco(ECLSession session) {
|
public ECLScreenReco(ECLSession session) {
|
||||||
this(session != null ? session.GetPS() : null);
|
this(session != null ? session.GetPS() : null, session != null ? session.GetOIA() : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ECLScreenReco(ECLPS ps) {
|
public ECLScreenReco(ECLPS ps) {
|
||||||
|
this(ps, (ECLOIA) null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLScreenReco(ECLPS ps, ECLOIA oia) {
|
||||||
this.ps = ps;
|
this.ps = ps;
|
||||||
this.session = (ps != null) ? ps.GetParent() : null;
|
this.session = (ps != null) ? ps.GetParent() : null;
|
||||||
|
this.oia = (oia != null) ? oia : ((session != null) ? session.GetOIA() : null);
|
||||||
|
|
||||||
this.internalListener = new ECLPSListener() {
|
this.internalListener = new ECLPSListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -34,9 +41,14 @@ public class ECLScreenReco {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.internalOiaListener = event -> checkAllScreens();
|
||||||
|
|
||||||
if (this.ps != null) {
|
if (this.ps != null) {
|
||||||
this.ps.RegisterPSEvent(this.internalListener);
|
this.ps.RegisterPSEvent(this.internalListener);
|
||||||
}
|
}
|
||||||
|
if (this.oia != null) {
|
||||||
|
this.oia.RegisterOIAEvent(this.internalOiaListener);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Static Recognition Helpers ==========
|
// ========== Static Recognition Helpers ==========
|
||||||
@@ -95,6 +107,16 @@ public class ECLScreenReco {
|
|||||||
UnregisterScreen(desc, notify);
|
UnregisterScreen(desc, notify);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UnregisterScreen(ECLScreenDesc desc) {
|
||||||
|
if (desc != null) {
|
||||||
|
notifyMap.remove(desc);
|
||||||
|
listenerMap.remove(desc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void unregisterScreen(ECLScreenDesc desc) {
|
||||||
|
UnregisterScreen(desc);
|
||||||
|
}
|
||||||
|
|
||||||
public void RegisterScreen(ECLScreenDesc desc, ECLPSListener listener) {
|
public void RegisterScreen(ECLScreenDesc desc, ECLPSListener listener) {
|
||||||
if (desc != null && listener != null) {
|
if (desc != null && listener != null) {
|
||||||
listenerMap.put(desc, listener);
|
listenerMap.put(desc, listener);
|
||||||
@@ -120,7 +142,10 @@ public class ECLScreenReco {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean IsMatch(ECLScreenDesc desc) {
|
public boolean IsMatch(ECLScreenDesc desc) {
|
||||||
return IsMatch(ps, desc);
|
if (desc == null) return true;
|
||||||
|
if (ps == null) return false;
|
||||||
|
ECLOIA activeOia = (oia != null) ? oia : ((ps.GetParent() != null) ? ps.GetParent().GetOIA() : null);
|
||||||
|
return desc.Matches(ps, activeOia);
|
||||||
}
|
}
|
||||||
public boolean isMatch(ECLScreenDesc desc) {
|
public boolean isMatch(ECLScreenDesc desc) {
|
||||||
return IsMatch(desc);
|
return IsMatch(desc);
|
||||||
@@ -142,6 +167,9 @@ public class ECLScreenReco {
|
|||||||
if (ps != null && internalListener != null) {
|
if (ps != null && internalListener != null) {
|
||||||
ps.UnregisterPSEvent(internalListener);
|
ps.UnregisterPSEvent(internalListener);
|
||||||
}
|
}
|
||||||
|
if (oia != null && internalOiaListener != null) {
|
||||||
|
oia.UnregisterOIAEvent(internalOiaListener);
|
||||||
|
}
|
||||||
notifyMap.clear();
|
notifyMap.clear();
|
||||||
listenerMap.clear();
|
listenerMap.clear();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ public class ECLSession {
|
|||||||
public static final String BLOCK_PASTE = "blockPaste";
|
public static final String BLOCK_PASTE = "blockPaste";
|
||||||
public static final String PASTE_FIELD_WRAP = "pasteFieldWrap";
|
public static final String PASTE_FIELD_WRAP = "pasteFieldWrap";
|
||||||
public static final String PASTE_LINE_WRAP = "pasteLineWrap";
|
public static final String PASTE_LINE_WRAP = "pasteLineWrap";
|
||||||
|
public static final String PASTE_WORD_BREAK = "pasteWordBreak";
|
||||||
public static final String ENTRYASSIST_DOCMODE = "EntryAssist_DOCmode";
|
public static final String ENTRYASSIST_DOCMODE = "EntryAssist_DOCmode";
|
||||||
public static final String ENTRYASSIST_DOCWORDWRAP = "EntryAssist_DOCwordWrap";
|
public static final String ENTRYASSIST_DOCWORDWRAP = "EntryAssist_DOCwordWrap";
|
||||||
public static final String ENTRYASSIST_STARTCOL = "EntryAssist_startCol";
|
public static final String ENTRYASSIST_STARTCOL = "EntryAssist_startCol";
|
||||||
|
|||||||
@@ -456,11 +456,26 @@ public class ECLXfer implements FTCut.FTCutListener, FTDft.FTDftListener {
|
|||||||
this.bytesTransferred = 0;
|
this.bytesTransferred = 0;
|
||||||
this.totalBytes = (config.isSend() && localFile.exists()) ? localFile.length() : 0;
|
this.totalBytes = (config.isSend() && localFile.exists()) ? localFile.length() : 0;
|
||||||
|
|
||||||
|
// Auto-detect DBCS from translator codepage if not explicitly set
|
||||||
|
if (!config.isDbcs() && translator != null && translator.getCodePage() != null && translator.getCodePage().isDBCS()) {
|
||||||
|
config.setDbcs(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for keyboard unlock before typing command
|
||||||
|
waitForKeyboardUnlock(2000);
|
||||||
|
|
||||||
|
// If clear is requested, send Clear AID first to wipe intermediate screen status
|
||||||
|
if (config.isClear() && input != null && !input.isNvtMode()) {
|
||||||
|
input.sendAid(haus.nightmare.lib3270j.protocol.DS3270Constants.AID_CLEAR);
|
||||||
|
try { Thread.sleep(150); } catch (InterruptedException ignored) {}
|
||||||
|
waitForKeyboardUnlock(2000);
|
||||||
|
}
|
||||||
|
|
||||||
String command = config.buildCommand();
|
String command = config.buildCommand();
|
||||||
log.info("ECLXfer: initiating transfer: " + command);
|
log.info("ECLXfer: initiating transfer: " + command);
|
||||||
|
|
||||||
int capacity = input.kybdPrime();
|
int capacity = input != null ? input.kybdPrime() : 0;
|
||||||
if (capacity < command.length()) {
|
if (input != null && capacity < command.length()) {
|
||||||
String err = "Input field capacity insufficient for command (" + capacity + " chars)";
|
String err = "Input field capacity insufficient for command (" + capacity + " chars)";
|
||||||
fireEvent(ECLXferEvent.XFER_ABORTED, FTConstants.ECL_ERR_XFER_ABORT, err);
|
fireEvent(ECLXferEvent.XFER_ABORTED, FTConstants.ECL_ERR_XFER_ABORT, err);
|
||||||
return FTConstants.ECL_ERR_XFER_ABORT;
|
return FTConstants.ECL_ERR_XFER_ABORT;
|
||||||
@@ -469,10 +484,25 @@ public class ECLXfer implements FTCut.FTCutListener, FTDft.FTDftListener {
|
|||||||
setState(FTState.AWAIT_ACK);
|
setState(FTState.AWAIT_ACK);
|
||||||
fireEvent(ECLXferEvent.XFER_STARTED, 0, "Transfer initiated");
|
fireEvent(ECLXferEvent.XFER_STARTED, 0, "Transfer initiated");
|
||||||
|
|
||||||
|
if (input != null) {
|
||||||
input.emulateInput(command + "\n");
|
input.emulateInput(command + "\n");
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void waitForKeyboardUnlock(long timeoutMs) {
|
||||||
|
if (input == null) return;
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
while (input.isKeyboardLocked() && (System.currentTimeMillis() - start) < timeoutMs) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(20);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void parseOptionsIntoConfig(FTConfig config, String options) {
|
private void parseOptionsIntoConfig(FTConfig config, String options) {
|
||||||
if (options == null || options.trim().isEmpty()) return;
|
if (options == null || options.trim().isEmpty()) return;
|
||||||
config.parseOptions(options);
|
config.parseOptions(options);
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package haus.nightmare.lib3270j.ecl.screenreco;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.ecl.ECLOIA;
|
||||||
|
import haus.nightmare.lib3270j.ecl.ECLPS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Screen descriptor matching field or presentation plane attributes matching IBM Host On-Demand ECLSDAttrib.
|
||||||
|
*/
|
||||||
|
public class ECLSDAttrib extends ECLScreenDescriptor {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public static final int TEXT_PLANE = 1;
|
||||||
|
public static final int COLOR_PLANE = 2;
|
||||||
|
public static final int DBCS_PLANE = 4;
|
||||||
|
public static final int GRID_PLANE = 8;
|
||||||
|
public static final int FIELD_PLANE = 16;
|
||||||
|
public static final int EXFIELD_PLANE = 32;
|
||||||
|
|
||||||
|
private char attrib = 0x00;
|
||||||
|
private int row = 1;
|
||||||
|
private int col = 1;
|
||||||
|
private int plane = FIELD_PLANE;
|
||||||
|
|
||||||
|
public ECLSDAttrib() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDAttrib(char attrib, int row, int col, int plane) {
|
||||||
|
this(attrib, row, col, plane, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDAttrib(char attrib, int row, int col, int plane, boolean optional) {
|
||||||
|
super(optional);
|
||||||
|
this.attrib = attrib;
|
||||||
|
this.row = row;
|
||||||
|
this.col = col;
|
||||||
|
this.plane = plane;
|
||||||
|
}
|
||||||
|
|
||||||
|
public char getAttrib() { return attrib; }
|
||||||
|
public char GetAttrib() { return attrib; }
|
||||||
|
public void setAttrib(char attrib) { this.attrib = attrib; }
|
||||||
|
public void SetAttrib(char attrib) { setAttrib(attrib); }
|
||||||
|
|
||||||
|
public int getRow() { return row; }
|
||||||
|
public int GetRow() { return row; }
|
||||||
|
public void setRow(int row) { this.row = row; }
|
||||||
|
public void SetRow(int row) { setRow(row); }
|
||||||
|
|
||||||
|
public int getCol() { return col; }
|
||||||
|
public int GetCol() { return col; }
|
||||||
|
public void setCol(int col) { this.col = col; }
|
||||||
|
public void SetCol(int col) { setCol(col); }
|
||||||
|
|
||||||
|
public int getPlane() { return plane; }
|
||||||
|
public int GetPlane() { return plane; }
|
||||||
|
public void setPlane(int plane) { this.plane = plane; }
|
||||||
|
public void SetPlane(int plane) { setPlane(plane); }
|
||||||
|
|
||||||
|
public static String PlaneToString(int plane) {
|
||||||
|
switch (plane) {
|
||||||
|
case TEXT_PLANE: return "TEXT_PLANE";
|
||||||
|
case COLOR_PLANE: return "COLOR_PLANE";
|
||||||
|
case DBCS_PLANE: return "DBCS_PLANE";
|
||||||
|
case GRID_PLANE: return "GRID_PLANE";
|
||||||
|
case FIELD_PLANE: return "FIELD_PLANE";
|
||||||
|
case EXFIELD_PLANE: return "EXFIELD_PLANE";
|
||||||
|
default: return "FIELD_PLANE";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int StringToPlane(String str) {
|
||||||
|
if (str == null) return FIELD_PLANE;
|
||||||
|
if (str.equalsIgnoreCase("TEXT_PLANE")) return TEXT_PLANE;
|
||||||
|
if (str.equalsIgnoreCase("COLOR_PLANE")) return COLOR_PLANE;
|
||||||
|
if (str.equalsIgnoreCase("DBCS_PLANE")) return DBCS_PLANE;
|
||||||
|
if (str.equalsIgnoreCase("GRID_PLANE")) return GRID_PLANE;
|
||||||
|
if (str.equalsIgnoreCase("FIELD_PLANE")) return FIELD_PLANE;
|
||||||
|
if (str.equalsIgnoreCase("EXFIELD_PLANE")) return EXFIELD_PLANE;
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(str);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return FIELD_PLANE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String AttribToString(char c) {
|
||||||
|
return "0x" + Integer.toHexString(c & 0xFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static char StringToAttrib(String str) {
|
||||||
|
if (str == null || str.isEmpty()) return 0;
|
||||||
|
str = str.trim();
|
||||||
|
int xIdx = str.toLowerCase().indexOf('x');
|
||||||
|
if (xIdx != -1) {
|
||||||
|
return (char) Integer.parseInt(str.substring(xIdx + 1), 16);
|
||||||
|
}
|
||||||
|
return (char) Integer.parseInt(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean checkMatch(ECLPS ps, ECLOIA oia) {
|
||||||
|
if (ps == null) return false;
|
||||||
|
int totalRows = ps.getRows();
|
||||||
|
int totalCols = ps.getCols();
|
||||||
|
|
||||||
|
if (row < 1 || row > totalRows || col < 1 || col > totalCols) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int addr = (row - 1) * totalCols + (col - 1);
|
||||||
|
char[] buf = new char[1];
|
||||||
|
ps.getPlane(plane, buf, addr, 1);
|
||||||
|
return buf[0] == attrib;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String format(int indent, boolean full) {
|
||||||
|
StringBuilder sb = new StringBuilder("<attrib ");
|
||||||
|
sb.append("value=\"").append(AttribToString(attrib)).append("\" ");
|
||||||
|
sb.append("row=\"").append(row).append("\" ");
|
||||||
|
sb.append("col=\"").append(col).append("\" ");
|
||||||
|
sb.append("plane=\"").append(PlaneToString(plane)).append("\" ");
|
||||||
|
return formatAttributes(sb.toString(), full);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
package haus.nightmare.lib3270j.ecl.screenreco;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import haus.nightmare.lib3270j.ecl.ECLOIA;
|
||||||
|
import haus.nightmare.lib3270j.ecl.ECLPS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Screen descriptor matching a rectangular block of text matching IBM Host On-Demand ECLSDBlock.
|
||||||
|
*/
|
||||||
|
public class ECLSDBlock extends ECLScreenDescriptor {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public static final int BLOCK_ABSOLUTE = 1;
|
||||||
|
public static final int BLOCK_INRECT = 2;
|
||||||
|
|
||||||
|
private int blockType = BLOCK_INRECT;
|
||||||
|
private int srow = 1;
|
||||||
|
private int scol = 1;
|
||||||
|
private int erow = -1;
|
||||||
|
private int ecol = -1;
|
||||||
|
private boolean caseSense = false;
|
||||||
|
private ECLSDBlockString[] strings = new ECLSDBlockString[0];
|
||||||
|
|
||||||
|
public ECLSDBlock() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDBlock(int srow, int scol, boolean caseSense, boolean optional) {
|
||||||
|
super(optional);
|
||||||
|
this.blockType = BLOCK_ABSOLUTE;
|
||||||
|
this.srow = srow;
|
||||||
|
this.scol = scol;
|
||||||
|
this.caseSense = caseSense;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDBlock(int srow, int scol, int erow, int ecol, boolean caseSense, boolean optional) {
|
||||||
|
super(optional);
|
||||||
|
this.blockType = BLOCK_INRECT;
|
||||||
|
this.srow = srow;
|
||||||
|
this.scol = scol;
|
||||||
|
this.erow = erow;
|
||||||
|
this.ecol = ecol;
|
||||||
|
this.caseSense = caseSense;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDBlock(String[] textLines, int srow, int scol, boolean caseSense) {
|
||||||
|
super(false);
|
||||||
|
this.blockType = BLOCK_ABSOLUTE;
|
||||||
|
this.srow = srow;
|
||||||
|
this.scol = scol;
|
||||||
|
this.caseSense = caseSense;
|
||||||
|
setStrings(textLines);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBlockType() { return blockType; }
|
||||||
|
public int GetBlockType() { return blockType; }
|
||||||
|
public void setBlockType(int blockType) { this.blockType = blockType; }
|
||||||
|
public void SetBlockType(int blockType) { setBlockType(blockType); }
|
||||||
|
|
||||||
|
public int getSRow() { return srow; }
|
||||||
|
public int GetSRow() { return srow; }
|
||||||
|
public void setSRow(int srow) { this.srow = srow; }
|
||||||
|
public void SetSRow(int srow) { setSRow(srow); }
|
||||||
|
|
||||||
|
public int getSCol() { return scol; }
|
||||||
|
public int GetSCol() { return scol; }
|
||||||
|
public void setSCol(int scol) { this.scol = scol; }
|
||||||
|
public void SetSCol(int scol) { setSCol(scol); }
|
||||||
|
|
||||||
|
public int getERow() { return erow; }
|
||||||
|
public int GetERow() { return erow; }
|
||||||
|
public void setERow(int erow) { this.erow = erow; }
|
||||||
|
public void SetERow(int erow) { setERow(erow); }
|
||||||
|
|
||||||
|
public int getECol() { return ecol; }
|
||||||
|
public int GetECol() { return ecol; }
|
||||||
|
public void setECol(int ecol) { this.ecol = ecol; }
|
||||||
|
public void SetECol(int ecol) { setECol(ecol); }
|
||||||
|
|
||||||
|
public boolean isCaseSense() { return caseSense; }
|
||||||
|
public boolean IsCaseSense() { return caseSense; }
|
||||||
|
public void setCaseSense(boolean caseSense) { this.caseSense = caseSense; }
|
||||||
|
public void SetCaseSense(boolean caseSense) { setCaseSense(caseSense); }
|
||||||
|
|
||||||
|
public String[] getStrings() {
|
||||||
|
String[] arr = new String[strings.length];
|
||||||
|
for (int i = 0; i < strings.length; i++) {
|
||||||
|
arr[i] = (strings[i] != null) ? strings[i].getText() : "";
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
public String[] GetStrings() { return getStrings(); }
|
||||||
|
|
||||||
|
public void setStrings(String[] textLines) {
|
||||||
|
if (textLines == null) {
|
||||||
|
this.strings = new ECLSDBlockString[0];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.strings = new ECLSDBlockString[textLines.length];
|
||||||
|
for (int i = 0; i < textLines.length; i++) {
|
||||||
|
this.strings[i] = new ECLSDBlockString(textLines[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void SetStrings(String[] textLines) { setStrings(textLines); }
|
||||||
|
|
||||||
|
public void setBlockStrings(ECLSDBlockString[] blockStrings) {
|
||||||
|
this.strings = (blockStrings != null) ? blockStrings : new ECLSDBlockString[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addString(String text) {
|
||||||
|
ECLSDBlockString[] newArr = new ECLSDBlockString[strings.length + 1];
|
||||||
|
System.arraycopy(strings, 0, newArr, 0, strings.length);
|
||||||
|
newArr[strings.length] = new ECLSDBlockString(text);
|
||||||
|
this.strings = newArr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean checkMatch(ECLPS ps, ECLOIA oia) {
|
||||||
|
if (ps == null) return false;
|
||||||
|
if (strings == null || strings.length == 0) return true;
|
||||||
|
|
||||||
|
int totalRows = ps.getRows();
|
||||||
|
int totalCols = ps.getCols();
|
||||||
|
String[] lines = getStrings();
|
||||||
|
|
||||||
|
// 1. Absolute matching at (srow, scol)
|
||||||
|
if (blockType == BLOCK_ABSOLUTE || (erow <= 0 && ecol <= 0 && srow > 0 && scol > 0)) {
|
||||||
|
for (int i = 0; i < lines.length; i++) {
|
||||||
|
int r0 = srow + i - 1;
|
||||||
|
int c0 = scol - 1;
|
||||||
|
if (r0 < 0 || r0 >= totalRows || c0 < 0 || c0 >= totalCols) return false;
|
||||||
|
String target = lines[i];
|
||||||
|
String onScreen = ps.getString(r0, c0, target.length());
|
||||||
|
if (onScreen == null) return false;
|
||||||
|
boolean eq = caseSense ? target.equals(onScreen) : target.equalsIgnoreCase(onScreen);
|
||||||
|
if (!eq) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Rectangular region matching
|
||||||
|
int sr = (srow > 0) ? srow : 1;
|
||||||
|
int sc = (scol > 0) ? scol : 1;
|
||||||
|
int er = (erow > 0) ? Math.min(erow, totalRows) : totalRows;
|
||||||
|
int ec = (ecol > 0) ? Math.min(ecol, totalCols) : totalCols;
|
||||||
|
|
||||||
|
if (sr > er || sc > ec) return false;
|
||||||
|
int numLines = lines.length;
|
||||||
|
if (er - sr + 1 < numLines) return false;
|
||||||
|
|
||||||
|
int rectWidth = ec - sc + 1;
|
||||||
|
List<String> screenLines = new ArrayList<>();
|
||||||
|
for (int r = sr; r <= er; r++) {
|
||||||
|
String s = ps.getString(r - 1, sc - 1, rectWidth);
|
||||||
|
screenLines.add(s != null ? s : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
String firstTarget = caseSense ? lines[0] : lines[0].toLowerCase();
|
||||||
|
for (int i = 0; i <= screenLines.size() - numLines; i++) {
|
||||||
|
String rowStr = caseSense ? screenLines.get(i) : screenLines.get(i).toLowerCase();
|
||||||
|
int matchIdx = rowStr.indexOf(firstTarget);
|
||||||
|
while (matchIdx != -1) {
|
||||||
|
boolean allMatch = true;
|
||||||
|
for (int j = 1; j < numLines; j++) {
|
||||||
|
String nextRowStr = caseSense ? screenLines.get(i + j) : screenLines.get(i + j).toLowerCase();
|
||||||
|
String nextTarget = caseSense ? lines[j] : lines[j].toLowerCase();
|
||||||
|
if (matchIdx + nextTarget.length() > nextRowStr.length() ||
|
||||||
|
!nextRowStr.startsWith(nextTarget, matchIdx)) {
|
||||||
|
allMatch = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (allMatch) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
matchIdx = rowStr.indexOf(firstTarget, matchIdx + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String format(int indent, boolean full) {
|
||||||
|
StringBuilder sb = new StringBuilder("<block ");
|
||||||
|
if (srow > 0 || full) sb.append("row=\"").append(srow).append("\" ");
|
||||||
|
if (scol > 0 || full) sb.append("col=\"").append(scol).append("\" ");
|
||||||
|
if (blockType == BLOCK_INRECT && (erow > 0 || ecol > 0 || full)) {
|
||||||
|
if (erow > 0 || full) sb.append("erow=\"").append(erow).append("\" ");
|
||||||
|
if (ecol > 0 || full) sb.append("ecol=\"").append(ecol).append("\" ");
|
||||||
|
}
|
||||||
|
if (caseSense || full) sb.append("casesense=\"").append(caseSense).append("\" ");
|
||||||
|
if (optional || full) sb.append("optional=\"").append(optional).append("\" ");
|
||||||
|
if (invert || full) sb.append("invertmatch=\"").append(invert).append("\" ");
|
||||||
|
if (hostid != null && !hostid.isEmpty()) sb.append("hostid=\"").append(escapeChars(hostid)).append("\" ");
|
||||||
|
sb.append(">\n");
|
||||||
|
|
||||||
|
for (ECLSDBlockString s : strings) {
|
||||||
|
sb.append(" ").append(s.format(indent + 1, full)).append("\n");
|
||||||
|
}
|
||||||
|
sb.append("</block>");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package haus.nightmare.lib3270j.ecl.screenreco;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulates an individual string line within an ECLSDBlock matching IBM Host On-Demand.
|
||||||
|
*/
|
||||||
|
public class ECLSDBlockString implements Serializable, Cloneable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private String text = "";
|
||||||
|
|
||||||
|
public ECLSDBlockString() {}
|
||||||
|
|
||||||
|
public ECLSDBlockString(String text) {
|
||||||
|
this.text = (text != null) ? text : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText() { return text; }
|
||||||
|
public String GetText() { return text; }
|
||||||
|
public String GetString() { return text; }
|
||||||
|
public String getString() { return text; }
|
||||||
|
public void setText(String text) { this.text = (text != null) ? text : ""; }
|
||||||
|
public void SetText(String text) { setText(text); }
|
||||||
|
public void SetString(String text) { setText(text); }
|
||||||
|
public void setString(String text) { setText(text); }
|
||||||
|
|
||||||
|
public String format(int indent, boolean full) {
|
||||||
|
return "<string value=\"" + ECLScreenDescriptor.escapeChars(text) + "\" />";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object clone() {
|
||||||
|
try {
|
||||||
|
return super.clone();
|
||||||
|
} catch (CloneNotSupportedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package haus.nightmare.lib3270j.ecl.screenreco;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.ecl.ECLField;
|
||||||
|
import haus.nightmare.lib3270j.ecl.ECLFieldList;
|
||||||
|
import haus.nightmare.lib3270j.ecl.ECLOIA;
|
||||||
|
import haus.nightmare.lib3270j.ecl.ECLPS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Screen descriptor matching screen buffer checksum matching IBM Host On-Demand ECLSDChecksum.
|
||||||
|
*/
|
||||||
|
public class ECLSDChecksum extends ECLScreenDescriptor {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private int value = 0;
|
||||||
|
|
||||||
|
public ECLSDChecksum() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDChecksum(int value) {
|
||||||
|
this(value, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDChecksum(int value, boolean optional) {
|
||||||
|
super(optional);
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getValue() { return value; }
|
||||||
|
public int GetValue() { return value; }
|
||||||
|
public void setValue(int value) { this.value = value; }
|
||||||
|
public void SetValue(int value) { setValue(value); }
|
||||||
|
|
||||||
|
public static int CalculateChecksum(ECLFieldList fieldList, int psSize) {
|
||||||
|
int sum = 0;
|
||||||
|
if (fieldList != null && fieldList.getFieldCount() > 0) {
|
||||||
|
int count = fieldList.getFieldCount();
|
||||||
|
sum = 17;
|
||||||
|
ECLField current = null;
|
||||||
|
for (int i = 0; i < count - 1; i++) {
|
||||||
|
if (i == 0) {
|
||||||
|
current = fieldList.getFirstField();
|
||||||
|
}
|
||||||
|
ECLField next = fieldList.getNextField(current);
|
||||||
|
if (current != null && next != null) {
|
||||||
|
int delta = next.getStart() - current.getStart() - 1;
|
||||||
|
sum = sum * 37 + delta;
|
||||||
|
}
|
||||||
|
current = next;
|
||||||
|
}
|
||||||
|
ECLField last = (current == null) ? fieldList.getFirstField() : current;
|
||||||
|
int endDelta = (last != null) ? (psSize - last.getStart()) : 0;
|
||||||
|
sum = sum * 37 + endDelta;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean checkMatch(ECLPS ps, ECLOIA oia) {
|
||||||
|
if (ps == null) return false;
|
||||||
|
int calc = CalculateChecksum(ps.getFieldList(), ps.getSize());
|
||||||
|
return calc == value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String format(int indent, boolean full) {
|
||||||
|
StringBuilder sb = new StringBuilder("<checksum ");
|
||||||
|
sb.append("value=\"").append(value).append("\" ");
|
||||||
|
return formatAttributes(sb.toString(), full);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package haus.nightmare.lib3270j.ecl.screenreco;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.ecl.ECLOIA;
|
||||||
|
import haus.nightmare.lib3270j.ecl.ECLPS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Screen descriptor matching cursor position matching IBM Host On-Demand ECLSDCursor.
|
||||||
|
*/
|
||||||
|
public class ECLSDCursor extends ECLScreenDescriptor {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private int row = 1;
|
||||||
|
private int col = 1;
|
||||||
|
private int pos = -1;
|
||||||
|
|
||||||
|
public ECLSDCursor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDCursor(int pos) {
|
||||||
|
this(pos, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDCursor(int pos, boolean optional) {
|
||||||
|
super(optional);
|
||||||
|
this.pos = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDCursor(int row, int col) {
|
||||||
|
this(row, col, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLSDCursor(int row, int col, boolean optional) {
|
||||||
|
super(optional);
|
||||||
|
this.row = row;
|
||||||
|
this.col = col;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPos() { return pos; }
|
||||||
|
public int GetPos() { return pos; }
|
||||||
|
public void setPos(int pos) { this.pos = pos; }
|
||||||
|
public void SetPos(int pos) { setPos(pos); }
|
||||||
|
|
||||||
|
public int getRow() { return row; }
|
||||||
|
public int GetRow() { return row; }
|
||||||
|
public void setRow(int row) { this.row = row; }
|
||||||
|
public void SetRow(int row) { setRow(row); }
|
||||||
|
|
||||||
|
public int getCol() { return col; }
|
||||||
|
public int GetCol() { return col; }
|
||||||
|
public void setCol(int col) { this.col = col; }
|
||||||
|
public void SetCol(int col) { setCol(col); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean checkMatch(ECLPS ps, ECLOIA oia) {
|
||||||
|
if (ps == null) return false;
|
||||||
|
if (pos > 0) {
|
||||||
|
return (ps.getCursorPos() + 1) == pos;
|
||||||
|
}
|
||||||
|
// In ECLPS, getCursorRow() and getCursorCol() return 0-based indices.
|
||||||
|
int curRow = ps.getCursorRow() + 1;
|
||||||
|
int curCol = ps.getCursorCol() + 1;
|
||||||
|
return (curRow == row && curCol == col);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String format(int indent, boolean full) {
|
||||||
|
StringBuilder sb = new StringBuilder("<cursor ");
|
||||||
|
if (pos > 0) {
|
||||||
|
sb.append("position=\"").append(pos).append("\" ");
|
||||||
|
} else {
|
||||||
|
sb.append("row=\"").append(row).append("\" ");
|
||||||
|
sb.append("col=\"").append(col).append("\" ");
|
||||||
|
}
|
||||||
|
return formatAttributes(sb.toString(), full);
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user