This commit is contained in:
@@ -92,6 +92,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
private void buildUI() {
|
||||
terminalPanel = new TerminalPanel();
|
||||
statusBar = new StatusBar();
|
||||
terminalPanel.setStatusBar(statusBar);
|
||||
|
||||
getContentPane().setLayout(new BorderLayout());
|
||||
getContentPane().setBackground(Color.BLACK);
|
||||
@@ -259,7 +260,89 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
viewMenu.add(createMenuItem("Field Inspector...", -1, this::showFieldInspectorDialog));
|
||||
menuBar.add(viewMenu);
|
||||
|
||||
// 4. Actions menu
|
||||
// 4. Modes menu
|
||||
JMenu modesMenu = createMenu("Modes");
|
||||
JCheckBoxMenuItem docModeItem = new JCheckBoxMenuItem("Document Mode (DOC)", terminalPanel.isDocMode());
|
||||
ThemeManager.styleMenuItem(docModeItem);
|
||||
docModeItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F1, InputEvent.ALT_DOWN_MASK));
|
||||
docModeItem.addActionListener(e -> terminalPanel.toggleDocMode());
|
||||
modesMenu.add(docModeItem);
|
||||
|
||||
JCheckBoxMenuItem wordWrapItem = new JCheckBoxMenuItem("Word Wrap Mode", terminalPanel.isWordWrap());
|
||||
ThemeManager.styleMenuItem(wordWrapItem);
|
||||
wordWrapItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F2, InputEvent.ALT_DOWN_MASK));
|
||||
wordWrapItem.addActionListener(e -> terminalPanel.toggleWordWrap());
|
||||
modesMenu.add(wordWrapItem);
|
||||
|
||||
JCheckBoxMenuItem aplModeItem = new JCheckBoxMenuItem("APL Keyboard Mode", terminalPanel.isAplMode());
|
||||
ThemeManager.styleMenuItem(aplModeItem);
|
||||
aplModeItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F3, InputEvent.ALT_DOWN_MASK));
|
||||
aplModeItem.addActionListener(e -> terminalPanel.toggleAplMode());
|
||||
modesMenu.add(aplModeItem);
|
||||
|
||||
JCheckBoxMenuItem insertModeItem = new JCheckBoxMenuItem("Insert Mode", client != null && client.isInsertMode());
|
||||
ThemeManager.styleMenuItem(insertModeItem);
|
||||
insertModeItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, 0));
|
||||
insertModeItem.addActionListener(e -> {
|
||||
if (client != null) {
|
||||
client.toggleInsert();
|
||||
terminalPanel.refreshScreen();
|
||||
}
|
||||
});
|
||||
modesMenu.add(insertModeItem);
|
||||
|
||||
modesMenu.addSeparator();
|
||||
|
||||
JCheckBoxMenuItem fourColorItem = new JCheckBoxMenuItem("Base 4-Color Override", haus.nightmare.j3270.config.Settings.getFourColorOverride());
|
||||
ThemeManager.styleMenuItem(fourColorItem);
|
||||
fourColorItem.addActionListener(e -> {
|
||||
haus.nightmare.j3270.config.Settings.setFourColorOverride(fourColorItem.isSelected());
|
||||
terminalPanel.repaint();
|
||||
});
|
||||
modesMenu.add(fourColorItem);
|
||||
|
||||
JCheckBoxMenuItem numLockItem = new JCheckBoxMenuItem("Numeric Field Lock", haus.nightmare.j3270.config.Settings.getNumericFieldLock());
|
||||
ThemeManager.styleMenuItem(numLockItem);
|
||||
numLockItem.addActionListener(e -> {
|
||||
haus.nightmare.j3270.config.Settings.setNumericFieldLock(numLockItem.isSelected());
|
||||
terminalPanel.applyModeSettings();
|
||||
});
|
||||
modesMenu.add(numLockItem);
|
||||
|
||||
JCheckBoxMenuItem autoSkipItem = new JCheckBoxMenuItem("Auto-Skip Across Fields", haus.nightmare.j3270.config.Settings.getAutoSkipEnabled());
|
||||
ThemeManager.styleMenuItem(autoSkipItem);
|
||||
autoSkipItem.addActionListener(e -> {
|
||||
haus.nightmare.j3270.config.Settings.setAutoSkipEnabled(autoSkipItem.isSelected());
|
||||
terminalPanel.applyModeSettings();
|
||||
});
|
||||
modesMenu.add(autoSkipItem);
|
||||
|
||||
JCheckBoxMenuItem insertOffAidItem = new JCheckBoxMenuItem("Reset Insert on AID Key", haus.nightmare.j3270.config.Settings.getInsertOffOnAid());
|
||||
ThemeManager.styleMenuItem(insertOffAidItem);
|
||||
insertOffAidItem.addActionListener(e -> {
|
||||
haus.nightmare.j3270.config.Settings.setInsertOffOnAid(insertOffAidItem.isSelected());
|
||||
terminalPanel.applyModeSettings();
|
||||
});
|
||||
modesMenu.add(insertOffAidItem);
|
||||
|
||||
modesMenu.addMenuListener(new javax.swing.event.MenuListener() {
|
||||
@Override
|
||||
public void menuSelected(javax.swing.event.MenuEvent e) {
|
||||
docModeItem.setSelected(terminalPanel.isDocMode());
|
||||
wordWrapItem.setSelected(terminalPanel.isWordWrap());
|
||||
aplModeItem.setSelected(terminalPanel.isAplMode());
|
||||
insertModeItem.setSelected(client != null && client.isInsertMode());
|
||||
fourColorItem.setSelected(haus.nightmare.j3270.config.Settings.getFourColorOverride());
|
||||
numLockItem.setSelected(haus.nightmare.j3270.config.Settings.getNumericFieldLock());
|
||||
autoSkipItem.setSelected(haus.nightmare.j3270.config.Settings.getAutoSkipEnabled());
|
||||
insertOffAidItem.setSelected(haus.nightmare.j3270.config.Settings.getInsertOffOnAid());
|
||||
}
|
||||
@Override public void menuDeselected(javax.swing.event.MenuEvent e) {}
|
||||
@Override public void menuCanceled(javax.swing.event.MenuEvent e) {}
|
||||
});
|
||||
menuBar.add(modesMenu);
|
||||
|
||||
// 5. Actions menu
|
||||
JMenu actionsMenu = createMenu("Actions");
|
||||
actionsMenu.add(createMenuItem("Send Enter", KeyEvent.VK_ENTER, () -> {
|
||||
if (client != null && client.getConnectionState().isFullSession()) {
|
||||
|
||||
@@ -33,6 +33,7 @@ public class Settings {
|
||||
|
||||
public static void setFontFamily(String family) {
|
||||
prefs.put("fontFamily", family);
|
||||
flushPrefs();
|
||||
}
|
||||
|
||||
public static int getFontSize() {
|
||||
@@ -41,6 +42,14 @@ public class Settings {
|
||||
|
||||
public static void setFontSize(int size) {
|
||||
prefs.putInt("fontSize", size);
|
||||
flushPrefs();
|
||||
}
|
||||
|
||||
private static void flushPrefs() {
|
||||
try {
|
||||
prefs.flush();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public static StartupBehavior getStartupBehavior() {
|
||||
@@ -269,6 +278,101 @@ public class Settings {
|
||||
prefs.put("cursorStyle", style != null ? style.toUpperCase() : "BLOCK");
|
||||
}
|
||||
|
||||
// ========== Entry Assist & Modes ==========
|
||||
|
||||
public static boolean getEntryAssistDocMode() {
|
||||
return prefs.getBoolean("entryAssistDocMode", false);
|
||||
}
|
||||
public static void setEntryAssistDocMode(boolean docMode) {
|
||||
prefs.putBoolean("entryAssistDocMode", docMode);
|
||||
}
|
||||
|
||||
public static boolean getEntryAssistWordWrap() {
|
||||
return prefs.getBoolean("entryAssistWordWrap", false);
|
||||
}
|
||||
public static void setEntryAssistWordWrap(boolean wordWrap) {
|
||||
prefs.putBoolean("entryAssistWordWrap", wordWrap);
|
||||
}
|
||||
|
||||
public static int getEntryAssistStartCol() {
|
||||
return prefs.getInt("entryAssistStartCol", 1);
|
||||
}
|
||||
public static void setEntryAssistStartCol(int startCol) {
|
||||
prefs.putInt("entryAssistStartCol", startCol);
|
||||
}
|
||||
|
||||
public static int getEntryAssistEndCol() {
|
||||
return prefs.getInt("entryAssistEndCol", 80);
|
||||
}
|
||||
public static void setEntryAssistEndCol(int endCol) {
|
||||
prefs.putInt("entryAssistEndCol", endCol);
|
||||
}
|
||||
|
||||
public static boolean getEntryAssistBell() {
|
||||
return prefs.getBoolean("entryAssistBell", false);
|
||||
}
|
||||
public static void setEntryAssistBell(boolean bell) {
|
||||
prefs.putBoolean("entryAssistBell", bell);
|
||||
}
|
||||
|
||||
public static int getEntryAssistBellCol() {
|
||||
return prefs.getInt("entryAssistBellCol", 75);
|
||||
}
|
||||
public static void setEntryAssistBellCol(int col) {
|
||||
prefs.putInt("entryAssistBellCol", col);
|
||||
}
|
||||
|
||||
public static String getEntryAssistTabStops() {
|
||||
return prefs.get("entryAssistTabStops", "5,10,15,20,25,30,35,40,45,50,55,60,65,70,75");
|
||||
}
|
||||
public static void setEntryAssistTabStops(String stops) {
|
||||
prefs.put("entryAssistTabStops", stops != null ? stops : "");
|
||||
}
|
||||
|
||||
public static int[] getEntryAssistTabStopsArray() {
|
||||
String s = getEntryAssistTabStops();
|
||||
if (s == null || s.trim().isEmpty()) return new int[0];
|
||||
String[] parts = s.split(",");
|
||||
java.util.List<Integer> list = new java.util.ArrayList<>();
|
||||
for (String p : parts) {
|
||||
try {
|
||||
int val = Integer.parseInt(p.trim());
|
||||
if (val > 0) list.add(val - 1);
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
int[] res = new int[list.size()];
|
||||
for (int i = 0; i < list.size(); i++) res[i] = list.get(i);
|
||||
return res;
|
||||
}
|
||||
|
||||
public static boolean getInsertOffOnAid() {
|
||||
return prefs.getBoolean("insertOffOnAid", true);
|
||||
}
|
||||
public static void setInsertOffOnAid(boolean val) {
|
||||
prefs.putBoolean("insertOffOnAid", val);
|
||||
}
|
||||
|
||||
public static boolean getFourColorOverride() {
|
||||
return prefs.getBoolean("fourColorOverride", false);
|
||||
}
|
||||
public static void setFourColorOverride(boolean val) {
|
||||
prefs.putBoolean("fourColorOverride", val);
|
||||
}
|
||||
|
||||
public static boolean getNumericFieldLock() {
|
||||
return prefs.getBoolean("numericFieldLock", true);
|
||||
}
|
||||
public static void setNumericFieldLock(boolean val) {
|
||||
prefs.putBoolean("numericFieldLock", val);
|
||||
}
|
||||
|
||||
public static boolean getAutoSkipEnabled() {
|
||||
return prefs.getBoolean("autoSkipEnabled", true);
|
||||
}
|
||||
public static void setAutoSkipEnabled(boolean val) {
|
||||
prefs.putBoolean("autoSkipEnabled", val);
|
||||
}
|
||||
|
||||
private static void applyConfigEntry(String section, String key, String value) {
|
||||
switch (section) {
|
||||
case "appearance":
|
||||
@@ -365,6 +469,55 @@ public class Settings {
|
||||
}
|
||||
break;
|
||||
|
||||
case "entryassist":
|
||||
case "modes":
|
||||
switch (key) {
|
||||
case "docMode":
|
||||
case "entryAssistDocMode":
|
||||
setEntryAssistDocMode(Boolean.parseBoolean(value));
|
||||
break;
|
||||
case "wordWrap":
|
||||
case "entryAssistWordWrap":
|
||||
setEntryAssistWordWrap(Boolean.parseBoolean(value));
|
||||
break;
|
||||
case "startCol":
|
||||
case "entryAssistStartCol":
|
||||
setEntryAssistStartCol(Integer.parseInt(value));
|
||||
break;
|
||||
case "endCol":
|
||||
case "entryAssistEndCol":
|
||||
setEntryAssistEndCol(Integer.parseInt(value));
|
||||
break;
|
||||
case "bell":
|
||||
case "entryAssistBell":
|
||||
setEntryAssistBell(Boolean.parseBoolean(value));
|
||||
break;
|
||||
case "bellCol":
|
||||
case "entryAssistBellCol":
|
||||
setEntryAssistBellCol(Integer.parseInt(value));
|
||||
break;
|
||||
case "tabStops":
|
||||
case "entryAssistTabStops":
|
||||
setEntryAssistTabStops(value);
|
||||
break;
|
||||
case "insertOffOnAid":
|
||||
setInsertOffOnAid(Boolean.parseBoolean(value));
|
||||
break;
|
||||
case "fourColorOverride":
|
||||
setFourColorOverride(Boolean.parseBoolean(value));
|
||||
break;
|
||||
case "numericFieldLock":
|
||||
setNumericFieldLock(Boolean.parseBoolean(value));
|
||||
break;
|
||||
case "autoSkip":
|
||||
case "autoSkipEnabled":
|
||||
setAutoSkipEnabled(Boolean.parseBoolean(value));
|
||||
break;
|
||||
default:
|
||||
log.warning("Unknown entryassist/modes key: " + key);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Allow bare keys outside any section — treat as raw prefs
|
||||
log.fine("Setting raw preference: " + key + " = " + value);
|
||||
@@ -412,6 +565,25 @@ public class Settings {
|
||||
w.println("blockSelectMode = " + getBlockSelectMode());
|
||||
w.println();
|
||||
|
||||
// [entryassist]
|
||||
w.println("[entryassist]");
|
||||
w.println("docMode = " + getEntryAssistDocMode());
|
||||
w.println("wordWrap = " + getEntryAssistWordWrap());
|
||||
w.println("startCol = " + getEntryAssistStartCol());
|
||||
w.println("endCol = " + getEntryAssistEndCol());
|
||||
w.println("bell = " + getEntryAssistBell());
|
||||
w.println("bellCol = " + getEntryAssistBellCol());
|
||||
w.println("tabStops = " + getEntryAssistTabStops());
|
||||
w.println();
|
||||
|
||||
// [modes]
|
||||
w.println("[modes]");
|
||||
w.println("insertOffOnAid = " + getInsertOffOnAid());
|
||||
w.println("fourColorOverride = " + getFourColorOverride());
|
||||
w.println("numericFieldLock = " + getNumericFieldLock());
|
||||
w.println("autoSkip = " + getAutoSkipEnabled());
|
||||
w.println();
|
||||
|
||||
// [graphics]
|
||||
w.println("[graphics]");
|
||||
w.println("graphicsMode = " + getGraphicsMode().name());
|
||||
|
||||
@@ -12,6 +12,50 @@ import java.io.File;
|
||||
|
||||
public class FileTransferDialog extends JDialog {
|
||||
|
||||
public static class TransferSessionState {
|
||||
public FTConfig.HostType hostType = FTConfig.HostType.TSO;
|
||||
public boolean isSend = false;
|
||||
public String localFile = "";
|
||||
public String hostFile = "";
|
||||
public boolean isAscii = true;
|
||||
public int mtu = FTConstants.DFT_BUF;
|
||||
public boolean crFlag = true;
|
||||
public boolean remapFlag = true;
|
||||
public boolean append = false;
|
||||
public boolean overwrite = false;
|
||||
public String recfm = "";
|
||||
public String lrecl = "";
|
||||
public String blksize = "";
|
||||
public String space = "";
|
||||
public String options = "";
|
||||
}
|
||||
|
||||
private static TransferSessionState lastTransferState = null;
|
||||
|
||||
public static TransferSessionState getLastTransferState() {
|
||||
return lastTransferState;
|
||||
}
|
||||
|
||||
public static void setLastTransferState(TransferSessionState state) {
|
||||
lastTransferState = state;
|
||||
}
|
||||
|
||||
public static void resetSessionState() {
|
||||
lastTransferState = null;
|
||||
}
|
||||
|
||||
public static String formatHostFilename(String localPath, FTConfig.HostType hostType) {
|
||||
if (localPath == null || localPath.trim().isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
File f = new File(localPath.trim());
|
||||
String name = f.getName();
|
||||
if (hostType == FTConfig.HostType.CMS) {
|
||||
name = name.replace('.', ' ');
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private final FileTransfer coordinator;
|
||||
private final Frame owner;
|
||||
|
||||
@@ -66,6 +110,16 @@ public class FileTransferDialog extends JDialog {
|
||||
|
||||
// Host Type
|
||||
hostTypeCombo = new JComboBox<>(FTConfig.HostType.values());
|
||||
hostTypeCombo.addActionListener(e -> {
|
||||
updateOptionStates();
|
||||
FTConfig.HostType hostType = (FTConfig.HostType) hostTypeCombo.getSelectedItem();
|
||||
if (hostType == FTConfig.HostType.CMS) {
|
||||
String hostText = hostFileField.getText().trim();
|
||||
if (hostText.contains(".")) {
|
||||
hostFileField.setText(hostText.replace('.', ' '));
|
||||
}
|
||||
}
|
||||
});
|
||||
formPanel.add(createRow("Host Environment:", hostTypeCombo));
|
||||
|
||||
// Direction
|
||||
@@ -88,6 +142,15 @@ public class FileTransferDialog extends JDialog {
|
||||
|
||||
// Local File
|
||||
localFileField = new JTextField(20);
|
||||
localFileField.addFocusListener(new java.awt.event.FocusAdapter() {
|
||||
@Override
|
||||
public void focusLost(java.awt.event.FocusEvent e) {
|
||||
if (hostFileField.getText().trim().isEmpty() && !localFileField.getText().trim().isEmpty()) {
|
||||
FTConfig.HostType hostType = (FTConfig.HostType) hostTypeCombo.getSelectedItem();
|
||||
hostFileField.setText(formatHostFilename(localFileField.getText(), hostType));
|
||||
}
|
||||
}
|
||||
});
|
||||
browseLocalButton = new JButton("Browse...");
|
||||
ThemeManager.styleButton(browseLocalButton, ThemeManager.ButtonVariant.DEFAULT);
|
||||
browseLocalButton.addActionListener(e -> browseLocalFile());
|
||||
@@ -211,13 +274,47 @@ public class FileTransferDialog extends JDialog {
|
||||
|
||||
setContentPane(mainPanel);
|
||||
|
||||
prefillFromLastTransfer();
|
||||
ThemeManager.applyThemeToWindow(this);
|
||||
updateOptionStates();
|
||||
}
|
||||
|
||||
private void prefillFromLastTransfer() {
|
||||
if (lastTransferState != null) {
|
||||
if (lastTransferState.hostType != null) {
|
||||
hostTypeCombo.setSelectedItem(lastTransferState.hostType);
|
||||
}
|
||||
sendRadio.setSelected(lastTransferState.isSend);
|
||||
receiveRadio.setSelected(!lastTransferState.isSend);
|
||||
if (lastTransferState.localFile != null) {
|
||||
localFileField.setText(lastTransferState.localFile);
|
||||
}
|
||||
if (lastTransferState.hostFile != null) {
|
||||
hostFileField.setText(lastTransferState.hostFile);
|
||||
}
|
||||
asciiRadio.setSelected(lastTransferState.isAscii);
|
||||
binaryRadio.setSelected(!lastTransferState.isAscii);
|
||||
if (lastTransferState.mtu > 0) {
|
||||
mtuCombo.setSelectedItem(lastTransferState.mtu);
|
||||
}
|
||||
crCheck.setSelected(lastTransferState.crFlag);
|
||||
remapCheck.setSelected(lastTransferState.remapFlag);
|
||||
appendCheck.setSelected(lastTransferState.append);
|
||||
overwriteCheck.setSelected(lastTransferState.overwrite);
|
||||
if (lastTransferState.recfm != null) recfmField.setText(lastTransferState.recfm);
|
||||
if (lastTransferState.lrecl != null) lreclField.setText(lastTransferState.lrecl);
|
||||
if (lastTransferState.blksize != null) blksizeField.setText(lastTransferState.blksize);
|
||||
if (lastTransferState.space != null) spaceField.setText(lastTransferState.space);
|
||||
if (lastTransferState.options != null) optionsField.setText(lastTransferState.options);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateOptionStates() {
|
||||
boolean isSend = sendRadio.isSelected();
|
||||
boolean isAscii = asciiRadio.isSelected();
|
||||
FTConfig.HostType hostType = (FTConfig.HostType) hostTypeCombo.getSelectedItem();
|
||||
boolean isTso = (hostType == FTConfig.HostType.TSO);
|
||||
boolean isCms = (hostType == FTConfig.HostType.CMS);
|
||||
|
||||
appendCheck.setEnabled(!isSend);
|
||||
overwriteCheck.setEnabled(!isSend);
|
||||
@@ -225,10 +322,11 @@ public class FileTransferDialog extends JDialog {
|
||||
crCheck.setEnabled(isAscii);
|
||||
remapCheck.setEnabled(isAscii);
|
||||
|
||||
recfmField.setEnabled(isSend);
|
||||
lreclField.setEnabled(isSend);
|
||||
blksizeField.setEnabled(isSend);
|
||||
spaceField.setEnabled(isSend);
|
||||
recfmField.setEnabled(isSend && isTso);
|
||||
lreclField.setEnabled(isSend && isTso);
|
||||
blksizeField.setEnabled(isSend && isTso);
|
||||
spaceField.setEnabled(isSend && isTso);
|
||||
optionsField.setEnabled(isCms);
|
||||
}
|
||||
|
||||
private JPanel createRow(String labelText, Component comp) {
|
||||
@@ -245,9 +343,13 @@ public class FileTransferDialog extends JDialog {
|
||||
private void browseLocalFile() {
|
||||
JFileChooser chooser = new JFileChooser();
|
||||
if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
|
||||
localFileField.setText(chooser.getSelectedFile().getAbsolutePath());
|
||||
if (hostFileField.getText().trim().isEmpty()) {
|
||||
hostFileField.setText(chooser.getSelectedFile().getName());
|
||||
File selected = chooser.getSelectedFile();
|
||||
localFileField.setText(selected.getAbsolutePath());
|
||||
FTConfig.HostType hostType = (FTConfig.HostType) hostTypeCombo.getSelectedItem();
|
||||
boolean shouldAutofill = hostFileField.getText().trim().isEmpty() ||
|
||||
(lastTransferState != null && hostFileField.getText().trim().equals(lastTransferState.hostFile));
|
||||
if (shouldAutofill) {
|
||||
hostFileField.setText(formatHostFilename(selected.getName(), hostType));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,10 +362,21 @@ public class FileTransferDialog extends JDialog {
|
||||
|
||||
if (dialog.isConfirmed() && dialog.getSelectedHostFile() != null) {
|
||||
hostFileField.setText(dialog.getSelectedHostFile());
|
||||
if (localFileField.getText().trim().isEmpty()) {
|
||||
boolean shouldAutofill = localFileField.getText().trim().isEmpty() ||
|
||||
(lastTransferState != null && localFileField.getText().trim().equals(lastTransferState.localFile));
|
||||
if (shouldAutofill) {
|
||||
String cleanName = dialog.getSelectedHostFile().replace("'", "").replace("\"", "");
|
||||
int lastDot = cleanName.lastIndexOf('.');
|
||||
if (lastDot > 0) cleanName = cleanName.substring(lastDot + 1);
|
||||
if (hostType == FTConfig.HostType.CMS) {
|
||||
String[] tokens = cleanName.trim().split("\\s+");
|
||||
if (tokens.length >= 2) {
|
||||
cleanName = tokens[0].toLowerCase() + "." + tokens[1].toLowerCase();
|
||||
} else if (tokens.length == 1) {
|
||||
cleanName = tokens[0].toLowerCase();
|
||||
}
|
||||
} else {
|
||||
int lastDot = cleanName.lastIndexOf('.');
|
||||
if (lastDot > 0) cleanName = cleanName.substring(lastDot + 1);
|
||||
}
|
||||
localFileField.setText(cleanName);
|
||||
}
|
||||
}
|
||||
@@ -298,6 +411,8 @@ public class FileTransferDialog extends JDialog {
|
||||
config.setSpace(spaceField.getText().trim());
|
||||
config.setOptions(optionsField.getText().trim());
|
||||
|
||||
recordSessionTransfer();
|
||||
|
||||
String error = coordinator.startTransfer(config);
|
||||
if (error != null) {
|
||||
JOptionPane.showMessageDialog(this, error, "Transfer Error", JOptionPane.ERROR_MESSAGE);
|
||||
@@ -306,6 +421,45 @@ public class FileTransferDialog extends JDialog {
|
||||
}
|
||||
}
|
||||
|
||||
public void recordSessionTransfer() {
|
||||
TransferSessionState state = new TransferSessionState();
|
||||
state.hostType = (FTConfig.HostType) hostTypeCombo.getSelectedItem();
|
||||
state.isSend = sendRadio.isSelected();
|
||||
state.localFile = localFileField.getText().trim();
|
||||
state.hostFile = hostFileField.getText().trim();
|
||||
state.isAscii = asciiRadio.isSelected();
|
||||
Integer mtu = (Integer) mtuCombo.getSelectedItem();
|
||||
state.mtu = (mtu != null) ? mtu : FTConstants.DFT_BUF;
|
||||
state.crFlag = crCheck.isSelected();
|
||||
state.remapFlag = remapCheck.isSelected();
|
||||
state.append = appendCheck.isSelected();
|
||||
state.overwrite = overwriteCheck.isSelected();
|
||||
state.recfm = recfmField.getText().trim();
|
||||
state.lrecl = lreclField.getText().trim();
|
||||
state.blksize = blksizeField.getText().trim();
|
||||
state.space = spaceField.getText().trim();
|
||||
state.options = optionsField.getText().trim();
|
||||
lastTransferState = state;
|
||||
}
|
||||
|
||||
public JComboBox<FTConfig.HostType> getHostTypeCombo() { return hostTypeCombo; }
|
||||
public JRadioButton getSendRadio() { return sendRadio; }
|
||||
public JRadioButton getReceiveRadio() { return receiveRadio; }
|
||||
public JTextField getLocalFileField() { return localFileField; }
|
||||
public JTextField getHostFileField() { return hostFileField; }
|
||||
public JRadioButton getAsciiRadio() { return asciiRadio; }
|
||||
public JRadioButton getBinaryRadio() { return binaryRadio; }
|
||||
public JComboBox<Integer> getMtuCombo() { return mtuCombo; }
|
||||
public JCheckBox getCrCheck() { return crCheck; }
|
||||
public JCheckBox getRemapCheck() { return remapCheck; }
|
||||
public JCheckBox getAppendCheck() { return appendCheck; }
|
||||
public JCheckBox getOverwriteCheck() { return overwriteCheck; }
|
||||
public JTextField getRecfmField() { return recfmField; }
|
||||
public JTextField getLreclField() { return lreclField; }
|
||||
public JTextField getBlksizeField() { return blksizeField; }
|
||||
public JTextField getSpaceField() { return spaceField; }
|
||||
public JTextField getOptionsField() { return optionsField; }
|
||||
|
||||
private void applyTheme(Container container) {
|
||||
Color fg = new Color(200, 200, 200);
|
||||
Color bg = new Color(40, 40, 40);
|
||||
|
||||
@@ -34,6 +34,19 @@ public class SettingsDialog extends JDialog {
|
||||
private JSpinner dynamicRowsSpinner;
|
||||
private JSpinner dynamicColsSpinner;
|
||||
|
||||
// Entry Assist & Modes tab
|
||||
private JCheckBox docModeCheck;
|
||||
private JCheckBox wordWrapCheck;
|
||||
private JSpinner startColSpinner;
|
||||
private JSpinner endColSpinner;
|
||||
private JCheckBox bellCheck;
|
||||
private JSpinner bellColSpinner;
|
||||
private JTextField tabStopsField;
|
||||
private JCheckBox insertOffOnAidCheck;
|
||||
private JCheckBox fourColorOverrideCheck;
|
||||
private JCheckBox numericFieldLockCheck;
|
||||
private JCheckBox autoSkipCheck;
|
||||
|
||||
// Advanced tab state tracking
|
||||
private final Color[] tempHostColors = new Color[16];
|
||||
private final Map<String, Color> tempMonoColors = new HashMap<>();
|
||||
@@ -45,7 +58,7 @@ public class SettingsDialog extends JDialog {
|
||||
this.parentApp = parent;
|
||||
|
||||
initComponents();
|
||||
setSize(560, 480);
|
||||
setSize(600, 520);
|
||||
setLocationRelativeTo(parent);
|
||||
}
|
||||
|
||||
@@ -55,6 +68,7 @@ public class SettingsDialog extends JDialog {
|
||||
|
||||
tabbedPane.addTab("Appearance", createAppearancePanel());
|
||||
tabbedPane.addTab("Behavior", createBehaviorPanel());
|
||||
tabbedPane.addTab("Entry Assist & Modes", createEntryAssistPanel());
|
||||
tabbedPane.addTab("Advanced", createAdvancedPanel());
|
||||
|
||||
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 8, 8));
|
||||
@@ -133,11 +147,31 @@ public class SettingsDialog extends JDialog {
|
||||
panel.add(fontLabel, gbc);
|
||||
|
||||
String[] fonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
|
||||
fontBox = new JComboBox<>(fonts);
|
||||
// Find default
|
||||
java.util.List<String> fontList = new java.util.ArrayList<>();
|
||||
boolean hasMonospaced = false;
|
||||
String currentFont = Settings.getFontFamily();
|
||||
for (int i = 0; i < fonts.length; i++) {
|
||||
if (fonts[i].equalsIgnoreCase(currentFont)) {
|
||||
for (String f : fonts) {
|
||||
if ("Monospaced".equalsIgnoreCase(f)) {
|
||||
hasMonospaced = true;
|
||||
}
|
||||
fontList.add(f);
|
||||
}
|
||||
if (!hasMonospaced) {
|
||||
fontList.add(0, "Monospaced");
|
||||
}
|
||||
boolean hasCurrent = false;
|
||||
for (String f : fontList) {
|
||||
if (f.equalsIgnoreCase(currentFont)) {
|
||||
hasCurrent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasCurrent && currentFont != null && !currentFont.trim().isEmpty()) {
|
||||
fontList.add(0, currentFont);
|
||||
}
|
||||
fontBox = new JComboBox<>(fontList.toArray(new String[0]));
|
||||
for (int i = 0; i < fontBox.getItemCount(); i++) {
|
||||
if (fontBox.getItemAt(i).equalsIgnoreCase(currentFont)) {
|
||||
fontBox.setSelectedIndex(i);
|
||||
break;
|
||||
}
|
||||
@@ -267,6 +301,105 @@ public class SettingsDialog extends JDialog {
|
||||
return panel;
|
||||
}
|
||||
|
||||
private JPanel createEntryAssistPanel() {
|
||||
JPanel panel = new JPanel();
|
||||
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
||||
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
|
||||
// Group 1: Entry Assist / Document Mode
|
||||
JPanel eaGroup = new JPanel(new GridBagLayout());
|
||||
eaGroup.setBorder(BorderFactory.createTitledBorder("Entry Assist (Document Mode)"));
|
||||
GridBagConstraints gbc = new GridBagConstraints();
|
||||
gbc.insets = new Insets(4, 6, 4, 6);
|
||||
gbc.anchor = GridBagConstraints.WEST;
|
||||
|
||||
docModeCheck = new JCheckBox("Enable Document Mode (DOC)", Settings.getEntryAssistDocMode());
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 0;
|
||||
gbc.gridwidth = 2;
|
||||
eaGroup.add(docModeCheck, gbc);
|
||||
|
||||
wordWrapCheck = new JCheckBox("Enable Word Wrap (V)", Settings.getEntryAssistWordWrap());
|
||||
gbc.gridy = 1;
|
||||
eaGroup.add(wordWrapCheck, gbc);
|
||||
|
||||
// Margins
|
||||
gbc.gridy = 2;
|
||||
gbc.gridwidth = 1;
|
||||
eaGroup.add(new JLabel("Margins:"), gbc);
|
||||
|
||||
JPanel marginPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 0));
|
||||
marginPanel.setOpaque(false);
|
||||
marginPanel.add(new JLabel("Left:"));
|
||||
startColSpinner = new JSpinner(new SpinnerNumberModel(Settings.getEntryAssistStartCol(), 1, 80, 1));
|
||||
ThemeManager.styleSpinner(startColSpinner);
|
||||
marginPanel.add(startColSpinner);
|
||||
|
||||
marginPanel.add(new JLabel("Right:"));
|
||||
endColSpinner = new JSpinner(new SpinnerNumberModel(Settings.getEntryAssistEndCol(), 1, 80, 1));
|
||||
ThemeManager.styleSpinner(endColSpinner);
|
||||
marginPanel.add(endColSpinner);
|
||||
|
||||
gbc.gridx = 1;
|
||||
eaGroup.add(marginPanel, gbc);
|
||||
|
||||
// End-of-Line Bell
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 3;
|
||||
bellCheck = new JCheckBox("Audible EOL Bell at Col:", Settings.getEntryAssistBell());
|
||||
eaGroup.add(bellCheck, gbc);
|
||||
|
||||
bellColSpinner = new JSpinner(new SpinnerNumberModel(Settings.getEntryAssistBellCol(), 1, 80, 1));
|
||||
ThemeManager.styleSpinner(bellColSpinner);
|
||||
gbc.gridx = 1;
|
||||
eaGroup.add(bellColSpinner, gbc);
|
||||
|
||||
// Tab Stops
|
||||
gbc.gridx = 0;
|
||||
gbc.gridy = 4;
|
||||
eaGroup.add(new JLabel("Tab Stops:"), gbc);
|
||||
|
||||
tabStopsField = new JTextField(Settings.getEntryAssistTabStops(), 20);
|
||||
tabStopsField.setToolTipText("Comma-separated column numbers (1-80, e.g. 1,9,17,25,33,41,49,57,65,73)");
|
||||
gbc.gridx = 1;
|
||||
gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||
eaGroup.add(tabStopsField, gbc);
|
||||
|
||||
panel.add(eaGroup);
|
||||
panel.add(Box.createVerticalStrut(10));
|
||||
|
||||
// Group 2: Terminal Operational Modes
|
||||
JPanel modesGroup = new JPanel(new GridBagLayout());
|
||||
modesGroup.setBorder(BorderFactory.createTitledBorder("Terminal Operational Modes"));
|
||||
GridBagConstraints gbcM = new GridBagConstraints();
|
||||
gbcM.insets = new Insets(4, 6, 4, 6);
|
||||
gbcM.anchor = GridBagConstraints.WEST;
|
||||
gbcM.fill = GridBagConstraints.HORIZONTAL;
|
||||
gbcM.gridx = 0;
|
||||
gbcM.weightx = 1.0;
|
||||
|
||||
insertOffOnAidCheck = new JCheckBox("Reset Insert mode on AID key (Enter, PF, PA, Clear)", Settings.getInsertOffOnAid());
|
||||
gbcM.gridy = 0;
|
||||
modesGroup.add(insertOffOnAidCheck, gbcM);
|
||||
|
||||
fourColorOverrideCheck = new JCheckBox("Base 4-Color Override mode (3279 green/white/red/turquoise)", Settings.getFourColorOverride());
|
||||
gbcM.gridy = 1;
|
||||
modesGroup.add(fourColorOverrideCheck, gbcM);
|
||||
|
||||
numericFieldLockCheck = new JCheckBox("Lock keyboard on non-numeric input in numeric fields (-NUMERIC)", Settings.getNumericFieldLock());
|
||||
gbcM.gridy = 2;
|
||||
modesGroup.add(numericFieldLockCheck, gbcM);
|
||||
|
||||
autoSkipCheck = new JCheckBox("Auto-Skip to next unprotected field when field is filled", Settings.getAutoSkipEnabled());
|
||||
gbcM.gridy = 3;
|
||||
modesGroup.add(autoSkipCheck, gbcM);
|
||||
|
||||
panel.add(modesGroup);
|
||||
panel.add(Box.createVerticalGlue());
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
private JPanel createAdvancedPanel() {
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
|
||||
@@ -585,6 +718,22 @@ public class SettingsDialog extends JDialog {
|
||||
Settings.setDynamicCols((Integer) dynamicColsSpinner.getValue());
|
||||
}
|
||||
|
||||
// Entry Assist & Modes
|
||||
if (docModeCheck != null) {
|
||||
Settings.setEntryAssistDocMode(docModeCheck.isSelected());
|
||||
Settings.setEntryAssistWordWrap(wordWrapCheck.isSelected());
|
||||
Settings.setEntryAssistStartCol((Integer) startColSpinner.getValue());
|
||||
Settings.setEntryAssistEndCol((Integer) endColSpinner.getValue());
|
||||
Settings.setEntryAssistBell(bellCheck.isSelected());
|
||||
Settings.setEntryAssistBellCol((Integer) bellColSpinner.getValue());
|
||||
Settings.setEntryAssistTabStops(tabStopsField.getText().trim());
|
||||
|
||||
Settings.setInsertOffOnAid(insertOffOnAidCheck.isSelected());
|
||||
Settings.setFourColorOverride(fourColorOverrideCheck.isSelected());
|
||||
Settings.setNumericFieldLock(numericFieldLockCheck.isSelected());
|
||||
Settings.setAutoSkipEnabled(autoSkipCheck.isSelected());
|
||||
}
|
||||
|
||||
// Propagate visual changes to the app
|
||||
// Save Colors
|
||||
for (int i=0; i<16; i++) {
|
||||
@@ -600,6 +749,9 @@ public class SettingsDialog extends JDialog {
|
||||
}
|
||||
|
||||
parentApp.getTerminalPanel().reloadSettings();
|
||||
if (parentApp != null && (parentApp.getExtendedState() & Frame.MAXIMIZED_BOTH) == 0) {
|
||||
parentApp.getTerminalPanel().guardedPack();
|
||||
}
|
||||
ThemeManager.applyThemeToWindow(this);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -18,7 +18,11 @@ public class StatusBar extends JPanel {
|
||||
private final JLabel tlsStatus;
|
||||
private final JLabel luName;
|
||||
private final JLabel lockStatus;
|
||||
private final JLabel insertStatus;
|
||||
private final JLabel aplStatus;
|
||||
private final JLabel fieldTypeStatus;
|
||||
private final JLabel docModeStatus;
|
||||
private final JLabel wordWrapStatus;
|
||||
private final JLabel codePageInfo;
|
||||
private final JLabel modelInfo;
|
||||
private final JLabel cursorPosition;
|
||||
@@ -38,7 +42,35 @@ public class StatusBar extends JPanel {
|
||||
tlsStatus = createLabel("", oiaFont, ThemeManager.getOiaFgNormal());
|
||||
luName = createLabel("", oiaFont, ThemeManager.getOiaFgNormal());
|
||||
lockStatus = createLabel("", oiaFont, ThemeManager.getOiaInputInhibited());
|
||||
insertStatus = createClickableLabel("", oiaFont, ThemeManager.getOiaStatusSysAvail(), "Insert Mode (^ / Insert key) - Click to toggle", () -> {
|
||||
if (client != null) {
|
||||
client.toggleInsert();
|
||||
if (terminalPanel != null) terminalPanel.refreshScreen();
|
||||
updateStatus();
|
||||
}
|
||||
});
|
||||
aplStatus = createClickableLabel("", oiaFont, ThemeManager.getOiaFgAlert(), "APL Keyboard Mode (Alt+F3) - Click to toggle", () -> {
|
||||
if (client != null) {
|
||||
client.toggleAplMode();
|
||||
if (terminalPanel != null) terminalPanel.refreshScreen();
|
||||
updateStatus();
|
||||
}
|
||||
});
|
||||
fieldTypeStatus = createLabel("", oiaFont, ThemeManager.getOiaFgDim());
|
||||
docModeStatus = createClickableLabel("", oiaFont, ThemeManager.getOiaStatusSysAvail(), "Entry Assist Document Mode (Alt+F1) - Click to toggle", () -> {
|
||||
if (client != null) {
|
||||
client.toggleDocMode();
|
||||
if (terminalPanel != null) terminalPanel.refreshScreen();
|
||||
updateStatus();
|
||||
}
|
||||
});
|
||||
wordWrapStatus = createClickableLabel("", oiaFont, ThemeManager.getOiaStatusSysAvail(), "Entry Assist Word Wrap (Alt+F2) - Click to toggle", () -> {
|
||||
if (client != null) {
|
||||
client.toggleWordWrap();
|
||||
if (terminalPanel != null) terminalPanel.refreshScreen();
|
||||
updateStatus();
|
||||
}
|
||||
});
|
||||
codePageInfo = createLabel("", oiaFont, ThemeManager.getOiaFgDim());
|
||||
modelInfo = createLabel("", oiaFont, ThemeManager.getOiaFgDim());
|
||||
cursorPosition = createLabel("001/001 [0000]", oiaFont, ThemeManager.getOiaFgNormal());
|
||||
@@ -51,9 +83,17 @@ public class StatusBar extends JPanel {
|
||||
add(luName);
|
||||
add(Box.createHorizontalStrut(12));
|
||||
add(lockStatus);
|
||||
add(Box.createHorizontalStrut(8));
|
||||
add(insertStatus);
|
||||
add(Box.createHorizontalStrut(8));
|
||||
add(aplStatus);
|
||||
add(Box.createHorizontalStrut(10));
|
||||
add(fieldTypeStatus);
|
||||
add(Box.createHorizontalGlue());
|
||||
add(docModeStatus);
|
||||
add(Box.createHorizontalStrut(8));
|
||||
add(wordWrapStatus);
|
||||
add(Box.createHorizontalStrut(12));
|
||||
add(codePageInfo);
|
||||
add(Box.createHorizontalStrut(12));
|
||||
add(modelInfo);
|
||||
@@ -69,6 +109,23 @@ public class StatusBar extends JPanel {
|
||||
return label;
|
||||
}
|
||||
|
||||
private JLabel createClickableLabel(String text, Font font, Color fg, String tooltip, Runnable onClick) {
|
||||
JLabel label = new JLabel(text);
|
||||
label.setFont(font);
|
||||
label.setForeground(fg);
|
||||
label.setToolTipText(tooltip);
|
||||
label.setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.HAND_CURSOR));
|
||||
label.addMouseListener(new java.awt.event.MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(java.awt.event.MouseEvent e) {
|
||||
if (onClick != null) {
|
||||
onClick.run();
|
||||
}
|
||||
}
|
||||
});
|
||||
return label;
|
||||
}
|
||||
|
||||
public void setClient(Telnet3270Client client, TerminalPanel terminalPanel) {
|
||||
this.client = client;
|
||||
this.terminalPanel = terminalPanel;
|
||||
@@ -90,7 +147,11 @@ public class StatusBar extends JPanel {
|
||||
luName.setText("");
|
||||
lockStatus.setText("");
|
||||
lockStatus.setForeground(ThemeManager.getOiaInputInhibited(theme));
|
||||
insertStatus.setText("");
|
||||
aplStatus.setText("");
|
||||
fieldTypeStatus.setText("");
|
||||
docModeStatus.setText("");
|
||||
wordWrapStatus.setText("");
|
||||
codePageInfo.setText("");
|
||||
modelInfo.setText("");
|
||||
cursorPosition.setText("001/001");
|
||||
@@ -203,13 +264,42 @@ public class StatusBar extends JPanel {
|
||||
break;
|
||||
}
|
||||
lockStatus.setForeground(lockFg);
|
||||
} else if (client.getInputProcessor().isInsertMode()) {
|
||||
lockStatus.setText("INSERT");
|
||||
lockStatus.setForeground(ThemeManager.getOiaStatusSysAvail(theme));
|
||||
} else {
|
||||
lockStatus.setText("");
|
||||
}
|
||||
|
||||
// Insert Mode Indicator
|
||||
if (client.getInputProcessor() != null && client.getInputProcessor().isInsertMode()) {
|
||||
insertStatus.setText("^ INS");
|
||||
insertStatus.setForeground(ThemeManager.getOiaStatusSysAvail(theme));
|
||||
} else {
|
||||
insertStatus.setText("");
|
||||
}
|
||||
|
||||
// APL Keyboard Mode Indicator
|
||||
if (client.getInputProcessor() != null && client.getInputProcessor().isAplKeyboardMode()) {
|
||||
aplStatus.setText("APL");
|
||||
aplStatus.setForeground(ThemeManager.getOiaFgAlert(theme));
|
||||
} else {
|
||||
aplStatus.setText("");
|
||||
}
|
||||
|
||||
// Entry Assist DOC Mode Indicator
|
||||
if (client.getScreenBuffer() != null && client.getScreenBuffer().isEntryAssistDOCmode()) {
|
||||
docModeStatus.setText("DOC");
|
||||
docModeStatus.setForeground(ThemeManager.getOiaStatusSysAvail(theme));
|
||||
} else {
|
||||
docModeStatus.setText("");
|
||||
}
|
||||
|
||||
// Entry Assist Word Wrap Indicator
|
||||
if (client.getScreenBuffer() != null && client.getScreenBuffer().isEntryAssistWordWrap()) {
|
||||
wordWrapStatus.setText("V");
|
||||
wordWrapStatus.setForeground(ThemeManager.getOiaStatusSysAvail(theme));
|
||||
} else {
|
||||
wordWrapStatus.setText("");
|
||||
}
|
||||
|
||||
// Field status: Numeric vs Alphanumeric
|
||||
if (state.isFullSession() && client.getScreenBuffer().isFormatted()) {
|
||||
if (client.getOIA().isNumeric()) {
|
||||
|
||||
@@ -84,6 +84,7 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
private long lastGraphicsUpdateCount = -1;
|
||||
private java.awt.image.BufferedImage cachedGraphicsImage = null;
|
||||
private boolean resizeGuard = false;
|
||||
private StatusBar statusBar = null;
|
||||
|
||||
/**
|
||||
* Compute the horizontal render offset to center the grid within the panel.
|
||||
@@ -331,12 +332,12 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
addMouseListener(mouseHandler);
|
||||
addMouseMotionListener(mouseHandler);
|
||||
|
||||
// Handle resize: auto-fit font to window size
|
||||
// Handle resize: keep user configured font persistent without auto-fit reset
|
||||
addComponentListener(new ComponentAdapter() {
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e) {
|
||||
if (resizeGuard) return;
|
||||
autoFitFont();
|
||||
repaint();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -564,47 +565,6 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Font auto-resize ==========
|
||||
|
||||
private void autoFitFont() {
|
||||
int panelW = getWidth();
|
||||
int panelH = getHeight();
|
||||
if (panelW <= 0 || panelH <= 0) return;
|
||||
|
||||
int termCols = 80;
|
||||
int termRows = 24;
|
||||
if (client != null) {
|
||||
ScreenBuffer sb = client.getScreenBuffer();
|
||||
termCols = sb.getDisplayCols();
|
||||
termRows = sb.getDisplayRows();
|
||||
}
|
||||
|
||||
int availW = panelW - 2 * padding;
|
||||
int availH = panelH - 2 * padding;
|
||||
if (availW <= 0 || availH <= 0) return;
|
||||
|
||||
int bestSize = 8;
|
||||
for (int testSize = 8; testSize <= 72; testSize++) {
|
||||
Font testFont = new Font(terminalFont.getFamily(), Font.PLAIN, testSize);
|
||||
FontMetrics fm = getFontMetrics(testFont);
|
||||
int testCellW = fm.charWidth('M');
|
||||
int testCellH = fm.getHeight();
|
||||
|
||||
if (testCellW * termCols <= availW && testCellH * termRows <= availH) {
|
||||
bestSize = testSize;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestSize != currentFontSize) {
|
||||
currentFontSize = bestSize;
|
||||
haus.nightmare.j3270.config.Settings.setFontSize(bestSize);
|
||||
terminalFont = new Font(terminalFont.getFamily(), Font.PLAIN, bestSize);
|
||||
updateCellSize();
|
||||
}
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void guardedPack() {
|
||||
resizeGuard = true;
|
||||
@@ -671,6 +631,11 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
bindKeyToMap(im, "SYSREQ", haus.nightmare.j3270.config.Settings.getKeyBinding("SYSREQ", "alt S"));
|
||||
bindKeyToMap(im, "CURSEL", haus.nightmare.j3270.config.Settings.getKeyBinding("CURSEL", "alt Q"));
|
||||
|
||||
// Operational Mode keybindings
|
||||
bindKeyToMap(im, "DOCMODE", haus.nightmare.j3270.config.Settings.getKeyBinding("DOCMODE", "alt F1"));
|
||||
bindKeyToMap(im, "WORDWRAP", haus.nightmare.j3270.config.Settings.getKeyBinding("WORDWRAP", "alt F2"));
|
||||
bindKeyToMap(im, "APL", haus.nightmare.j3270.config.Settings.getKeyBinding("APL", "alt F3"));
|
||||
|
||||
// Copy/Paste/Lightpen bindings
|
||||
int shortcutMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
|
||||
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, shortcutMask), "j3270-COPY");
|
||||
@@ -683,6 +648,9 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
am.put("j3270-ESCAPE", createAction(this::handleReset));
|
||||
am.put("j3270-TAB", createAction(() -> handleTab(false)));
|
||||
am.put("j3270-shift TAB", createAction(() -> handleTab(true)));
|
||||
am.put("j3270-DOCMODE", createAction(this::toggleDocMode));
|
||||
am.put("j3270-WORDWRAP", createAction(this::toggleWordWrap));
|
||||
am.put("j3270-APL", createAction(this::toggleAplMode));
|
||||
am.put("j3270-UP", createAction(() -> handleCursor("up")));
|
||||
am.put("j3270-DOWN", createAction(() -> handleCursor("down")));
|
||||
am.put("j3270-LEFT", createAction(() -> handleCursor("left")));
|
||||
@@ -978,9 +946,48 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
var ip = client.getInputProcessor();
|
||||
ip.setInsertMode(!ip.isInsertMode());
|
||||
refreshScreen();
|
||||
if (statusBar != null) statusBar.updateStatus();
|
||||
}
|
||||
}
|
||||
|
||||
public void toggleDocMode() {
|
||||
if (client != null) {
|
||||
client.toggleDocMode();
|
||||
haus.nightmare.j3270.config.Settings.setEntryAssistDocMode(client.isDocMode());
|
||||
refreshScreen();
|
||||
if (statusBar != null) statusBar.updateStatus();
|
||||
}
|
||||
}
|
||||
|
||||
public void toggleWordWrap() {
|
||||
if (client != null) {
|
||||
client.toggleWordWrap();
|
||||
haus.nightmare.j3270.config.Settings.setEntryAssistWordWrap(client.isWordWrap());
|
||||
refreshScreen();
|
||||
if (statusBar != null) statusBar.updateStatus();
|
||||
}
|
||||
}
|
||||
|
||||
public void toggleAplMode() {
|
||||
if (client != null) {
|
||||
client.toggleAplMode();
|
||||
refreshScreen();
|
||||
if (statusBar != null) statusBar.updateStatus();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDocMode() {
|
||||
return client != null && client.isDocMode();
|
||||
}
|
||||
|
||||
public boolean isWordWrap() {
|
||||
return client != null && client.isWordWrap();
|
||||
}
|
||||
|
||||
public boolean isAplMode() {
|
||||
return client != null && client.isAplMode();
|
||||
}
|
||||
|
||||
private void handleClear() {
|
||||
if (client != null && client.getConnectionState().isFullSession()) {
|
||||
if (client.getConnectionState().isNvt()) {
|
||||
@@ -1046,11 +1053,47 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
}
|
||||
}
|
||||
|
||||
public void setStatusBar(StatusBar statusBar) {
|
||||
this.statusBar = statusBar;
|
||||
}
|
||||
|
||||
public StatusBar getStatusBar() {
|
||||
return statusBar;
|
||||
}
|
||||
|
||||
public void applyModeSettings() {
|
||||
if (client != null) {
|
||||
ScreenBuffer sb = client.getScreenBuffer();
|
||||
if (sb != null) {
|
||||
sb.setEntryAssistDOCmode(haus.nightmare.j3270.config.Settings.getEntryAssistDocMode());
|
||||
sb.setEntryAssistWordWrap(haus.nightmare.j3270.config.Settings.getEntryAssistWordWrap());
|
||||
sb.setLeftMargin(Math.max(0, haus.nightmare.j3270.config.Settings.getEntryAssistStartCol() - 1));
|
||||
sb.setRightMargin(Math.max(0, haus.nightmare.j3270.config.Settings.getEntryAssistEndCol() - 1));
|
||||
sb.setWordTabPositions(haus.nightmare.j3270.config.Settings.getEntryAssistTabStopsArray());
|
||||
}
|
||||
haus.nightmare.lib3270j.input.InputProcessor ip = client.getInputProcessor();
|
||||
if (ip != null) {
|
||||
ip.setBellEnabled(haus.nightmare.j3270.config.Settings.getEntryAssistBell());
|
||||
ip.setBellColumn(Math.max(0, haus.nightmare.j3270.config.Settings.getEntryAssistBellCol() - 1));
|
||||
ip.setInsertOffOnAid(haus.nightmare.j3270.config.Settings.getInsertOffOnAid());
|
||||
ip.setNumericFieldLock(haus.nightmare.j3270.config.Settings.getNumericFieldLock());
|
||||
ip.setAutoSkipEnabled(haus.nightmare.j3270.config.Settings.getAutoSkipEnabled());
|
||||
}
|
||||
}
|
||||
if (statusBar != null) {
|
||||
statusBar.updateStatus();
|
||||
}
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void refreshScreen() {
|
||||
if (client != null) {
|
||||
client.getScreenBuffer().updateDisplaySnapshot();
|
||||
}
|
||||
repaint();
|
||||
if (statusBar != null) {
|
||||
statusBar.updateStatus();
|
||||
}
|
||||
Container parent = getParent();
|
||||
while (parent != null) {
|
||||
if (parent instanceof JFrame) {
|
||||
@@ -1088,6 +1131,7 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
setupColors();
|
||||
setupKeyBindings();
|
||||
setupFont();
|
||||
applyModeSettings();
|
||||
blockSelectMode = haus.nightmare.j3270.config.Settings.getBlockSelectMode();
|
||||
crosshairRulerEnabled = haus.nightmare.j3270.config.Settings.getCrosshairRuler();
|
||||
String cStyle = haus.nightmare.j3270.config.Settings.getCursorStyle();
|
||||
@@ -1137,6 +1181,13 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
if (client != null) {
|
||||
setupGraphicsPlaneRenderer();
|
||||
updateCellSize();
|
||||
applyModeSettings();
|
||||
|
||||
if (client.getInputProcessor() != null) {
|
||||
client.getInputProcessor().setBellListener(() -> {
|
||||
SwingUtilities.invokeLater(() -> Toolkit.getDefaultToolkit().beep());
|
||||
});
|
||||
}
|
||||
|
||||
client.setNvtClipboardHandler(new haus.nightmare.lib3270j.nvt.NvtProcessor.ClipboardHandler() {
|
||||
@Override
|
||||
@@ -1564,10 +1615,12 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
}
|
||||
|
||||
private Color getColorForAttribute(ExtendedAttribute ea, ExtendedAttribute currentFieldEa, byte currentFA) {
|
||||
int fg = ea.fg != 0 ? (ea.fg & 0xFF)
|
||||
: (currentFieldEa != null && currentFieldEa.fg != 0 ? (currentFieldEa.fg & 0xFF) : 0);
|
||||
if (fg >= 0xf0 && fg <= 0xff) {
|
||||
return hostColors[fg - 0xf0];
|
||||
if (!haus.nightmare.j3270.config.Settings.getFourColorOverride()) {
|
||||
int fg = ea.fg != 0 ? (ea.fg & 0xFF)
|
||||
: (currentFieldEa != null && currentFieldEa.fg != 0 ? (currentFieldEa.fg & 0xFF) : 0);
|
||||
if (fg >= 0xf0 && fg <= 0xff) {
|
||||
return hostColors[fg - 0xf0];
|
||||
}
|
||||
}
|
||||
if (faIsProtected(currentFA & 0xFF)) {
|
||||
return faIsHigh(currentFA & 0xFF) ? hostColors[HOST_COLOR_WHITE] : hostColors[HOST_COLOR_TURQUOISE];
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package haus.nightmare.j3270.ft;
|
||||
|
||||
import haus.nightmare.lib3270j.ft.FTConfig;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.awt.HeadlessException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class FileTransferSessionAndCmsTest {
|
||||
|
||||
@BeforeEach
|
||||
@AfterEach
|
||||
public void cleanup() {
|
||||
FileTransferDialog.resetSessionState();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCmsFilenamePeriodReplacement() {
|
||||
// Test replacing periods with spaces for CMS
|
||||
assertEquals("fscms exec", FileTransferDialog.formatHostFilename("fscms.exec", FTConfig.HostType.CMS));
|
||||
assertEquals("fscms exec", FileTransferDialog.formatHostFilename("/var/tmp/fscms.exec", FTConfig.HostType.CMS));
|
||||
assertEquals("test script exec", FileTransferDialog.formatHostFilename("test.script.exec", FTConfig.HostType.CMS));
|
||||
assertEquals("noextension", FileTransferDialog.formatHostFilename("noextension", FTConfig.HostType.CMS));
|
||||
|
||||
// TSO and CICS should retain periods
|
||||
assertEquals("fscms.exec", FileTransferDialog.formatHostFilename("fscms.exec", FTConfig.HostType.TSO));
|
||||
assertEquals("my.dataset.name", FileTransferDialog.formatHostFilename("/path/to/my.dataset.name", FTConfig.HostType.TSO));
|
||||
assertEquals("cics.file", FileTransferDialog.formatHostFilename("cics.file", FTConfig.HostType.CICS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionStateRememberedInMemory() {
|
||||
assertNull(FileTransferDialog.getLastTransferState());
|
||||
|
||||
FileTransferDialog.TransferSessionState state = new FileTransferDialog.TransferSessionState();
|
||||
state.hostType = FTConfig.HostType.CMS;
|
||||
state.isSend = true;
|
||||
state.localFile = "/tmp/fscms.exec";
|
||||
state.hostFile = "fscms exec";
|
||||
state.isAscii = true;
|
||||
state.mtu = 8192;
|
||||
state.crFlag = false;
|
||||
state.remapFlag = false;
|
||||
state.append = true;
|
||||
state.overwrite = true;
|
||||
state.options = "CLEAR";
|
||||
|
||||
FileTransferDialog.setLastTransferState(state);
|
||||
|
||||
FileTransferDialog.TransferSessionState retrieved = FileTransferDialog.getLastTransferState();
|
||||
assertNotNull(retrieved);
|
||||
assertEquals(FTConfig.HostType.CMS, retrieved.hostType);
|
||||
assertTrue(retrieved.isSend);
|
||||
assertEquals("/tmp/fscms.exec", retrieved.localFile);
|
||||
assertEquals("fscms exec", retrieved.hostFile);
|
||||
assertTrue(retrieved.isAscii);
|
||||
assertEquals(8192, retrieved.mtu);
|
||||
assertFalse(retrieved.crFlag);
|
||||
assertFalse(retrieved.remapFlag);
|
||||
assertTrue(retrieved.append);
|
||||
assertTrue(retrieved.overwrite);
|
||||
assertEquals("CLEAR", retrieved.options);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDialogPrefillingFromSessionState() {
|
||||
FileTransferDialog.TransferSessionState state = new FileTransferDialog.TransferSessionState();
|
||||
state.hostType = FTConfig.HostType.CMS;
|
||||
state.isSend = true;
|
||||
state.localFile = "/tmp/fscms.exec";
|
||||
state.hostFile = "fscms exec";
|
||||
state.isAscii = false; // Binary
|
||||
state.mtu = 4096;
|
||||
state.crFlag = true;
|
||||
state.remapFlag = true;
|
||||
state.append = false;
|
||||
state.overwrite = true;
|
||||
state.options = "ASCII";
|
||||
|
||||
FileTransferDialog.setLastTransferState(state);
|
||||
|
||||
try {
|
||||
FileTransferDialog dialog = new FileTransferDialog(null, null);
|
||||
assertEquals(FTConfig.HostType.CMS, dialog.getHostTypeCombo().getSelectedItem());
|
||||
assertTrue(dialog.getSendRadio().isSelected());
|
||||
assertFalse(dialog.getReceiveRadio().isSelected());
|
||||
assertEquals("/tmp/fscms.exec", dialog.getLocalFileField().getText());
|
||||
assertEquals("fscms exec", dialog.getHostFileField().getText());
|
||||
assertFalse(dialog.getAsciiRadio().isSelected());
|
||||
assertTrue(dialog.getBinaryRadio().isSelected());
|
||||
assertEquals(4096, dialog.getMtuCombo().getSelectedItem());
|
||||
assertTrue(dialog.getOverwriteCheck().isSelected());
|
||||
assertEquals("ASCII", dialog.getOptionsField().getText());
|
||||
dialog.dispose();
|
||||
} catch (HeadlessException e) {
|
||||
// Headless environment; verified through state retention
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package haus.nightmare.j3270.ui;
|
||||
|
||||
import haus.nightmare.j3270.config.Settings;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.HeadlessException;
|
||||
import java.awt.event.ComponentEvent;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class FontSettingsPersistenceTest {
|
||||
|
||||
private int originalFontSize;
|
||||
private String originalFontFamily;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
originalFontSize = Settings.getFontSize();
|
||||
originalFontFamily = Settings.getFontFamily();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
Settings.setFontSize(originalFontSize);
|
||||
Settings.setFontFamily(originalFontFamily);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSettingsPersistence() {
|
||||
Settings.setFontSize(22);
|
||||
Settings.setFontFamily("Monospaced");
|
||||
assertEquals(22, Settings.getFontSize());
|
||||
assertEquals("Monospaced", Settings.getFontFamily());
|
||||
|
||||
Settings.setFontSize(18);
|
||||
assertEquals(18, Settings.getFontSize());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTerminalPanelResizeDoesNotResetFontSize() {
|
||||
try {
|
||||
Settings.setFontSize(24);
|
||||
Settings.setFontFamily("Monospaced");
|
||||
|
||||
TerminalPanel panel = new TerminalPanel();
|
||||
assertEquals(24, panel.getFontSize());
|
||||
assertEquals(24, Settings.getFontSize());
|
||||
|
||||
// Simulate window / component resize events
|
||||
panel.setSize(new Dimension(800, 600));
|
||||
ComponentEvent resizeEvent1 = new ComponentEvent(panel, ComponentEvent.COMPONENT_RESIZED);
|
||||
for (java.awt.event.ComponentListener cl : panel.getComponentListeners()) {
|
||||
cl.componentResized(resizeEvent1);
|
||||
}
|
||||
|
||||
// Verify font size and family did NOT reset
|
||||
assertEquals(24, panel.getFontSize());
|
||||
assertEquals(24, Settings.getFontSize());
|
||||
assertEquals("Monospaced", Settings.getFontFamily());
|
||||
|
||||
// Simulate another resize to smaller dimension
|
||||
panel.setSize(new Dimension(400, 300));
|
||||
ComponentEvent resizeEvent2 = new ComponentEvent(panel, ComponentEvent.COMPONENT_RESIZED);
|
||||
for (java.awt.event.ComponentListener cl : panel.getComponentListeners()) {
|
||||
cl.componentResized(resizeEvent2);
|
||||
}
|
||||
|
||||
assertEquals(24, panel.getFontSize());
|
||||
assertEquals(24, Settings.getFontSize());
|
||||
assertEquals("Monospaced", Settings.getFontFamily());
|
||||
|
||||
panel.dispose();
|
||||
} catch (HeadlessException e) {
|
||||
// In headless environment without display, Settings persistence is verified above
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -434,7 +434,7 @@ public class ConnectionConfig {
|
||||
return terminalName;
|
||||
}
|
||||
if (isDynamicModel()) {
|
||||
return extendedDataStream ? "IBM-DYNAMIC-E" : "IBM-DYNAMIC";
|
||||
return "IBM-DYNAMIC";
|
||||
}
|
||||
return extendedDataStream ? model.getTerminalType() : model.getBaseTerminalType();
|
||||
}
|
||||
|
||||
@@ -462,8 +462,23 @@ public class Telnet3270Client {
|
||||
public void processFieldMark() { inputProcessor.processFieldMark(); }
|
||||
|
||||
/** Toggle Insert Mode. */
|
||||
public void toggleInsert() { inputProcessor.setInsertMode(!inputProcessor.isInsertMode()); }
|
||||
public void processToggleInsert() { inputProcessor.processToggleInsert(); }
|
||||
public boolean isInsertMode() { return inputProcessor != null && inputProcessor.isInsertMode(); }
|
||||
public void toggleInsert() { if (inputProcessor != null) inputProcessor.setInsertMode(!inputProcessor.isInsertMode()); }
|
||||
public void processToggleInsert() { if (inputProcessor != null) inputProcessor.processToggleInsert(); }
|
||||
|
||||
/** Document Mode (Entry Assist) operations. */
|
||||
public boolean isDocMode() { return screenBuffer != null && screenBuffer.isEntryAssistDOCmode(); }
|
||||
public void setDocMode(boolean b) { if (screenBuffer != null) screenBuffer.setEntryAssistDOCmode(b); if (oia != null) oia.notifyOIAChanged(); }
|
||||
public void toggleDocMode() { setDocMode(!isDocMode()); }
|
||||
|
||||
public boolean isWordWrap() { return screenBuffer != null && screenBuffer.isEntryAssistWordWrap(); }
|
||||
public void setWordWrap(boolean b) { if (screenBuffer != null) screenBuffer.setEntryAssistWordWrap(b); if (oia != null) oia.notifyOIAChanged(); }
|
||||
public void toggleWordWrap() { setWordWrap(!isWordWrap()); }
|
||||
|
||||
/** APL Keyboard Mode operations. */
|
||||
public boolean isAplMode() { return inputProcessor != null && inputProcessor.isAplKeyboardMode(); }
|
||||
public void setAplMode(boolean b) { if (inputProcessor != null) inputProcessor.setAplKeyboardMode(b); }
|
||||
public void toggleAplMode() { if (inputProcessor != null) inputProcessor.toggleAplKeyboardMode(); }
|
||||
|
||||
/** Move word left. */
|
||||
public void processWordLeft() { inputProcessor.processWordLeft(); }
|
||||
|
||||
@@ -46,11 +46,11 @@ public enum TerminalModel {
|
||||
/**
|
||||
* Returns the terminal type string for TN3270E negotiation.
|
||||
* e.g., "IBM-3279-4-E" for a color model 4 with extended data stream,
|
||||
* or "IBM-DYNAMIC-E" for dynamic model.
|
||||
* or "IBM-DYNAMIC" for dynamic model.
|
||||
*/
|
||||
public String getTerminalType() {
|
||||
if (modelNumber == 0) {
|
||||
return "IBM-DYNAMIC-E";
|
||||
return "IBM-DYNAMIC";
|
||||
}
|
||||
return String.format("IBM-327%c-%d-E", color ? '9' : '8', modelNumber);
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ public class ECLOIA implements ECLConstants {
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void notifyOIAChanged() {
|
||||
public synchronized void notifyOIAChanged() {
|
||||
ECLOIAEvent event = new ECLOIAEvent(this, ECLOIAEvent.OIA_UPDATE, getInputInhibited(),
|
||||
getAlphanumericType(), isInsertMode(), getStatusString());
|
||||
for (haus.nightmare.lib3270j.ecl.ECLOIANotify l : listeners) {
|
||||
@@ -484,6 +484,7 @@ public class ECLOIA implements ECLConstants {
|
||||
if (isNumeric()) s |= STATE_NUMFIELD;
|
||||
if (screen != null && screen.isEntryAssistDOCmode()) s |= STATE_DOC_MODE;
|
||||
if (screen != null && screen.isEntryAssistWordWrap()) s |= STATE_WORDWRAP;
|
||||
if (isApl()) s |= STATE_APL;
|
||||
if (isXSystem()) s |= STATE_SYS_LOCK;
|
||||
if (isXComm()) s |= STATE_COMM_CHECK;
|
||||
return s;
|
||||
@@ -494,6 +495,21 @@ public class ECLOIA implements ECLConstants {
|
||||
public int getStatusFlags() { return GetStatusFlags(); }
|
||||
public long getStatusFlagsEx() { return GetStatusFlagsEx(); }
|
||||
|
||||
public boolean isApl() {
|
||||
return inputProcessor != null && inputProcessor.isAplKeyboardMode();
|
||||
}
|
||||
public boolean IsApl() { return isApl(); }
|
||||
|
||||
public boolean isDocMode() {
|
||||
return screen != null && screen.isEntryAssistDOCmode();
|
||||
}
|
||||
public boolean IsDocMode() { return isDocMode(); }
|
||||
|
||||
public boolean isWordWrap() {
|
||||
return screen != null && screen.isEntryAssistWordWrap();
|
||||
}
|
||||
public boolean IsWordWrap() { return isWordWrap(); }
|
||||
|
||||
public synchronized void setBitmaskState(long flag, boolean on) {
|
||||
this.previousState = this.state;
|
||||
if (on) {
|
||||
|
||||
@@ -118,7 +118,91 @@ public class InputProcessor {
|
||||
}
|
||||
}
|
||||
public boolean isInsertMode() { return insertMode; }
|
||||
public void setInsertMode(boolean insert) { this.insertMode = insert; }
|
||||
public void setInsertMode(boolean insert) {
|
||||
this.insertMode = insert;
|
||||
if (oia != null) {
|
||||
oia.notifyOIAChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface BellListener {
|
||||
void onBell();
|
||||
}
|
||||
|
||||
private BellListener bellListener;
|
||||
private boolean bellEnabled = false;
|
||||
private int bellColumn = 74; // 0-based index for column 75
|
||||
private boolean insertOffOnAid = true;
|
||||
private boolean aplKeyboardMode = false;
|
||||
private boolean numericFieldLock = true;
|
||||
private boolean autoSkipEnabled = true;
|
||||
|
||||
public void setBellListener(BellListener listener) { this.bellListener = listener; }
|
||||
public BellListener getBellListener() { return bellListener; }
|
||||
public boolean isBellEnabled() { return bellEnabled; }
|
||||
public void setBellEnabled(boolean enabled) { this.bellEnabled = enabled; }
|
||||
public int getBellColumn() { return bellColumn; }
|
||||
public void setBellColumn(int col) { this.bellColumn = col; }
|
||||
|
||||
public boolean isInsertOffOnAid() { return insertOffOnAid; }
|
||||
public void setInsertOffOnAid(boolean val) { this.insertOffOnAid = val; }
|
||||
|
||||
public boolean isAplKeyboardMode() { return aplKeyboardMode; }
|
||||
public void setAplKeyboardMode(boolean enabled) {
|
||||
this.aplKeyboardMode = enabled;
|
||||
if (oia != null) {
|
||||
oia.notifyOIAChanged();
|
||||
}
|
||||
}
|
||||
public void toggleAplKeyboardMode() {
|
||||
setAplKeyboardMode(!aplKeyboardMode);
|
||||
}
|
||||
|
||||
public boolean isNumericFieldLock() { return numericFieldLock; }
|
||||
public void setNumericFieldLock(boolean lock) { this.numericFieldLock = lock; }
|
||||
|
||||
public boolean isAutoSkipEnabled() { return autoSkipEnabled; }
|
||||
public void setAutoSkipEnabled(boolean enabled) { this.autoSkipEnabled = enabled; }
|
||||
|
||||
/**
|
||||
* Map ASCII key character to IBM 3270 APL / Graphic Escape code point.
|
||||
*/
|
||||
private int getAplCodeForChar(char ch) {
|
||||
char upper = Character.toUpperCase(ch);
|
||||
switch (upper) {
|
||||
case 'A': return 0x81; // ⍺ Alpha
|
||||
case 'B': return 0x82; // ⊥ Up tack / decode
|
||||
case 'C': return 0x83; // ∩ Intersection
|
||||
case 'D': return 0x84; // ⌊ Floor
|
||||
case 'E': return 0x85; // │ Vertical line
|
||||
case 'F': return 0x87; // ∇ Del / Grad
|
||||
case 'G': return 0x88; // ∆ Delta
|
||||
case 'H': return 0x89; // ⍳ Iota
|
||||
case 'I': return 0x8A; // → Right arrow
|
||||
case 'J': return 0x8B; // ⍞ Quote Quad
|
||||
case 'K': return 0x8C; // ≤ Less than or equal
|
||||
case 'L': return 0xAD; // [ Bracket left
|
||||
case 'M': return 0x8E; // × Multiply
|
||||
case 'N': return 0x8F; // ÷ Divide
|
||||
case 'O': return 0x90; // ⍟ Circle Star
|
||||
case 'P': return 0x91; // ⌹ Domino / Quad divide
|
||||
case 'Q': return 0x92; // ⊤ Down tack / encode
|
||||
case 'R': return 0x95; // ⍴ Rho
|
||||
case 'S': return 0x94; // ⌈ Ceiling
|
||||
case 'T': return 0x98; // ⍷ Epsilon underbar
|
||||
case 'U': return 0x93; // ∪ Union
|
||||
case 'V': return 0x97; // ≠ Not equal
|
||||
case 'W': return 0x96; // ⍵ Omega
|
||||
case 'X': return 0xAC; // ⍉ Transpose
|
||||
case 'Y': return 0xA8; // ↑ Up arrow / Take
|
||||
case 'Z': return 0xA9; // ↓ Down arrow / Drop
|
||||
case '-': return 0xA2; // ─ Horizontal line
|
||||
case '|': return 0x85; // │ Vertical line
|
||||
case '+': return 0xCB; // ┼ Cross
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter a character at the current cursor position.
|
||||
@@ -142,8 +226,8 @@ public class InputProcessor {
|
||||
int baddr = screen.getCursorAddress();
|
||||
baddr = ((baddr % size) + size) % size;
|
||||
|
||||
// Entry Assist DOC mode / Word Wrap handling
|
||||
if ((screen.isEntryAssistDOCmode() || screen.isEntryAssistWordWrap()) && !isNvtMode()) {
|
||||
// Entry Assist Word Wrap handling
|
||||
if (screen.isEntryAssistWordWrap() && !isNvtMode()) {
|
||||
int curCol = baddr % screen.getCols();
|
||||
int endCol = screen.getEntryAssistEndColumn();
|
||||
int startCol = screen.getEntryAssistStartColumn();
|
||||
@@ -178,7 +262,7 @@ public class InputProcessor {
|
||||
}
|
||||
|
||||
// Numeric-only field check: digits 0-9, minus (-), period (.), space ( ), DUP, FM
|
||||
if (faIsNumeric(faVal & 0xFF)) {
|
||||
if (numericFieldLock && faIsNumeric(faVal & 0xFF)) {
|
||||
boolean isValidNumeric = (ch >= '0' && ch <= '9') || ch == '-' || ch == '.' || ch == ' '
|
||||
|| ch == '*' || ch == ';' || ch == (char) FCORDER_DUP || ch == (char) FCORDER_FM;
|
||||
if (!isValidNumeric) {
|
||||
@@ -191,8 +275,20 @@ public class InputProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
// Translate character to EBCDIC
|
||||
int ebc = translator.unicodeToEbcdic(ch);
|
||||
// Translate character to EBCDIC (or APL Graphic Escape if in APL Keyboard Mode)
|
||||
boolean isAplChar = false;
|
||||
int ebc = -1;
|
||||
if (aplKeyboardMode) {
|
||||
int aplCode = getAplCodeForChar(ch);
|
||||
if (aplCode >= 0) {
|
||||
ebc = aplCode;
|
||||
ch = translator.mapAPL(aplCode);
|
||||
isAplChar = true;
|
||||
}
|
||||
}
|
||||
if (ebc < 0) {
|
||||
ebc = translator.unicodeToEbcdic(ch);
|
||||
}
|
||||
if (ebc < 0) return;
|
||||
|
||||
if (insertMode) {
|
||||
@@ -229,6 +325,9 @@ public class InputProcessor {
|
||||
ExtendedAttribute ea = screen.getCell(baddr);
|
||||
ea.ec = (byte) ebc;
|
||||
ea.ucs4 = ch;
|
||||
if (isAplChar) {
|
||||
ea.cs = ExtendedAttribute.CS_GE;
|
||||
}
|
||||
|
||||
// Set MDT on field attribute
|
||||
if (screen.isFormatted()) {
|
||||
@@ -244,7 +343,7 @@ public class InputProcessor {
|
||||
int nextAddr = screen.incrementAddress(baddr);
|
||||
if (screen.getCell(nextAddr).isFieldAttribute()) {
|
||||
byte nextFa = screen.getCell(nextAddr).fa;
|
||||
if (faIsSkip(nextFa & 0xFF)) {
|
||||
if (autoSkipEnabled && faIsSkip(nextFa & 0xFF)) {
|
||||
// Auto-skip field (Protected + Numeric): jump to next unprotected field
|
||||
int skipTarget = screen.findNextUnprotected(nextAddr);
|
||||
screen.setCursorAddress(skipTarget);
|
||||
@@ -264,6 +363,28 @@ public class InputProcessor {
|
||||
screen.setCursorAddress((baddr + 1) % size);
|
||||
}
|
||||
|
||||
// Entry Assist DOC mode: if we typed into or past the right margin, advance to startCol on next row
|
||||
if (screen != null && screen.isEntryAssistDOCmode() && !screen.isEntryAssistWordWrap() && !isNvtMode()) {
|
||||
int typedCol = baddr % screen.getCols();
|
||||
if (typedCol >= screen.getEntryAssistEndColumn()) {
|
||||
int curRow = baddr / screen.getCols();
|
||||
int nextRow = (curRow + 1) % screen.getRows();
|
||||
int targetAddr = nextRow * screen.getCols() + screen.getEntryAssistStartColumn();
|
||||
if (screen.isFormatted()) {
|
||||
targetAddr = screen.findNextUnprotected(targetAddr - 1);
|
||||
}
|
||||
screen.setCursorAddress(targetAddr);
|
||||
}
|
||||
}
|
||||
|
||||
// Audible End-of-Line Warning Signal
|
||||
if (bellEnabled && screen != null) {
|
||||
int curCol = screen.getCursorCol();
|
||||
if (curCol == bellColumn && bellListener != null) {
|
||||
bellListener.onBell();
|
||||
}
|
||||
}
|
||||
|
||||
screen.markAllChanged();
|
||||
screen.updateDisplaySnapshot();
|
||||
}
|
||||
@@ -523,6 +644,9 @@ public class InputProcessor {
|
||||
}
|
||||
|
||||
if (aidCode == AID_CLEAR) {
|
||||
if (insertOffOnAid && insertMode) {
|
||||
setInsertMode(false);
|
||||
}
|
||||
screen.clear();
|
||||
screen.markAllChanged();
|
||||
if (graphicsPlane != null) {
|
||||
@@ -538,6 +662,10 @@ public class InputProcessor {
|
||||
return;
|
||||
}
|
||||
|
||||
if (insertOffOnAid && insertMode) {
|
||||
setInsertMode(false);
|
||||
}
|
||||
|
||||
lastAid = aidCode;
|
||||
setKeyboardLocked(true);
|
||||
|
||||
@@ -792,6 +920,10 @@ public class InputProcessor {
|
||||
}
|
||||
|
||||
public void tab() {
|
||||
if (screen != null && screen.isEntryAssistDOCmode()) {
|
||||
screen.processWordTab(true);
|
||||
return;
|
||||
}
|
||||
int addr = screen.findNextUnprotected(screen.getCursorAddress());
|
||||
screen.setCursorAddress(addr);
|
||||
screen.updateDisplaySnapshot();
|
||||
@@ -806,6 +938,10 @@ public class InputProcessor {
|
||||
}
|
||||
|
||||
public void backTab() {
|
||||
if (screen != null && screen.isEntryAssistDOCmode()) {
|
||||
screen.processWordTab(false);
|
||||
return;
|
||||
}
|
||||
if (!screen.isFormatted()) return;
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
if (size <= 0) return;
|
||||
@@ -1546,6 +1682,22 @@ public class InputProcessor {
|
||||
case "lightpen":
|
||||
processLightPen();
|
||||
break;
|
||||
case "docmode":
|
||||
if (screen != null) {
|
||||
screen.setEntryAssistDOCmode(!screen.isEntryAssistDOCmode());
|
||||
if (oia != null) oia.notifyOIAChanged();
|
||||
}
|
||||
break;
|
||||
case "wordwrap":
|
||||
if (screen != null) {
|
||||
screen.setEntryAssistWordWrap(!screen.isEntryAssistWordWrap());
|
||||
if (oia != null) oia.notifyOIAChanged();
|
||||
}
|
||||
break;
|
||||
case "apl":
|
||||
case "aplmode":
|
||||
toggleAplKeyboardMode();
|
||||
break;
|
||||
default:
|
||||
if (token.startsWith("pf")) {
|
||||
try {
|
||||
|
||||
@@ -1071,12 +1071,16 @@ public class ScreenBuffer {
|
||||
public void setEntryAssistTabStops(int[] stops) { this.tabStops = stops; }
|
||||
public void SetEntryAssistTabStops(int[] stops) { setEntryAssistTabStops(stops); }
|
||||
|
||||
public void setLeftMargin(int n) { setEntryAssistStartColumn(n); }
|
||||
public void setRightMargin(int n) { setEntryAssistEndColumn(n); }
|
||||
public void setWordTabPositions(int[] stops) { setEntryAssistTabStops(stops); }
|
||||
|
||||
/**
|
||||
* Perform Entry Assist word wrap if typing near/past end margin.
|
||||
* Moves any partial word typed on the current line to the beginning of the next line (docStartCol).
|
||||
*/
|
||||
public synchronized boolean handleWordWrap(int curAddr, char typedChar) {
|
||||
if (!docMode && !wordWrap) return false;
|
||||
if (!wordWrap) return false;
|
||||
int size = rows * cols;
|
||||
if (size <= 0) return false;
|
||||
curAddr = ((curAddr % size) + size) % size;
|
||||
|
||||
@@ -75,9 +75,6 @@ public class TelnetFSM {
|
||||
return list;
|
||||
}
|
||||
if (config.isDynamicModel()) {
|
||||
if (config.isExtendedDataStream()) {
|
||||
list.add("IBM-DYNAMIC-E");
|
||||
}
|
||||
list.add("IBM-DYNAMIC");
|
||||
list.add("IBM-3279-4-E");
|
||||
list.add("IBM-3279-4");
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
package haus.nightmare.lib3270j.ecl;
|
||||
|
||||
import haus.nightmare.lib3270j.TerminalModel;
|
||||
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||
import haus.nightmare.lib3270j.input.InputProcessor;
|
||||
import haus.nightmare.lib3270j.protocol.DS3270Constants;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class EntryAssistFullModeTest {
|
||||
|
||||
private ScreenBuffer screen;
|
||||
private EbcdicTranslator translator;
|
||||
private InputProcessor inputProcessor;
|
||||
private ECLOIA oia;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
translator = new EbcdicTranslator();
|
||||
screen = new ScreenBuffer(TerminalModel.IBM_3279_2, translator); // 24x80
|
||||
inputProcessor = new InputProcessor(screen, translator, null);
|
||||
oia = new ECLOIA(screen, inputProcessor, null);
|
||||
inputProcessor.setOIA(oia);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDocModeCursorAdvanceWithoutWordWrap() {
|
||||
// Document Mode ON, Word Wrap OFF
|
||||
screen.setEntryAssistDOCmode(true);
|
||||
screen.setEntryAssistWordWrap(false);
|
||||
screen.setLeftMargin(0); // Col 1 (0-based 0)
|
||||
screen.setRightMargin(72); // Col 73 (0-based 72)
|
||||
|
||||
assertTrue(screen.isEntryAssistDOCmode());
|
||||
assertFalse(screen.isEntryAssistWordWrap());
|
||||
assertEquals(0, screen.getEntryAssistStartColumn());
|
||||
assertEquals(72, screen.getEntryAssistEndColumn());
|
||||
|
||||
// Setup open cursor position
|
||||
screen.setCursorPosition(0, 72); // Row 0, Col 72 (at right margin)
|
||||
inputProcessor.typeCharacter('X');
|
||||
|
||||
// Should advance to next row at left margin (Row 1, Col 0)
|
||||
assertEquals(1, screen.getCursorRow());
|
||||
assertEquals(0, screen.getCursorCol());
|
||||
assertEquals('X', (char) screen.getCell(72).ucs4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWordWrapTransfersPartialWord() {
|
||||
// Document Mode ON, Word Wrap ON
|
||||
screen.setEntryAssistDOCmode(true);
|
||||
screen.setEntryAssistWordWrap(true);
|
||||
screen.setLeftMargin(5); // Start col = 5
|
||||
screen.setRightMargin(20); // End col = 20
|
||||
|
||||
// Type "HELLO " starting at col 10
|
||||
screen.setCursorPosition(0, 10);
|
||||
for (char c : "HELLO ".toCharArray()) {
|
||||
inputProcessor.typeCharacter(c);
|
||||
}
|
||||
|
||||
// Now cursor is at col 16. Type "TEST" which crosses col 20
|
||||
screen.setCursorPosition(0, 18);
|
||||
inputProcessor.typeCharacter('W');
|
||||
inputProcessor.typeCharacter('O');
|
||||
inputProcessor.typeCharacter('R');
|
||||
inputProcessor.typeCharacter('D'); // At col 21, past end col 20
|
||||
|
||||
// Word wrap should have moved "WORD" to next line starting at left margin 5
|
||||
assertEquals(1, screen.getCursorRow());
|
||||
assertTrue(screen.getCursorCol() >= 5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDocModeTabStopsNavigation() {
|
||||
screen.setEntryAssistDOCmode(true);
|
||||
screen.setLeftMargin(0);
|
||||
screen.setRightMargin(79);
|
||||
screen.setEntryAssistTabStops(new int[]{0, 10, 20, 30});
|
||||
|
||||
screen.setCursorPosition(0, 0);
|
||||
|
||||
// Word tab forward
|
||||
screen.processWordTab(true);
|
||||
assertEquals(10, screen.getCursorCol());
|
||||
assertEquals(0, screen.getCursorRow());
|
||||
|
||||
screen.processWordTab(true);
|
||||
assertEquals(20, screen.getCursorCol());
|
||||
|
||||
// Word back tab
|
||||
screen.processWordTab(false);
|
||||
assertEquals(10, screen.getCursorCol());
|
||||
|
||||
screen.processWordTab(false);
|
||||
assertEquals(0, screen.getCursorCol());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAudibleEndOfLineBell() {
|
||||
inputProcessor.setBellEnabled(true);
|
||||
inputProcessor.setBellColumn(74);
|
||||
|
||||
AtomicInteger bellCount = new AtomicInteger(0);
|
||||
inputProcessor.setBellListener(bellCount::incrementAndGet);
|
||||
|
||||
screen.setCursorPosition(0, 70);
|
||||
inputProcessor.typeCharacter('A'); // 71
|
||||
inputProcessor.typeCharacter('B'); // 72
|
||||
inputProcessor.typeCharacter('C'); // 73
|
||||
assertEquals(0, bellCount.get());
|
||||
|
||||
inputProcessor.typeCharacter('D'); // 74 -> triggers bell
|
||||
assertEquals(1, bellCount.get());
|
||||
|
||||
inputProcessor.typeCharacter('E'); // 75 -> does not retrigger on same line
|
||||
assertEquals(1, bellCount.get());
|
||||
|
||||
// Move to next line at col 73 and type across bell column
|
||||
screen.setCursorPosition(1, 73);
|
||||
inputProcessor.typeCharacter('Z'); // advances to 74 -> triggers bell on next line
|
||||
assertEquals(2, bellCount.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertModeResetOnAid() {
|
||||
inputProcessor.setInsertOffOnAid(true);
|
||||
inputProcessor.setInsertMode(true);
|
||||
assertTrue(inputProcessor.isInsertMode());
|
||||
|
||||
// Send AID_ENTER
|
||||
inputProcessor.sendAid(AID_ENTER);
|
||||
assertFalse(inputProcessor.isInsertMode(), "Insert mode should be reset after AID key");
|
||||
|
||||
// When insertOffOnAid is disabled
|
||||
inputProcessor.setInsertOffOnAid(false);
|
||||
inputProcessor.setInsertMode(true);
|
||||
inputProcessor.sendAid(AID_ENTER);
|
||||
assertTrue(inputProcessor.isInsertMode(), "Insert mode should be preserved when insertOffOnAid is false");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAplKeyboardModeAndOia() {
|
||||
inputProcessor.setAplKeyboardMode(true);
|
||||
assertTrue(inputProcessor.isAplKeyboardMode());
|
||||
|
||||
// OIA status flag includes STATE_APL
|
||||
long statusEx = oia.GetStatusFlagsEx();
|
||||
assertEquals(ECLOIA.STATE_APL, statusEx & ECLOIA.STATE_APL);
|
||||
assertTrue(oia.isApl());
|
||||
|
||||
// Test APL translation for key 'a' (alpha -> 0x41 with CS_GE)
|
||||
screen.setCursorPosition(0, 0);
|
||||
inputProcessor.typeCharacter('a');
|
||||
|
||||
var cell = screen.getCell(0);
|
||||
assertEquals(CS_GE, cell.cs, "APL character must be stored with CS_GE (Graphic Escape) charset");
|
||||
|
||||
// Toggle APL mode off
|
||||
inputProcessor.toggleAplKeyboardMode();
|
||||
assertFalse(inputProcessor.isAplKeyboardMode());
|
||||
assertFalse(oia.isApl());
|
||||
assertEquals(0, oia.GetStatusFlagsEx() & ECLOIA.STATE_APL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNumericFieldLockSetting() {
|
||||
// Field: pos 0 is numeric FA, pos 1..5 unprotected, pos 6 protect FA
|
||||
screen.setFieldAttribute(0, (byte) FA_NUMERIC);
|
||||
screen.setFieldAttribute(6, (byte) FA_PROTECT);
|
||||
screen.setCursorAddress(1);
|
||||
|
||||
// Case 1: NumericFieldLock is true
|
||||
inputProcessor.setNumericFieldLock(true);
|
||||
inputProcessor.typeCharacter('X'); // invalid numeric character
|
||||
assertTrue(inputProcessor.isKeyboardLocked(), "Keyboard should be locked on non-numeric char");
|
||||
assertEquals(ECLConstants.INHIBIT_NUMERIC_ONLY, oia.getInputInhibited());
|
||||
|
||||
// Reset keyboard
|
||||
inputProcessor.reset();
|
||||
assertFalse(inputProcessor.isKeyboardLocked());
|
||||
|
||||
// Case 2: NumericFieldLock is false
|
||||
inputProcessor.setNumericFieldLock(false);
|
||||
screen.setCursorAddress(1);
|
||||
inputProcessor.typeCharacter('X');
|
||||
assertFalse(inputProcessor.isKeyboardLocked(), "Keyboard should NOT be locked when NumericFieldLock is false");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOiaModeIndicators() {
|
||||
screen.setEntryAssistDOCmode(true);
|
||||
screen.setEntryAssistWordWrap(true);
|
||||
inputProcessor.setAplKeyboardMode(true);
|
||||
inputProcessor.setInsertMode(true);
|
||||
|
||||
long flags = oia.GetStatusFlagsEx();
|
||||
assertTrue((flags & ECLOIA.STATE_DOC_MODE) != 0);
|
||||
assertTrue((flags & ECLOIA.STATE_WORDWRAP) != 0);
|
||||
assertTrue((flags & ECLOIA.STATE_APL) != 0);
|
||||
assertTrue((flags & ECLOIA.STATE_INSERT) != 0);
|
||||
|
||||
assertTrue(oia.isDocMode());
|
||||
assertTrue(oia.isWordWrap());
|
||||
assertTrue(oia.isApl());
|
||||
assertTrue(oia.isInsertMode());
|
||||
}
|
||||
}
|
||||
@@ -76,8 +76,9 @@ public class DynamicScreenBufferTest {
|
||||
assertTrue(model.isDynamic());
|
||||
assertEquals(0, model.getModelNumber());
|
||||
assertTrue(model.isColor());
|
||||
assertEquals("IBM-DYNAMIC-E", model.getTerminalType());
|
||||
assertEquals("IBM-DYNAMIC", model.getTerminalType());
|
||||
assertEquals("IBM-DYNAMIC", model.getBaseTerminalType());
|
||||
assertEquals("IBM-DYNAMIC", model.toString());
|
||||
|
||||
ScreenBuffer buffer = new ScreenBuffer(model, translator);
|
||||
assertEquals(24, buffer.getDefRows());
|
||||
|
||||
@@ -65,12 +65,14 @@ public class DynamicTelnetFSMTest {
|
||||
// Host requests TTYPE: IAC SB TTYPE SEND IAC SE
|
||||
feedBytes(255, 250, 24, 1, 255, 240);
|
||||
|
||||
// Verify sent response is IBM-DYNAMIC-E
|
||||
// Verify sent response is IBM-DYNAMIC
|
||||
assertFalse(connection.sentData.isEmpty());
|
||||
byte[] lastSent = connection.sentData.get(connection.sentData.size() - 1);
|
||||
String s = new String(lastSent);
|
||||
assertTrue(s.contains("IBM-DYNAMIC-E") || s.contains("IBM-DYNAMIC"),
|
||||
assertTrue(s.contains("IBM-DYNAMIC"),
|
||||
"Expected terminal type negotiation to send IBM-DYNAMIC, got: " + s);
|
||||
assertFalse(s.contains("IBM-DYNAMIC-E"),
|
||||
"Terminal type negotiation should not contain IBM-DYNAMIC-E, got: " + s);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user