IBM-DYNAMIC tweaking
Build and Test j3270 / Build JAR & Run Tests (push) Successful in 1m25s

This commit is contained in:
2026-09-04 20:58:19 -04:00
parent cbb310b889
commit 74e30d1ead
18 changed files with 1374 additions and 86 deletions
@@ -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
}
}
}