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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user