More functional changes and UI adaptations from overnight

This commit is contained in:
2026-08-31 09:16:32 -04:00
parent a43969da08
commit a9956298e2
33 changed files with 3817 additions and 667 deletions
@@ -53,6 +53,8 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
buildUI();
buildMenuBar();
ThemeManager.addThemeChangeListener(this::onThemeChanged);
pack();
setLocationRelativeTo(null);
setMinimumSize(new Dimension(640, 400));
@@ -97,10 +99,16 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
getContentPane().add(statusBar, BorderLayout.SOUTH);
}
private void onThemeChanged(UITheme theme) {
buildMenuBar();
statusBar.applyTheme(theme);
ThemeManager.applyThemeToWindow(this);
terminalPanel.repaint();
}
private void buildMenuBar() {
JMenuBar menuBar = new JMenuBar();
menuBar.setBackground(new Color(30, 30, 30));
menuBar.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, new Color(50, 50, 50)));
ThemeManager.styleMenuBar(menuBar);
int shortcutMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
@@ -145,6 +153,31 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
}));
viewMenu.addSeparator();
// UI Theme Submenu
JMenu themeMenu = createMenu("UI Theme");
ButtonGroup themeGroup = new ButtonGroup();
JRadioButtonMenuItem darkThemeItem = new JRadioButtonMenuItem("Dark Mode", ThemeManager.isDark());
ThemeManager.styleMenuItem(darkThemeItem);
darkThemeItem.addActionListener(e -> {
haus.nightmare.j3270.config.Settings.setJavaUiTheme(UITheme.DARK);
ThemeManager.setTheme(UITheme.DARK);
});
themeGroup.add(darkThemeItem);
themeMenu.add(darkThemeItem);
JRadioButtonMenuItem lightThemeItem = new JRadioButtonMenuItem("Light Mode", ThemeManager.isLight());
ThemeManager.styleMenuItem(lightThemeItem);
lightThemeItem.addActionListener(e -> {
haus.nightmare.j3270.config.Settings.setJavaUiTheme(UITheme.LIGHT);
ThemeManager.setTheme(UITheme.LIGHT);
});
themeGroup.add(lightThemeItem);
themeMenu.add(lightThemeItem);
viewMenu.add(themeMenu);
viewMenu.addSeparator();
// CodePage Submenu
JMenu cpMenu = createMenu("Code Page");
String[] codePages = {
@@ -174,8 +207,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
for (String cpItem : codePages) {
String cpId = cpItem.split(" -")[0].trim();
JMenuItem cpMi = new JMenuItem(cpItem);
cpMi.setBackground(new Color(40, 40, 40));
cpMi.setForeground(new Color(200, 200, 200));
ThemeManager.styleMenuItem(cpMi);
cpMi.addActionListener(e -> changeCodePage(cpId));
cpMenu.add(cpMi);
}
@@ -185,8 +217,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
JMenu gfxMenu = createMenu("Graphics Mode");
for (GraphicsMode gm : GraphicsMode.values()) {
JMenuItem gmMi = new JMenuItem(gm.name());
gmMi.setBackground(new Color(40, 40, 40));
gmMi.setForeground(new Color(200, 200, 200));
ThemeManager.styleMenuItem(gmMi);
gmMi.addActionListener(e -> changeGraphicsMode(gm));
gfxMenu.add(gmMi);
}
@@ -257,14 +288,12 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
private JMenu createMenu(String name) {
JMenu menu = new JMenu(name);
menu.setForeground(new Color(200, 200, 200));
return menu;
return ThemeManager.styleMenu(menu);
}
private JMenuItem createMenuItem(String name, int mnemonic, Runnable action, boolean useAlt) {
JMenuItem item = new JMenuItem(name);
item.setBackground(new Color(40, 40, 40));
item.setForeground(new Color(200, 200, 200));
ThemeManager.styleMenuItem(item);
if (mnemonic > 0) {
int modifier = useAlt ? KeyEvent.ALT_DOWN_MASK : Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
item.setAccelerator(KeyStroke.getKeyStroke(mnemonic, modifier));
@@ -693,9 +722,9 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
JTextArea area = new JTextArea(text);
area.setEditable(false);
area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 13));
area.setBackground(new Color(30, 30, 30));
area.setForeground(new Color(200, 200, 200));
ThemeManager.styleTextArea(area);
JScrollPane sp = new JScrollPane(area);
ThemeManager.styleScrollPane(sp);
sp.setPreferredSize(new Dimension(380, 430));
JOptionPane.showMessageDialog(this, sp, "Key Mappings", JOptionPane.INFORMATION_MESSAGE);
}
@@ -726,9 +755,9 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
JTextArea area = new JTextArea(text);
area.setEditable(false);
area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 13));
area.setBackground(new Color(30, 30, 30));
area.setForeground(new Color(200, 200, 200));
ThemeManager.styleTextArea(area);
JScrollPane sp = new JScrollPane(area);
ThemeManager.styleScrollPane(sp);
sp.setPreferredSize(new Dimension(440, 430));
JOptionPane.showMessageDialog(this, sp, "Mnemonic Keystroke Reference", JOptionPane.INFORMATION_MESSAGE);
}
@@ -758,45 +787,47 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
Boolean cliTn3270e = null;
GraphicsMode cliGraphicsMode = null;
String configFile = null;
java.util.List<String> remainingArgs = new java.util.ArrayList<>();
for (int i = 0; i < args.length; i++) {
if ("--debug".equals(args[i]) || "-d".equals(args[i])) {
String arg = args[i];
if ("-d".equals(arg) || "--debug".equals(arg)) {
debug = true;
} else if ("--tls".equals(args[i]) || "--ssl".equals(args[i]) || "-s".equals(args[i])) {
} else if ("-s".equals(arg) || "--tls".equals(arg) || "--ssl".equals(arg)) {
cliTls = true;
} else if ("--no-verify-cert".equals(args[i]) || "--insecure".equals(args[i]) || "-k".equals(args[i])) {
} else if ("--no-verify-cert".equals(arg) || "--insecure".equals(arg) || "-k".equals(arg)) {
cliNoVerifyCert = true;
} else if ("--no-tn3270e".equals(args[i]) || "--plain-tn3270".equals(args[i]) || "--plain".equals(args[i]) || "-P".equals(args[i]) || "-p".equals(args[i]) || "--non-e".equals(args[i])) {
} else if ("--no-tn3270e".equals(arg) || "--plain-tn3270".equals(arg) || "--plain".equals(arg) || "-P".equals(arg) || "-p".equals(arg) || "--non-e".equals(arg)) {
cliTn3270e = false;
} else if ("--tn3270e".equals(args[i])) {
} else if ("--tn3270e".equals(arg)) {
cliTn3270e = true;
} else if (args[i].startsWith("--graphics=")) {
cliGraphicsMode = GraphicsMode.fromString(args[i].substring(11));
} else if (("--graphics".equals(args[i]) || "-g".equals(args[i])) && i + 1 < args.length && !args[i + 1].startsWith("-")) {
} else if (arg.startsWith("--graphics=")) {
cliGraphicsMode = GraphicsMode.fromString(arg.substring(11));
} else if (("--graphics".equals(arg) || "-g".equals(arg)) && i + 1 < args.length && !args[i + 1].startsWith("-")) {
cliGraphicsMode = GraphicsMode.fromString(args[++i]);
} else if ("--no-graphics".equals(args[i])) {
} else if ("--no-graphics".equals(arg)) {
cliGraphicsMode = GraphicsMode.NONE;
} else if (("-c".equals(args[i]) || "--config".equals(args[i])) && i + 1 < args.length) {
} else if (("-c".equals(arg) || "--config".equals(arg)) && i + 1 < args.length) {
configFile = args[++i];
} else if (arg.startsWith("-")) {
System.err.println("Unknown option: " + arg);
} else {
remainingArgs.add(args[i]);
remainingArgs.add(arg);
}
}
Level logLevel = debug ? Level.ALL : Level.INFO;
Level logLevel = debug ? Level.FINE : Level.INFO;
Logger globalRoot = Logger.getLogger("");
for (java.util.logging.Handler h : globalRoot.getHandlers()) {
globalRoot.removeHandler(h);
}
globalRoot.setLevel(Level.ALL);
java.util.logging.Filter appFilter = record -> {
String name = record.getLoggerName();
return name != null && (name.startsWith("haus.nightmare.lib3270j") || name.startsWith("haus.nightmare.j3270"));
};
java.util.logging.Filter appFilter = record -> record.getLoggerName() != null &&
(record.getLoggerName().startsWith("haus.nightmare") || record.getLoggerName().startsWith("org.pubvm"));
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setLevel(logLevel);
consoleHandler.setLevel(Level.ALL);
consoleHandler.setFormatter(new SimpleFormatter());
consoleHandler.setFilter(appFilter);
globalRoot.addHandler(consoleHandler);
@@ -837,6 +868,8 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
} catch (Exception e) {
log.fine("Could not set system look and feel");
}
ThemeManager.applyUIManagerDefaults(haus.nightmare.j3270.config.Settings.getJavaUiTheme());
ThemeManager.setTheme(haus.nightmare.j3270.config.Settings.getJavaUiTheme());
System.setProperty("apple.laf.useScreenMenuBar", "true");
System.setProperty("apple.awt.application.name", "j3270");
@@ -1,5 +1,6 @@
package haus.nightmare.j3270.config;
import haus.nightmare.j3270.ui.UITheme;
import java.util.prefs.Preferences;
import java.awt.Color;
import java.io.*;
@@ -17,6 +18,15 @@ public class Settings {
AUTO_CONNECT
}
public static UITheme getJavaUiTheme() {
String themeStr = prefs.get("javaUiTheme", UITheme.DARK.name());
return UITheme.fromString(themeStr);
}
public static void setJavaUiTheme(UITheme theme) {
prefs.put("javaUiTheme", (theme != null ? theme : UITheme.DARK).name());
}
public static String getFontFamily() {
return prefs.get("fontFamily", "Monospaced");
}
@@ -229,6 +239,11 @@ public class Settings {
switch (key) {
case "fontFamily": setFontFamily(value); break;
case "fontSize": setFontSize(Integer.parseInt(value)); break;
case "javaUiTheme":
case "theme":
case "uiTheme":
setJavaUiTheme(UITheme.fromString(value));
break;
default:
log.warning("Unknown appearance key: " + key);
}
@@ -322,6 +337,7 @@ public class Settings {
// [appearance]
w.println("[appearance]");
w.println("javaUiTheme = " + getJavaUiTheme().name());
w.println("fontFamily = " + getFontFamily());
w.println("fontSize = " + getFontSize());
w.println();
@@ -1,6 +1,7 @@
package haus.nightmare.j3270.ft;
import haus.nightmare.j3270.ui.HostDirectoryDialog;
import haus.nightmare.j3270.ui.ThemeManager;
import haus.nightmare.lib3270j.ft.FTConfig;
import haus.nightmare.lib3270j.ft.FTConstants;
@@ -88,6 +89,7 @@ public class FileTransferDialog extends JDialog {
// Local File
localFileField = new JTextField(20);
browseLocalButton = new JButton("Browse...");
ThemeManager.styleButton(browseLocalButton, ThemeManager.ButtonVariant.DEFAULT);
browseLocalButton.addActionListener(e -> browseLocalFile());
JPanel localPanel = new JPanel(new BorderLayout(5, 0));
localPanel.setOpaque(false);
@@ -98,6 +100,7 @@ public class FileTransferDialog extends JDialog {
// Host File
hostFileField = new JTextField(20);
browseHostButton = new JButton("Browse Host...");
ThemeManager.styleButton(browseHostButton, ThemeManager.ButtonVariant.DEFAULT);
browseHostButton.addActionListener(e -> browseHostDirectory());
JPanel hostPanel = new JPanel(new BorderLayout(5, 0));
hostPanel.setOpaque(false);
@@ -154,10 +157,7 @@ public class FileTransferDialog extends JDialog {
// Host-specific options panel (TSO dataset allocation)
JPanel hostOptsPanel = new JPanel(new GridLayout(2, 4, 5, 5));
hostOptsPanel.setOpaque(false);
hostOptsPanel.setBorder(BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(new Color(60, 60, 60)),
"TSO Allocation Options (Send Only)"));
((javax.swing.border.TitledBorder)hostOptsPanel.getBorder()).setTitleColor(new Color(180, 180, 180));
hostOptsPanel.setBorder(ThemeManager.createTitledBorder("TSO Allocation Options (Send Only)"));
hostOptsPanel.add(new JLabel("RECFM:"));
recfmField = new JTextField(5);
@@ -181,10 +181,7 @@ public class FileTransferDialog extends JDialog {
// CMS options
JPanel vmOptsPanel = new JPanel(new BorderLayout(5, 0));
vmOptsPanel.setOpaque(false);
vmOptsPanel.setBorder(BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(new Color(60, 60, 60)),
"CMS Options"));
((javax.swing.border.TitledBorder)vmOptsPanel.getBorder()).setTitleColor(new Color(180, 180, 180));
vmOptsPanel.setBorder(ThemeManager.createTitledBorder("CMS Options"));
optionsField = new JTextField(20);
vmOptsPanel.add(new JLabel("Additional Options: "), BorderLayout.WEST);
@@ -200,9 +197,11 @@ public class FileTransferDialog extends JDialog {
buttonPanel.setOpaque(false);
transferButton = new JButton("Start Transfer");
ThemeManager.styleButton(transferButton, ThemeManager.ButtonVariant.PRIMARY);
transferButton.addActionListener(e -> startTransfer());
cancelButton = new JButton("Close");
ThemeManager.styleButton(cancelButton, ThemeManager.ButtonVariant.CANCEL);
cancelButton.addActionListener(e -> dispose());
buttonPanel.add(cancelButton);
@@ -212,7 +211,7 @@ public class FileTransferDialog extends JDialog {
setContentPane(mainPanel);
applyTheme(mainPanel);
ThemeManager.applyThemeToWindow(this);
updateOptionStates();
}
@@ -34,24 +34,21 @@ public class ConnectDialog extends JDialog {
private void buildUI() {
JPanel mainPanel = new JPanel(new GridBagLayout());
mainPanel.setBorder(BorderFactory.createEmptyBorder(16, 16, 16, 16));
mainPanel.setBackground(new Color(30, 30, 30));
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(4, 4, 4, 4);
gbc.insets = new Insets(5, 6, 5, 6);
gbc.fill = GridBagConstraints.HORIZONTAL;
Font labelFont = new Font(Font.SANS_SERIF, Font.PLAIN, 14);
Color fg = new Color(200, 200, 200);
Font labelFont = new Font(Font.SANS_SERIF, Font.PLAIN, 13);
// Host
gbc.gridx = 0;
gbc.gridy = 0;
JLabel hostLabel = new JLabel("Host:");
hostLabel.setForeground(fg);
hostLabel.setFont(labelFont);
mainPanel.add(hostLabel, gbc);
gbc.gridx = 1;
gbc.weightx = 1.0;
hostField = createDarkField(20);
hostField = createField(20);
mainPanel.add(hostField, gbc);
// Port
@@ -59,12 +56,11 @@ public class ConnectDialog extends JDialog {
gbc.gridy = 1;
gbc.weightx = 0;
JLabel portLabel = new JLabel("Port:");
portLabel.setForeground(fg);
portLabel.setFont(labelFont);
mainPanel.add(portLabel, gbc);
gbc.gridx = 1;
gbc.weightx = 1.0;
portField = createDarkField(6);
portField = createField(6);
portField.setText("23");
mainPanel.add(portField, gbc);
@@ -73,16 +69,14 @@ public class ConnectDialog extends JDialog {
gbc.gridy = 2;
gbc.weightx = 0;
JLabel modelLabel = new JLabel("Model:");
modelLabel.setForeground(fg);
modelLabel.setFont(labelFont);
mainPanel.add(modelLabel, gbc);
gbc.gridx = 1;
gbc.weightx = 1.0;
modelCombo = new JComboBox<>(TerminalModel.values());
modelCombo.setSelectedItem(TerminalModel.IBM_3279_4);
modelCombo.setBackground(new Color(45, 45, 45));
modelCombo.setForeground(fg);
modelCombo.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 13));
ThemeManager.styleComboBox(modelCombo);
mainPanel.add(modelCombo, gbc);
// LU Name
@@ -90,12 +84,11 @@ public class ConnectDialog extends JDialog {
gbc.gridy = 3;
gbc.weightx = 0;
JLabel luLabel = new JLabel("LU Name:");
luLabel.setForeground(fg);
luLabel.setFont(labelFont);
mainPanel.add(luLabel, gbc);
gbc.gridx = 1;
gbc.weightx = 1.0;
luField = createDarkField(12);
luField = createField(12);
mainPanel.add(luField, gbc);
// Graphics Mode
@@ -103,16 +96,14 @@ public class ConnectDialog extends JDialog {
gbc.gridy = 4;
gbc.weightx = 0;
JLabel graphicsLabel = new JLabel("Graphics:");
graphicsLabel.setForeground(fg);
graphicsLabel.setFont(labelFont);
mainPanel.add(graphicsLabel, gbc);
gbc.gridx = 1;
gbc.weightx = 1.0;
graphicsCombo = new JComboBox<>(haus.nightmare.lib3270j.graphics.GraphicsMode.values());
graphicsCombo.setSelectedItem(haus.nightmare.j3270.config.Settings.getGraphicsMode());
graphicsCombo.setBackground(new Color(45, 45, 45));
graphicsCombo.setForeground(fg);
graphicsCombo.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 13));
ThemeManager.styleComboBox(graphicsCombo);
mainPanel.add(graphicsCombo, gbc);
// Code Page
@@ -120,7 +111,6 @@ public class ConnectDialog extends JDialog {
gbc.gridy = 5;
gbc.weightx = 0;
JLabel cpLabel = new JLabel("Code Page:");
cpLabel.setForeground(fg);
cpLabel.setFont(labelFont);
mainPanel.add(cpLabel, gbc);
gbc.gridx = 1;
@@ -158,9 +148,8 @@ public class ConnectDialog extends JDialog {
break;
}
}
codePageCombo.setBackground(new Color(45, 45, 45));
codePageCombo.setForeground(fg);
codePageCombo.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 13));
ThemeManager.styleComboBox(codePageCombo);
mainPanel.add(codePageCombo, gbc);
// TLS / SSL Checkbox
@@ -168,10 +157,8 @@ public class ConnectDialog extends JDialog {
gbc.gridy = 6;
gbc.weightx = 1.0;
tlsCheckBox = new JCheckBox("Enable TLS/SSL");
tlsCheckBox.setBackground(new Color(30, 30, 30));
tlsCheckBox.setForeground(fg);
ThemeManager.styleCheckBox(tlsCheckBox);
tlsCheckBox.setFont(labelFont);
tlsCheckBox.setFocusPainted(false);
tlsCheckBox.addActionListener(e -> {
boolean isTls = tlsCheckBox.isSelected();
verifyCertCheckBox.setEnabled(isTls);
@@ -188,38 +175,31 @@ public class ConnectDialog extends JDialog {
gbc.gridx = 1;
gbc.gridy = 7;
verifyCertCheckBox = new JCheckBox("Verify Server Certificate");
verifyCertCheckBox.setBackground(new Color(30, 30, 30));
verifyCertCheckBox.setForeground(new Color(160, 160, 160));
ThemeManager.styleCheckBox(verifyCertCheckBox);
verifyCertCheckBox.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 13));
verifyCertCheckBox.setSelected(true);
verifyCertCheckBox.setEnabled(false);
verifyCertCheckBox.setFocusPainted(false);
mainPanel.add(verifyCertCheckBox, gbc);
// TN3270E Checkbox
gbc.gridx = 1;
gbc.gridy = 8;
tn3270eCheckBox = new JCheckBox("Enable TN3270E (Extended 3270)");
tn3270eCheckBox.setBackground(new Color(30, 30, 30));
tn3270eCheckBox.setForeground(fg);
ThemeManager.styleCheckBox(tn3270eCheckBox);
tn3270eCheckBox.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 13));
tn3270eCheckBox.setSelected(haus.nightmare.j3270.config.Settings.getAutoConnectTn3270e());
tn3270eCheckBox.setFocusPainted(false);
mainPanel.add(tn3270eCheckBox, gbc);
// Buttons
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
buttonPanel.setBackground(new Color(30, 30, 30));
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 8, 4));
JButton connectBtn = new JButton("Connect");
connectBtn.setBackground(new Color(50, 120, 50));
connectBtn.setForeground(Color.WHITE);
ThemeManager.styleButton(connectBtn, ThemeManager.ButtonVariant.PRIMARY);
connectBtn.setFont(labelFont);
connectBtn.addActionListener(e -> onConnect());
JButton cancelBtn = new JButton("Cancel");
cancelBtn.setBackground(new Color(60, 60, 60));
cancelBtn.setForeground(fg);
ThemeManager.styleButton(cancelBtn, ThemeManager.ButtonVariant.CANCEL);
cancelBtn.setFont(labelFont);
cancelBtn.addActionListener(e -> {
confirmed = false;
@@ -235,20 +215,16 @@ public class ConnectDialog extends JDialog {
mainPanel.add(buttonPanel, gbc);
setContentPane(mainPanel);
ThemeManager.applyThemeToWindow(this);
// Enter key triggers connect
getRootPane().setDefaultButton(connectBtn);
}
private JTextField createDarkField(int cols) {
private JTextField createField(int cols) {
JTextField field = new JTextField(cols);
field.setBackground(new Color(45, 45, 45));
field.setForeground(new Color(200, 200, 200));
field.setCaretColor(new Color(200, 200, 200));
field.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
field.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(60, 60, 60)),
BorderFactory.createEmptyBorder(4, 6, 4, 6)));
ThemeManager.styleTextField(field);
return field;
}
@@ -28,12 +28,6 @@ public class FieldInspectorDialog extends JDialog implements ScreenUpdateListene
private JTable table;
private JLabel countLabel;
private static final Color DARK_BG = new Color(35, 35, 35);
private static final Color DARK_FIELD_BG = new Color(45, 45, 45);
private static final Color DARK_FG = new Color(220, 220, 220);
private static final Color DARK_BORDER = new Color(65, 65, 65);
private static final Color DARK_SELECTION = new Color(75, 110, 175);
public FieldInspectorDialog(Frame parent, Telnet3270Client client, TerminalPanel terminalPanel) {
super(parent, "3270 Presentation Space Field Inspector", false);
this.client = client;
@@ -61,19 +55,16 @@ public class FieldInspectorDialog extends JDialog implements ScreenUpdateListene
private void buildUI() {
JPanel mainPanel = new JPanel(new BorderLayout(8, 8));
mainPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
mainPanel.setBackground(DARK_BG);
// Top info bar
JPanel topPanel = new JPanel(new BorderLayout());
topPanel.setOpaque(false);
countLabel = new JLabel("0 fields detected on screen");
countLabel.setForeground(DARK_FG);
countLabel.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 13));
topPanel.add(countLabel, BorderLayout.WEST);
JButton refreshBtn = new JButton("Refresh");
refreshBtn.setBackground(new Color(55, 55, 55));
refreshBtn.setForeground(DARK_FG);
ThemeManager.styleButton(refreshBtn, ThemeManager.ButtonVariant.DEFAULT);
refreshBtn.addActionListener(e -> refreshFields());
topPanel.add(refreshBtn, BorderLayout.EAST);
mainPanel.add(topPanel, BorderLayout.NORTH);
@@ -91,18 +82,12 @@ public class FieldInspectorDialog extends JDialog implements ScreenUpdateListene
};
table = new JTable(tableModel);
table.setBackground(DARK_FIELD_BG);
table.setForeground(DARK_FG);
table.setSelectionBackground(DARK_SELECTION);
table.setSelectionForeground(Color.WHITE);
table.setGridColor(DARK_BORDER);
ThemeManager.styleTable(table);
table.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.setRowHeight(20);
JTableHeader header = table.getTableHeader();
header.setBackground(new Color(50, 50, 50));
header.setForeground(DARK_FG);
header.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 12));
// Column widths
@@ -125,8 +110,7 @@ public class FieldInspectorDialog extends JDialog implements ScreenUpdateListene
});
JScrollPane scrollPane = new JScrollPane(table);
scrollPane.setBorder(new LineBorder(DARK_BORDER));
scrollPane.getViewport().setBackground(DARK_FIELD_BG);
ThemeManager.styleScrollPane(scrollPane);
mainPanel.add(scrollPane, BorderLayout.CENTER);
// Bottom panel
@@ -134,8 +118,7 @@ public class FieldInspectorDialog extends JDialog implements ScreenUpdateListene
bottomPanel.setOpaque(false);
JButton jumpBtn = new JButton("Jump to Selected Field");
jumpBtn.setBackground(new Color(50, 100, 160));
jumpBtn.setForeground(Color.WHITE);
ThemeManager.styleButton(jumpBtn, ThemeManager.ButtonVariant.PRIMARY);
jumpBtn.addActionListener(e -> {
int row = table.getSelectedRow();
if (row >= 0) {
@@ -144,8 +127,7 @@ public class FieldInspectorDialog extends JDialog implements ScreenUpdateListene
});
JButton closeBtn = new JButton("Close");
closeBtn.setBackground(new Color(55, 55, 55));
closeBtn.setForeground(DARK_FG);
ThemeManager.styleButton(closeBtn, ThemeManager.ButtonVariant.CANCEL);
closeBtn.addActionListener(e -> dispose());
bottomPanel.add(jumpBtn);
@@ -153,6 +135,7 @@ public class FieldInspectorDialog extends JDialog implements ScreenUpdateListene
mainPanel.add(bottomPanel, BorderLayout.SOUTH);
setContentPane(mainPanel);
ThemeManager.applyThemeToWindow(this);
}
public synchronized void refreshFields() {
@@ -42,7 +42,6 @@ public class FindDialog extends JDialog {
private void buildUI() {
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
mainPanel.setBorder(new EmptyBorder(12, 14, 12, 14));
mainPanel.setBackground(new Color(35, 35, 35));
// Form
JPanel formPanel = new JPanel(new GridBagLayout());
@@ -51,14 +50,12 @@ public class FindDialog extends JDialog {
gbc.insets = new Insets(4, 4, 4, 4);
gbc.fill = GridBagConstraints.HORIZONTAL;
Color fg = new Color(220, 220, 220);
Font labelFont = new Font(Font.SANS_SERIF, Font.PLAIN, 13);
// Search text
gbc.gridx = 0;
gbc.gridy = 0;
JLabel findLabel = new JLabel("Find what:");
findLabel.setForeground(fg);
findLabel.setFont(labelFont);
formPanel.add(findLabel, gbc);
@@ -66,23 +63,16 @@ public class FindDialog extends JDialog {
gbc.weightx = 1.0;
searchField = new JTextField(20);
searchField.setText(lastSearchText);
searchField.setBackground(new Color(50, 50, 50));
searchField.setForeground(fg);
searchField.setCaretColor(fg);
searchField.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 13));
searchField.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(new Color(70, 70, 70)),
BorderFactory.createEmptyBorder(3, 6, 3, 6)));
ThemeManager.styleTextField(searchField);
formPanel.add(searchField, gbc);
// Options
gbc.gridx = 1;
gbc.gridy = 1;
matchCaseCheck = new JCheckBox("Match case", lastMatchCase);
matchCaseCheck.setForeground(fg);
matchCaseCheck.setOpaque(false);
ThemeManager.styleCheckBox(matchCaseCheck);
matchCaseCheck.setFont(labelFont);
matchCaseCheck.setFocusPainted(false);
formPanel.add(matchCaseCheck, gbc);
// Direction
@@ -91,16 +81,11 @@ public class FindDialog extends JDialog {
JPanel dirPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
dirPanel.setOpaque(false);
JLabel dirLabel = new JLabel("Direction: ");
dirLabel.setForeground(fg);
dirLabel.setFont(labelFont);
forwardRadio = new JRadioButton("Down", lastForward);
backwardRadio = new JRadioButton("Up", !lastForward);
forwardRadio.setForeground(fg);
backwardRadio.setForeground(fg);
forwardRadio.setOpaque(false);
backwardRadio.setOpaque(false);
forwardRadio.setFocusPainted(false);
backwardRadio.setFocusPainted(false);
ThemeManager.styleRadioButton(forwardRadio);
ThemeManager.styleRadioButton(backwardRadio);
ButtonGroup bg = new ButtonGroup();
bg.add(forwardRadio);
@@ -116,7 +101,7 @@ public class FindDialog extends JDialog {
gbc.gridy = 3;
gbc.gridwidth = 2;
statusLabel = new JLabel(" ");
statusLabel.setForeground(new Color(255, 120, 120));
statusLabel.setForeground(ThemeManager.getOiaFgAlert());
statusLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12));
formPanel.add(statusLabel, gbc);
@@ -127,14 +112,12 @@ public class FindDialog extends JDialog {
buttonPanel.setOpaque(false);
JButton findNextBtn = new JButton("Find Next");
findNextBtn.setBackground(new Color(60, 63, 65));
findNextBtn.setForeground(fg);
ThemeManager.styleButton(findNextBtn, ThemeManager.ButtonVariant.PRIMARY);
findNextBtn.setFont(labelFont);
findNextBtn.addActionListener(e -> findNext());
JButton closeBtn = new JButton("Close");
closeBtn.setBackground(new Color(60, 63, 65));
closeBtn.setForeground(fg);
ThemeManager.styleButton(closeBtn, ThemeManager.ButtonVariant.CANCEL);
closeBtn.setFont(labelFont);
closeBtn.addActionListener(e -> dispose());
@@ -144,6 +127,7 @@ public class FindDialog extends JDialog {
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
setContentPane(mainPanel);
ThemeManager.applyThemeToWindow(this);
getRootPane().setDefaultButton(findNextBtn);
searchField.addKeyListener(new KeyAdapter() {
@@ -49,9 +49,7 @@ public class HostDirectoryDialog extends JDialog {
private void buildUI(String initialQuery) {
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
mainPanel.setBorder(new EmptyBorder(12, 12, 12, 12));
mainPanel.setBackground(new Color(35, 35, 35));
Color fg = new Color(220, 220, 220);
Font font = new Font(Font.SANS_SERIF, Font.PLAIN, 13);
// Top Query bar
@@ -62,7 +60,6 @@ public class HostDirectoryDialog extends JDialog {
gbc.fill = GridBagConstraints.HORIZONTAL;
JLabel envLabel = new JLabel("System:");
envLabel.setForeground(fg);
envLabel.setFont(font);
topPanel.add(envLabel, gbc);
@@ -70,33 +67,25 @@ public class HostDirectoryDialog extends JDialog {
hostTypeCombo = new JComboBox<>(new FTConfig.HostType[]{FTConfig.HostType.TSO, FTConfig.HostType.CMS});
if (initialHostType == FTConfig.HostType.CMS) hostTypeCombo.setSelectedItem(FTConfig.HostType.CMS);
else hostTypeCombo.setSelectedItem(FTConfig.HostType.TSO);
hostTypeCombo.setBackground(new Color(50, 50, 50));
hostTypeCombo.setForeground(fg);
ThemeManager.styleComboBox(hostTypeCombo);
topPanel.add(hostTypeCombo, gbc);
gbc.gridx = 2;
JLabel queryLabel = new JLabel("Query Pattern / Text:");
queryLabel.setForeground(fg);
queryLabel.setFont(font);
topPanel.add(queryLabel, gbc);
gbc.gridx = 3;
gbc.weightx = 1.0;
queryField = new JTextField(initialQuery != null ? initialQuery : "", 16);
queryField.setBackground(new Color(50, 50, 50));
queryField.setForeground(fg);
queryField.setCaretColor(fg);
queryField.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 13));
queryField.setBorder(BorderFactory.createCompoundBorder(
new LineBorder(new Color(70, 70, 70)),
new EmptyBorder(3, 5, 3, 5)));
ThemeManager.styleTextField(queryField);
topPanel.add(queryField, gbc);
gbc.gridx = 4;
gbc.weightx = 0;
JButton parseBtn = new JButton("Query / Parse");
parseBtn.setBackground(new Color(55, 55, 55));
parseBtn.setForeground(fg);
ThemeManager.styleButton(parseBtn, ThemeManager.ButtonVariant.DEFAULT);
parseBtn.setFont(font);
parseBtn.addActionListener(e -> runQuery());
topPanel.add(parseBtn, gbc);
@@ -106,18 +95,12 @@ public class HostDirectoryDialog extends JDialog {
// Table
tableModel = new DefaultTableModel();
table = new JTable(tableModel);
table.setBackground(new Color(45, 45, 45));
table.setForeground(fg);
table.setSelectionBackground(new Color(75, 110, 175));
table.setSelectionForeground(Color.WHITE);
table.setGridColor(new Color(65, 65, 65));
ThemeManager.styleTable(table);
table.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.setRowHeight(20);
JTableHeader header = table.getTableHeader();
header.setBackground(new Color(50, 50, 50));
header.setForeground(fg);
header.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 12));
table.addMouseListener(new MouseAdapter() {
@@ -130,8 +113,7 @@ public class HostDirectoryDialog extends JDialog {
});
JScrollPane scrollPane = new JScrollPane(table);
scrollPane.setBorder(new LineBorder(new Color(65, 65, 65)));
scrollPane.getViewport().setBackground(new Color(45, 45, 45));
ThemeManager.styleScrollPane(scrollPane);
mainPanel.add(scrollPane, BorderLayout.CENTER);
// Bottom
@@ -139,7 +121,7 @@ public class HostDirectoryDialog extends JDialog {
bottomPanel.setOpaque(false);
statusLabel = new JLabel("Enter a dataset pattern or parse active screen.");
statusLabel.setForeground(new Color(170, 170, 170));
statusLabel.setForeground(ThemeManager.getFgMuted());
statusLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12));
bottomPanel.add(statusLabel, BorderLayout.WEST);
@@ -147,20 +129,17 @@ public class HostDirectoryDialog extends JDialog {
btnPanel.setOpaque(false);
JButton pasteScreenBtn = new JButton("Parse Current Screen");
pasteScreenBtn.setBackground(new Color(50, 50, 50));
pasteScreenBtn.setForeground(fg);
ThemeManager.styleButton(pasteScreenBtn, ThemeManager.ButtonVariant.DEFAULT);
pasteScreenBtn.setFont(font);
pasteScreenBtn.addActionListener(e -> parseCurrentScreen());
JButton selectBtn = new JButton("Select Dataset");
selectBtn.setBackground(new Color(50, 120, 50));
selectBtn.setForeground(Color.WHITE);
ThemeManager.styleButton(selectBtn, ThemeManager.ButtonVariant.PRIMARY);
selectBtn.setFont(font);
selectBtn.addActionListener(e -> onConfirmSelection());
JButton cancelBtn = new JButton("Cancel");
cancelBtn.setBackground(new Color(60, 60, 60));
cancelBtn.setForeground(fg);
ThemeManager.styleButton(cancelBtn, ThemeManager.ButtonVariant.CANCEL);
cancelBtn.setFont(font);
cancelBtn.addActionListener(e -> {
confirmed = false;
@@ -176,6 +155,7 @@ public class HostDirectoryDialog extends JDialog {
mainPanel.add(bottomPanel, BorderLayout.SOUTH);
setContentPane(mainPanel);
ThemeManager.applyThemeToWindow(this);
// Initial setup
setupTableColumns();
@@ -45,10 +45,6 @@ public class PrinterSessionDialog extends JDialog implements PrintSessionListene
// Spool Display
private JTextArea spoolArea;
private static final Color DARK_BG = new Color(35, 35, 35);
private static final Color DARK_FG = new Color(220, 220, 220);
private static final Color DARK_FIELD_BG = new Color(45, 45, 45);
public PrinterSessionDialog(Frame parent, String defaultHost, int defaultPort, boolean defaultTls) {
super(parent, "IBM 3287 Printer Session Manager", false);
this.parent = parent;
@@ -65,14 +61,11 @@ public class PrinterSessionDialog extends JDialog implements PrintSessionListene
private void buildUI() {
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
mainPanel.setBorder(new EmptyBorder(12, 14, 12, 14));
mainPanel.setBackground(DARK_BG);
// Top: Configuration Panel
JPanel topPanel = new JPanel(new GridBagLayout());
topPanel.setOpaque(false);
topPanel.setBorder(BorderFactory.createTitledBorder(
new LineBorder(new Color(65, 65, 65)), "Printer Session Configuration"));
((TitledBorder) topPanel.getBorder()).setTitleColor(DARK_FG);
topPanel.setBorder(ThemeManager.createTitledBorder("Printer Session Configuration"));
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(3, 4, 3, 4);
@@ -83,60 +76,59 @@ public class PrinterSessionDialog extends JDialog implements PrintSessionListene
// Host / Port
gbc.gridx = 0; gbc.gridy = 0;
JLabel hLbl = new JLabel("Host:");
hLbl.setForeground(DARK_FG); hLbl.setFont(labelFont);
hLbl.setFont(labelFont);
topPanel.add(hLbl, gbc);
gbc.gridx = 1; gbc.weightx = 1.0;
hostField = createDarkField(config.getHost() != null ? config.getHost() : "localhost", 14);
hostField = createField(config.getHost() != null ? config.getHost() : "localhost", 14);
topPanel.add(hostField, gbc);
gbc.gridx = 2; gbc.weightx = 0;
JLabel pLbl = new JLabel("Port:");
pLbl.setForeground(DARK_FG); pLbl.setFont(labelFont);
pLbl.setFont(labelFont);
topPanel.add(pLbl, gbc);
gbc.gridx = 3; gbc.weightx = 0.5;
portField = createDarkField(String.valueOf(config.getPort()), 5);
portField = createField(String.valueOf(config.getPort()), 5);
topPanel.add(portField, gbc);
// Printer LU / Display LU
gbc.gridx = 0; gbc.gridy = 1; gbc.weightx = 0;
JLabel pluLbl = new JLabel("Printer LU:");
pluLbl.setForeground(DARK_FG); pluLbl.setFont(labelFont);
pluLbl.setFont(labelFont);
topPanel.add(pluLbl, gbc);
gbc.gridx = 1; gbc.weightx = 1.0;
printerLuField = createDarkField("", 10);
printerLuField = createField("", 10);
topPanel.add(printerLuField, gbc);
gbc.gridx = 2; gbc.weightx = 0;
JLabel assocLbl = new JLabel("Assoc LU:");
assocLbl.setForeground(DARK_FG); assocLbl.setFont(labelFont);
assocLbl.setFont(labelFont);
topPanel.add(assocLbl, gbc);
gbc.gridx = 3; gbc.weightx = 0.5;
displayLuField = createDarkField("", 10);
displayLuField = createField("", 10);
topPanel.add(displayLuField, gbc);
// CodePage & TLS
gbc.gridx = 0; gbc.gridy = 2; gbc.weightx = 0;
JLabel cpLbl = new JLabel("CodePage:");
cpLbl.setForeground(DARK_FG); cpLbl.setFont(labelFont);
cpLbl.setFont(labelFont);
topPanel.add(cpLbl, gbc);
gbc.gridx = 1; gbc.weightx = 1.0;
codePageCombo = new JComboBox<>(new String[]{"037", "1047", "500", "273", "277", "278", "280", "284", "285", "297", "870", "1140"});
codePageCombo.setBackground(DARK_FIELD_BG);
codePageCombo.setForeground(DARK_FG);
ThemeManager.styleComboBox(codePageCombo);
topPanel.add(codePageCombo, gbc);
gbc.gridx = 2; gbc.gridwidth = 2;
JPanel tlsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 4, 0));
tlsPanel.setOpaque(false);
tlsCheck = new JCheckBox("TLS/SSL", config.isUseTls());
tlsCheck.setOpaque(false); tlsCheck.setForeground(DARK_FG); tlsCheck.setFocusPainted(false);
ThemeManager.styleCheckBox(tlsCheck);
verifyCertCheck = new JCheckBox("Verify Cert", config.isTlsVerifyCert());
verifyCertCheck.setOpaque(false); verifyCertCheck.setForeground(DARK_FG); verifyCertCheck.setFocusPainted(false);
ThemeManager.styleCheckBox(verifyCertCheck);
tlsPanel.add(tlsCheck);
tlsPanel.add(verifyCertCheck);
topPanel.add(tlsPanel, gbc);
@@ -144,23 +136,22 @@ public class PrinterSessionDialog extends JDialog implements PrintSessionListene
// Destination Type & Target
gbc.gridx = 0; gbc.gridy = 3; gbc.gridwidth = 1;
JLabel destLbl = new JLabel("Destination:");
destLbl.setForeground(DARK_FG); destLbl.setFont(labelFont);
destLbl.setFont(labelFont);
topPanel.add(destLbl, gbc);
gbc.gridx = 1;
destinationCombo = new JComboBox<>(PrinterConfig.DestinationType.values());
destinationCombo.setSelectedItem(PrinterConfig.DestinationType.MEMORY);
destinationCombo.setBackground(DARK_FIELD_BG);
destinationCombo.setForeground(DARK_FG);
ThemeManager.styleComboBox(destinationCombo);
topPanel.add(destinationCombo, gbc);
gbc.gridx = 2;
JLabel tgtLbl = new JLabel("Target Path:");
tgtLbl.setForeground(DARK_FG); tgtLbl.setFont(labelFont);
tgtLbl.setFont(labelFont);
topPanel.add(tgtLbl, gbc);
gbc.gridx = 3;
targetField = createDarkField("printer_output.txt", 12);
targetField = createField("printer_output.txt", 12);
topPanel.add(targetField, gbc);
// Connect / Disconnect Buttons
@@ -169,13 +160,11 @@ public class PrinterSessionDialog extends JDialog implements PrintSessionListene
connBtnPan.setOpaque(false);
connectBtn = new JButton("Start Printer Session");
connectBtn.setBackground(new Color(50, 120, 50));
connectBtn.setForeground(Color.WHITE);
ThemeManager.styleButton(connectBtn, ThemeManager.ButtonVariant.PRIMARY);
connectBtn.addActionListener(e -> startPrinterSession());
disconnectBtn = new JButton("Stop Session");
disconnectBtn.setBackground(new Color(120, 50, 50));
disconnectBtn.setForeground(Color.WHITE);
ThemeManager.styleButton(disconnectBtn, ThemeManager.ButtonVariant.DANGER);
disconnectBtn.setEnabled(false);
disconnectBtn.addActionListener(e -> stopPrinterSession());
@@ -188,9 +177,7 @@ public class PrinterSessionDialog extends JDialog implements PrintSessionListene
// Center: Spool Area & Status
JPanel centerPanel = new JPanel(new BorderLayout(6, 6));
centerPanel.setOpaque(false);
centerPanel.setBorder(BorderFactory.createTitledBorder(
new LineBorder(new Color(65, 65, 65)), "Printer Spool & Status"));
((TitledBorder) centerPanel.getBorder()).setTitleColor(DARK_FG);
centerPanel.setBorder(ThemeManager.createTitledBorder("Printer Spool & Status"));
// Status Header
JPanel statusHeader = new JPanel(new GridLayout(1, 4, 10, 0));
@@ -198,19 +185,16 @@ public class PrinterSessionDialog extends JDialog implements PrintSessionListene
statusHeader.setBorder(new EmptyBorder(0, 4, 4, 4));
statusLabel = new JLabel("Status: Disconnected");
statusLabel.setForeground(new Color(180, 180, 180));
statusLabel.setForeground(ThemeManager.getFgMuted());
statusLabel.setFont(labelFont);
sessionTypeLabel = new JLabel("Session: -");
sessionTypeLabel.setForeground(DARK_FG);
sessionTypeLabel.setFont(labelFont);
pagesLabel = new JLabel("Pages: 0");
pagesLabel.setForeground(DARK_FG);
pagesLabel.setFont(labelFont);
bytesLabel = new JLabel("Bytes: 0");
bytesLabel.setForeground(DARK_FG);
bytesLabel.setFont(labelFont);
statusHeader.add(statusLabel);
@@ -221,14 +205,12 @@ public class PrinterSessionDialog extends JDialog implements PrintSessionListene
// Spool text area
spoolArea = new JTextArea();
spoolArea.setBackground(new Color(25, 25, 25));
spoolArea.setForeground(new Color(100, 255, 100));
spoolArea.setCaretColor(Color.WHITE);
spoolArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
spoolArea.setEditable(false);
ThemeManager.styleTextArea(spoolArea);
JScrollPane spoolScroll = new JScrollPane(spoolArea);
spoolScroll.setBorder(new LineBorder(new Color(60, 60, 60)));
ThemeManager.styleScrollPane(spoolScroll);
centerPanel.add(spoolScroll, BorderLayout.CENTER);
mainPanel.add(centerPanel, BorderLayout.CENTER);
@@ -238,8 +220,7 @@ public class PrinterSessionDialog extends JDialog implements PrintSessionListene
bottomPanel.setOpaque(false);
JButton clearSpoolBtn = new JButton("Clear Spool");
clearSpoolBtn.setBackground(new Color(50, 50, 50));
clearSpoolBtn.setForeground(DARK_FG);
ThemeManager.styleButton(clearSpoolBtn, ThemeManager.ButtonVariant.DEFAULT);
clearSpoolBtn.addActionListener(e -> {
spoolArea.setText("");
if (printerSession != null && printerSession.getPD() != null) {
@@ -250,18 +231,15 @@ public class PrinterSessionDialog extends JDialog implements PrintSessionListene
});
JButton saveSpoolBtn = new JButton("Save Spool As...");
saveSpoolBtn.setBackground(new Color(50, 50, 50));
saveSpoolBtn.setForeground(DARK_FG);
ThemeManager.styleButton(saveSpoolBtn, ThemeManager.ButtonVariant.DEFAULT);
saveSpoolBtn.addActionListener(e -> saveSpool());
JButton printSpoolBtn = new JButton("Print Spool...");
printSpoolBtn.setBackground(new Color(50, 100, 160));
printSpoolBtn.setForeground(Color.WHITE);
ThemeManager.styleButton(printSpoolBtn, ThemeManager.ButtonVariant.DEFAULT);
printSpoolBtn.addActionListener(e -> printSpool());
JButton closeBtn = new JButton("Close");
closeBtn.setBackground(new Color(60, 60, 60));
closeBtn.setForeground(DARK_FG);
ThemeManager.styleButton(closeBtn, ThemeManager.ButtonVariant.CANCEL);
closeBtn.addActionListener(e -> dispose());
bottomPanel.add(clearSpoolBtn);
@@ -273,17 +251,13 @@ public class PrinterSessionDialog extends JDialog implements PrintSessionListene
mainPanel.add(bottomPanel, BorderLayout.SOUTH);
setContentPane(mainPanel);
ThemeManager.applyThemeToWindow(this);
}
private JTextField createDarkField(String text, int cols) {
private JTextField createField(String text, int cols) {
JTextField tf = new JTextField(text, cols);
tf.setBackground(DARK_FIELD_BG);
tf.setForeground(DARK_FG);
tf.setCaretColor(DARK_FG);
tf.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
tf.setBorder(BorderFactory.createCompoundBorder(
new LineBorder(new Color(65, 65, 65)),
new EmptyBorder(2, 4, 2, 4)));
ThemeManager.styleTextField(tf);
return tf;
}
@@ -34,9 +34,7 @@ public class ScriptDialog extends JDialog {
private void buildUI() {
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
mainPanel.setBorder(new EmptyBorder(12, 14, 12, 14));
mainPanel.setBackground(new Color(35, 35, 35));
Color fg = new Color(220, 220, 220);
Font labelFont = new Font(Font.SANS_SERIF, Font.PLAIN, 13);
// Header
@@ -44,31 +42,24 @@ public class ScriptDialog extends JDialog {
topPanel.setOpaque(false);
JLabel descLabel = new JLabel("<html>Compose keystrokes and ECL bracketed mnemonics to stream to the mainframe.<br>"
+ "Example: <code>TSO[enter]USER[tab]PASSWORD[enter]</code> or <code>[pf3][clear]</code></html>");
descLabel.setForeground(new Color(180, 180, 180));
descLabel.setFont(labelFont);
topPanel.add(descLabel, BorderLayout.CENTER);
mainPanel.add(topPanel, BorderLayout.NORTH);
// Script area
scriptArea = new JTextArea();
scriptArea.setBackground(new Color(25, 25, 25));
scriptArea.setForeground(new Color(50, 205, 50));
scriptArea.setCaretColor(Color.WHITE);
scriptArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 13));
scriptArea.setLineWrap(true);
scriptArea.setWrapStyleWord(false);
ThemeManager.styleTextArea(scriptArea);
JScrollPane scrollPane = new JScrollPane(scriptArea);
scrollPane.setBorder(BorderFactory.createCompoundBorder(
new LineBorder(new Color(60, 60, 60)),
new EmptyBorder(2, 2, 2, 2)));
ThemeManager.styleScrollPane(scrollPane);
// Mnemonic helper buttons
JPanel tokenPanel = new JPanel(new WrapLayout(FlowLayout.LEFT, 4, 4));
tokenPanel.setOpaque(false);
tokenPanel.setBorder(BorderFactory.createTitledBorder(
new LineBorder(new Color(60, 60, 60)), "Insert Keystroke Token"));
((TitledBorder) tokenPanel.getBorder()).setTitleColor(new Color(180, 180, 180));
tokenPanel.setBorder(ThemeManager.createTitledBorder("Insert Keystroke Token"));
String[] tokens = {
"[enter]", "[tab]", "[backtab]", "[clear]", "[reset]",
@@ -84,8 +75,7 @@ public class ScriptDialog extends JDialog {
for (String token : tokens) {
JButton btn = new JButton(token);
btn.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 11));
btn.setBackground(new Color(50, 50, 50));
btn.setForeground(fg);
ThemeManager.styleButton(btn, ThemeManager.ButtonVariant.DEFAULT);
btn.setFocusable(false);
btn.setMargin(new Insets(2, 4, 2, 4));
btn.addActionListener(e -> insertToken(token));
@@ -103,7 +93,7 @@ public class ScriptDialog extends JDialog {
bottomPanel.setOpaque(false);
statusLabel = new JLabel("Ready");
statusLabel.setForeground(new Color(160, 160, 160));
statusLabel.setForeground(ThemeManager.getFgMuted());
statusLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12));
bottomPanel.add(statusLabel, BorderLayout.WEST);
@@ -111,26 +101,22 @@ public class ScriptDialog extends JDialog {
buttonPanel.setOpaque(false);
JButton loadBtn = new JButton("Load Script...");
loadBtn.setBackground(new Color(50, 50, 50));
loadBtn.setForeground(fg);
ThemeManager.styleButton(loadBtn, ThemeManager.ButtonVariant.DEFAULT);
loadBtn.setFont(labelFont);
loadBtn.addActionListener(e -> loadScript());
JButton saveBtn = new JButton("Save Script...");
saveBtn.setBackground(new Color(50, 50, 50));
saveBtn.setForeground(fg);
ThemeManager.styleButton(saveBtn, ThemeManager.ButtonVariant.DEFAULT);
saveBtn.setFont(labelFont);
saveBtn.addActionListener(e -> saveScript());
JButton runBtn = new JButton("Execute");
runBtn.setBackground(new Color(50, 120, 50));
runBtn.setForeground(Color.WHITE);
ThemeManager.styleButton(runBtn, ThemeManager.ButtonVariant.PRIMARY);
runBtn.setFont(labelFont);
runBtn.addActionListener(e -> executeScript());
JButton closeBtn = new JButton("Close");
closeBtn.setBackground(new Color(60, 60, 60));
closeBtn.setForeground(fg);
ThemeManager.styleButton(closeBtn, ThemeManager.ButtonVariant.CANCEL);
closeBtn.setFont(labelFont);
closeBtn.addActionListener(e -> dispose());
@@ -144,6 +130,7 @@ public class ScriptDialog extends JDialog {
mainPanel.add(bottomPanel, BorderLayout.SOUTH);
setContentPane(mainPanel);
ThemeManager.applyThemeToWindow(this);
}
private void insertToken(String token) {
@@ -19,16 +19,8 @@ public class SettingsDialog extends JDialog {
private final J3270App parentApp;
// Dark theme colors
private static final Color DARK_BG = new Color(43, 43, 43);
private static final Color DARK_BG_LIGHTER = new Color(55, 55, 55);
private static final Color DARK_FG = new Color(224, 224, 224);
private static final Color DARK_BORDER = new Color(70, 70, 70);
private static final Color DARK_SELECTION = new Color(75, 110, 175);
private static final Color DARK_BUTTON_BG = new Color(60, 63, 65);
private static final Color DARK_FIELD_BG = new Color(50, 50, 50);
// Appearance tab
private JComboBox<UITheme> uiThemeBox;
private JComboBox<String> fontBox;
private JSpinner fontSizeSpinner;
private JComboBox<haus.nightmare.lib3270j.graphics.GraphicsMode> graphicsModeBox;
@@ -51,22 +43,30 @@ public class SettingsDialog extends JDialog {
this.parentApp = parent;
initComponents();
setSize(550, 450);
setSize(560, 480);
setLocationRelativeTo(parent);
}
private void initComponents() {
JTabbedPane tabbedPane = new JTabbedPane();
ThemeManager.styleTabbedPane(tabbedPane);
tabbedPane.addTab("Appearance", createAppearancePanel());
tabbedPane.addTab("Behavior", createBehaviorPanel());
tabbedPane.addTab("Advanced", createAdvancedPanel());
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 8, 8));
JButton btnExport = new JButton("Export Config...");
JButton btnOk = new JButton("OK");
ThemeManager.styleButton(btnExport, ThemeManager.ButtonVariant.DEFAULT);
JButton btnApply = new JButton("Apply");
ThemeManager.styleButton(btnApply, ThemeManager.ButtonVariant.DEFAULT);
JButton btnCancel = new JButton("Cancel");
ThemeManager.styleButton(btnCancel, ThemeManager.ButtonVariant.CANCEL);
JButton btnOk = new JButton("OK");
ThemeManager.styleButton(btnOk, ThemeManager.ButtonVariant.PRIMARY);
btnExport.addActionListener((ActionEvent e) -> {
exportConfig();
@@ -96,132 +96,11 @@ public class SettingsDialog extends JDialog {
getContentPane().add(tabbedPane, BorderLayout.CENTER);
getContentPane().add(buttonPanel, BorderLayout.SOUTH);
// Apply dark theme to all components for cross-platform readability
applyDarkTheme(getContentPane());
applyDarkTheme(tabbedPane);
applyDarkTheme(buttonPanel);
getContentPane().setBackground(DARK_BG);
ThemeManager.applyThemeToWindow(this);
}
// ========== Dark Theme Utility ==========
/**
* Recursively apply dark theme to a component and all its children.
* Ensures the Settings dialog is readable on Windows, Linux, and macOS.
*/
private void applyDarkTheme(Component comp) {
if (comp instanceof JTabbedPane) {
JTabbedPane tp = (JTabbedPane) comp;
tp.setBackground(DARK_BG);
tp.setForeground(DARK_FG);
for (int i = 0; i < tp.getTabCount(); i++) {
applyDarkTheme(tp.getComponentAt(i));
}
return;
}
if (comp instanceof JTable) {
JTable table = (JTable) comp;
table.setBackground(DARK_FIELD_BG);
table.setForeground(DARK_FG);
table.setSelectionBackground(DARK_SELECTION);
table.setSelectionForeground(Color.WHITE);
table.setGridColor(DARK_BORDER);
JTableHeader header = table.getTableHeader();
if (header != null) {
header.setBackground(DARK_BG_LIGHTER);
header.setForeground(DARK_FG);
DefaultTableCellRenderer headerRenderer = new DefaultTableCellRenderer();
headerRenderer.setBackground(DARK_BG_LIGHTER);
headerRenderer.setForeground(DARK_FG);
header.setDefaultRenderer(headerRenderer);
}
return;
}
if (comp instanceof JScrollPane) {
JScrollPane sp = (JScrollPane) comp;
sp.setBackground(DARK_BG);
sp.getViewport().setBackground(DARK_FIELD_BG);
applyDarkTheme(sp.getViewport().getView());
return;
}
if (comp instanceof JButton) {
JButton btn = (JButton) comp;
btn.setBackground(DARK_BUTTON_BG);
btn.setForeground(DARK_FG);
btn.setFocusPainted(false);
btn.setBorder(BorderFactory.createCompoundBorder(
new LineBorder(DARK_BORDER, 1),
BorderFactory.createEmptyBorder(3, 10, 3, 10)));
btn.setOpaque(true);
return;
}
if (comp instanceof JComboBox) {
JComboBox<?> cb = (JComboBox<?>) comp;
cb.setBackground(DARK_FIELD_BG);
cb.setForeground(DARK_FG);
return;
}
if (comp instanceof JSpinner) {
JSpinner sp = (JSpinner) comp;
sp.setBackground(DARK_FIELD_BG);
sp.setForeground(DARK_FG);
JComponent editor = sp.getEditor();
if (editor instanceof JSpinner.DefaultEditor) {
JTextField tf = ((JSpinner.DefaultEditor) editor).getTextField();
tf.setBackground(DARK_FIELD_BG);
tf.setForeground(DARK_FG);
tf.setCaretColor(DARK_FG);
}
return;
}
if (comp instanceof JTextField) {
JTextField tf = (JTextField) comp;
tf.setBackground(DARK_FIELD_BG);
tf.setForeground(DARK_FG);
tf.setCaretColor(DARK_FG);
return;
}
if (comp instanceof JCheckBox) {
JCheckBox cb = (JCheckBox) comp;
cb.setBackground(DARK_BG);
cb.setForeground(DARK_FG);
return;
}
if (comp instanceof JLabel) {
comp.setForeground(DARK_FG);
return;
}
// Generic panel / container
// Skip color swatch panels — their background IS the color
if (comp instanceof JPanel && "colorSwatch".equals(comp.getName())) {
return;
}
comp.setBackground(DARK_BG);
comp.setForeground(DARK_FG);
if (comp instanceof JPanel) {
JPanel panel = (JPanel) comp;
// Style titled borders
if (panel.getBorder() instanceof TitledBorder) {
TitledBorder tb = (TitledBorder) panel.getBorder();
tb.setTitleColor(DARK_FG);
}
}
if (comp instanceof Container) {
for (Component child : ((Container) comp).getComponents()) {
applyDarkTheme(child);
}
}
ThemeManager.applyTheme(comp);
}
// ========== Tab Panels ==========
@@ -232,9 +111,22 @@ public class SettingsDialog extends JDialog {
gbc.insets = new Insets(10, 10, 10, 10);
gbc.anchor = GridBagConstraints.WEST;
// Font family
// UI Theme
gbc.gridx = 0;
gbc.gridy = 0;
JLabel themeLabel = new JLabel("Java UI Theme:");
panel.add(themeLabel, gbc);
uiThemeBox = new JComboBox<>(UITheme.values());
uiThemeBox.setSelectedItem(Settings.getJavaUiTheme());
gbc.gridx = 1;
gbc.fill = GridBagConstraints.HORIZONTAL;
panel.add(uiThemeBox, gbc);
// Font family
gbc.gridx = 0;
gbc.gridy = 1;
gbc.fill = GridBagConstraints.NONE;
JLabel fontLabel = new JLabel("Terminal Font:");
panel.add(fontLabel, gbc);
@@ -254,7 +146,7 @@ public class SettingsDialog extends JDialog {
// Font size
gbc.gridx = 0;
gbc.gridy = 1;
gbc.gridy = 2;
gbc.fill = GridBagConstraints.NONE;
JLabel sizeLabel = new JLabel("Font Size:");
panel.add(sizeLabel, gbc);
@@ -267,7 +159,7 @@ public class SettingsDialog extends JDialog {
// Graphics Mode
gbc.gridx = 0;
gbc.gridy = 2;
gbc.gridy = 3;
gbc.fill = GridBagConstraints.NONE;
JLabel graphicsLabel = new JLabel("Graphics Mode:");
panel.add(graphicsLabel, gbc);
@@ -279,7 +171,7 @@ public class SettingsDialog extends JDialog {
panel.add(graphicsModeBox, gbc);
// Fill remaining space
gbc.gridy = 3;
gbc.gridy = 4;
gbc.weighty = 1.0;
panel.add(Box.createGlue(), gbc);
@@ -429,6 +321,7 @@ public class SettingsDialog extends JDialog {
JPanel resetPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
JButton btnResetColors = new JButton("Reset to Defaults");
ThemeManager.styleButton(btnResetColors, ThemeManager.ButtonVariant.DEFAULT);
btnResetColors.addActionListener(e -> {
for (int i=0; i<16; i++) {
tempHostColors[i] = TerminalPanel.DEFAULT_HOST_COLORS[i];
@@ -436,8 +329,6 @@ public class SettingsDialog extends JDialog {
for (int i=0; i<monoKeys.length; i++) {
tempMonoColors.put(monoKeys[i], monoDefs[i]);
}
// Repaint container implicitly handled if we trigger a UI update,
// but for simplicity user can just close/reopen or have it refresh on save
JOptionPane.showMessageDialog(main, "Colors reset. Click Apply to save.");
});
resetPanel.add(btnResetColors);
@@ -498,6 +389,7 @@ public class SettingsDialog extends JDialog {
}
JTable table = new JTable(keymapModel);
ThemeManager.styleTable(table);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.setFillsViewportHeight(true);
@@ -510,9 +402,8 @@ public class SettingsDialog extends JDialog {
captureDialog.setSize(320, 100);
captureDialog.setLocationRelativeTo(this);
JLabel lbl = new JLabel("Press any key combination now...", SwingConstants.CENTER);
lbl.setForeground(DARK_FG);
captureDialog.getContentPane().setBackground(DARK_BG);
captureDialog.add(lbl);
ThemeManager.applyThemeToWindow(captureDialog);
captureDialog.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
@@ -532,6 +423,7 @@ public class SettingsDialog extends JDialog {
// Replace Binding — sets the action to exactly one new key
JButton btnReplace = new JButton("Replace Binding");
ThemeManager.styleButton(btnReplace, ThemeManager.ButtonVariant.DEFAULT);
btnReplace.addActionListener(e -> {
int row = table.getSelectedRow();
if (row < 0) return;
@@ -545,6 +437,7 @@ public class SettingsDialog extends JDialog {
// Add Binding — appends an additional key to the existing binding(s)
JButton btnAdd = new JButton("Add Binding");
ThemeManager.styleButton(btnAdd, ThemeManager.ButtonVariant.DEFAULT);
btnAdd.addActionListener(e -> {
int row = table.getSelectedRow();
if (row < 0) return;
@@ -565,6 +458,7 @@ public class SettingsDialog extends JDialog {
// Remove Last — removes the last comma-separated binding entry
JButton btnRemoveLast = new JButton("Remove Last");
ThemeManager.styleButton(btnRemoveLast, ThemeManager.ButtonVariant.DEFAULT);
btnRemoveLast.addActionListener(e -> {
int row = table.getSelectedRow();
if (row < 0) return;
@@ -585,6 +479,7 @@ public class SettingsDialog extends JDialog {
// Unbind All — clears all bindings for the action
JButton btnUnbind = new JButton("Unbind All");
ThemeManager.styleButton(btnUnbind, ThemeManager.ButtonVariant.DEFAULT);
btnUnbind.addActionListener(e -> {
int row = table.getSelectedRow();
if (row < 0) return;
@@ -596,6 +491,7 @@ public class SettingsDialog extends JDialog {
// Reset Keymaps — restore all defaults
JButton btnReset = new JButton("Reset All");
ThemeManager.styleButton(btnReset, ThemeManager.ButtonVariant.DEFAULT);
btnReset.addActionListener(e -> {
for (int row = 0; row < keymapModel.getRowCount(); row++) {
String action = (String) keymapModel.getValueAt(row, 0);
@@ -628,6 +524,12 @@ public class SettingsDialog extends JDialog {
private boolean applySettings() {
try {
// Apply Appearance
UITheme selectedTheme = (UITheme) uiThemeBox.getSelectedItem();
if (selectedTheme != null) {
Settings.setJavaUiTheme(selectedTheme);
ThemeManager.setTheme(selectedTheme);
}
String fontFam = (String) fontBox.getSelectedItem();
if (fontFam != null) {
Settings.setFontFamily(fontFam);
@@ -668,6 +570,7 @@ public class SettingsDialog extends JDialog {
}
parentApp.getTerminalPanel().reloadSettings();
ThemeManager.applyThemeToWindow(this);
return true;
} catch (Exception e) {
@@ -26,29 +26,22 @@ public class StatusBar extends JPanel {
private Telnet3270Client client;
private TerminalPanel terminalPanel;
// OIA colors
private static final Color OIA_BG = new Color(20, 20, 20);
private static final Color OIA_FG = new Color(50, 205, 50);
private static final Color OIA_DIM = new Color(80, 80, 80);
private static final Color OIA_ALERT = new Color(255, 80, 80);
private static final Color OIA_WARN = new Color(255, 200, 80);
public StatusBar() {
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
setBackground(OIA_BG);
setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, new Color(40, 40, 40)));
setBackground(ThemeManager.getStatusBarBg());
setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, ThemeManager.getStatusBarBorder()));
setPreferredSize(new Dimension(800, 22));
Font oiaFont = new Font(Font.MONOSPACED, Font.PLAIN, 12);
connectionStatus = createLabel("Not Connected", oiaFont, OIA_DIM);
tlsStatus = createLabel("", oiaFont, OIA_FG);
luName = createLabel("", oiaFont, OIA_FG);
lockStatus = createLabel("", oiaFont, OIA_ALERT);
fieldTypeStatus = createLabel("", oiaFont, OIA_DIM);
codePageInfo = createLabel("", oiaFont, OIA_DIM);
modelInfo = createLabel("", oiaFont, OIA_DIM);
cursorPosition = createLabel("001/001 [0000]", oiaFont, OIA_FG);
connectionStatus = createLabel("Not Connected", oiaFont, ThemeManager.getOiaFgDim());
tlsStatus = createLabel("", oiaFont, ThemeManager.getOiaFgNormal());
luName = createLabel("", oiaFont, ThemeManager.getOiaFgNormal());
lockStatus = createLabel("", oiaFont, ThemeManager.getOiaFgAlert());
fieldTypeStatus = createLabel("", oiaFont, ThemeManager.getOiaFgDim());
codePageInfo = createLabel("", oiaFont, ThemeManager.getOiaFgDim());
modelInfo = createLabel("", oiaFont, ThemeManager.getOiaFgDim());
cursorPosition = createLabel("001/001 [0000]", oiaFont, ThemeManager.getOiaFgNormal());
add(Box.createHorizontalStrut(6));
add(connectionStatus);
@@ -82,10 +75,17 @@ public class StatusBar extends JPanel {
updateStatus();
}
public void applyTheme(UITheme theme) {
setBackground(ThemeManager.getStatusBarBg(theme));
setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, ThemeManager.getStatusBarBorder(theme)));
updateStatus();
}
public void updateStatus() {
UITheme theme = ThemeManager.getTheme();
if (client == null) {
connectionStatus.setText("Not Connected");
connectionStatus.setForeground(OIA_DIM);
connectionStatus.setForeground(ThemeManager.getOiaFgDim(theme));
tlsStatus.setText("");
luName.setText("");
lockStatus.setText("");
@@ -101,38 +101,38 @@ public class StatusBar extends JPanel {
switch (state) {
case NOT_CONNECTED:
connectionStatus.setText("Not Connected");
connectionStatus.setForeground(OIA_DIM);
connectionStatus.setForeground(ThemeManager.getOiaFgDim(theme));
break;
case TCP_PENDING:
case TELNET_PENDING:
connectionStatus.setText("Connecting...");
connectionStatus.setForeground(OIA_ALERT);
connectionStatus.setForeground(ThemeManager.getOiaFgAlert(theme));
break;
case CONNECTED_3270:
connectionStatus.setText("TN3270");
connectionStatus.setForeground(OIA_FG);
connectionStatus.setForeground(ThemeManager.getOiaFgNormal(theme));
break;
case CONNECTED_TN3270E:
connectionStatus.setText("TN3270E");
connectionStatus.setForeground(OIA_FG);
connectionStatus.setForeground(ThemeManager.getOiaFgNormal(theme));
break;
case CONNECTED_SSCP:
connectionStatus.setText("SSCP-LU");
connectionStatus.setForeground(OIA_FG);
connectionStatus.setForeground(ThemeManager.getOiaFgNormal(theme));
break;
case CONNECTED_NVT:
case CONNECTED_NVT_CHAR:
case CONNECTED_E_NVT:
connectionStatus.setText("NVT");
connectionStatus.setForeground(OIA_FG);
connectionStatus.setForeground(ThemeManager.getOiaFgNormal(theme));
break;
case CONNECTED_UNBOUND:
connectionStatus.setText("Unbound");
connectionStatus.setForeground(OIA_WARN);
connectionStatus.setForeground(ThemeManager.getOiaFgWarn(theme));
break;
default:
connectionStatus.setText(state.name());
connectionStatus.setForeground(OIA_DIM);
connectionStatus.setForeground(ThemeManager.getOiaFgDim(theme));
break;
}
@@ -144,11 +144,11 @@ public class StatusBar extends JPanel {
String protocol = session != null ? session.getProtocol() : "TLS";
if (verified) {
tlsStatus.setText("🔒 TLS");
tlsStatus.setForeground(OIA_FG);
tlsStatus.setForeground(ThemeManager.getOiaFgNormal(theme));
tlsStatus.setToolTipText(protocol + " / " + cipher + " (Verified)");
} else {
tlsStatus.setText("🔓 TLS (Unverified)");
tlsStatus.setForeground(new Color(255, 180, 80));
tlsStatus.setForeground(ThemeManager.getOiaFgWarn(theme));
tlsStatus.setToolTipText(protocol + " / " + cipher + " (Verification Bypassed)");
}
} else {
@@ -164,6 +164,7 @@ public class StatusBar extends JPanel {
lu = "LU:" + client.getConfig().getLuName();
}
luName.setText(lu);
luName.setForeground(ThemeManager.getOiaFgNormal(theme));
// Lock / Inhibit status
int inhibit = client.getOIA().getInputInhibited();
@@ -191,10 +192,10 @@ public class StatusBar extends JPanel {
lockStatus.setText("X LOCKED");
break;
}
lockStatus.setForeground(OIA_ALERT);
lockStatus.setForeground(ThemeManager.getOiaFgAlert(theme));
} else if (client.getInputProcessor().isInsertMode()) {
lockStatus.setText("INSERT");
lockStatus.setForeground(OIA_FG);
lockStatus.setForeground(ThemeManager.getOiaFgNormal(theme));
} else {
lockStatus.setText("");
}
@@ -203,10 +204,10 @@ public class StatusBar extends JPanel {
if (state.isFullSession() && client.getScreenBuffer().isFormatted()) {
if (client.getOIA().isNumeric()) {
fieldTypeStatus.setText("NUM");
fieldTypeStatus.setForeground(OIA_WARN);
fieldTypeStatus.setForeground(ThemeManager.getOiaFgWarn(theme));
} else {
fieldTypeStatus.setText("ALPHA");
fieldTypeStatus.setForeground(OIA_DIM);
fieldTypeStatus.setForeground(ThemeManager.getOiaFgDim(theme));
}
} else {
fieldTypeStatus.setText("");
@@ -215,6 +216,7 @@ public class StatusBar extends JPanel {
// Active Code Page
String cp = client.getCodePage();
codePageInfo.setText(cp != null ? "CP" + cp : "");
codePageInfo.setForeground(ThemeManager.getOiaFgDim(theme));
codePageInfo.setToolTipText("Active EBCDIC Code Page: CP" + cp);
// Model & Dimensions info
@@ -222,11 +224,13 @@ public class StatusBar extends JPanel {
int rows = sb.getDisplayRows();
int cols = sb.getDisplayCols();
modelInfo.setText(client.getConfig().getModel().name().replace('_', '-') + " [" + rows + "x" + cols + "]");
modelInfo.setForeground(ThemeManager.getOiaFgDim(theme));
// Cursor position and buffer address
int curAddr = sb.getCursorAddress();
int row = sb.getCursorRow() + 1;
int col = sb.getCursorCol() + 1;
cursorPosition.setText(String.format("%03d/%03d [%04d]", row, col, curAddr));
cursorPosition.setForeground(ThemeManager.getOiaFgNormal(theme));
}
}
@@ -434,6 +434,16 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
public void pasteClipboard() {
if (client == null || !client.getConnectionState().isFullSession()) return;
if (client.getConnectionState().isNvt()) {
try {
String text = (String) Toolkit.getDefaultToolkit().getSystemClipboard()
.getData(DataFlavor.stringFlavor);
if (text != null && !text.isEmpty()) {
client.sendNVTString(text);
}
} catch (Exception ignored) {}
return;
}
try {
String text = (String) Toolkit.getDefaultToolkit().getSystemClipboard()
.getData(DataFlavor.stringFlavor);
@@ -481,24 +491,29 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
private void showContextMenu(MouseEvent e) {
JPopupMenu popup = new JPopupMenu();
ThemeManager.stylePopupMenu(popup);
JMenuItem copyItem = new JMenuItem("Copy");
ThemeManager.styleMenuItem(copyItem);
copyItem.setEnabled(hasSelection());
copyItem.addActionListener(ev -> copySelection());
popup.add(copyItem);
JMenuItem pasteItem = new JMenuItem("Paste");
ThemeManager.styleMenuItem(pasteItem);
pasteItem.setEnabled(client != null && client.getConnectionState().isFullSession());
pasteItem.addActionListener(ev -> pasteClipboard());
popup.add(pasteItem);
JMenuItem selectAllItem = new JMenuItem("Select All");
ThemeManager.styleMenuItem(selectAllItem);
selectAllItem.addActionListener(ev -> selectAll());
popup.add(selectAllItem);
popup.addSeparator();
JCheckBoxMenuItem blockModeItem = new JCheckBoxMenuItem("Block Select Mode", blockSelectMode);
ThemeManager.styleMenuItem(blockModeItem);
blockModeItem.addActionListener(ev -> {
blockSelectMode = blockModeItem.isSelected();
haus.nightmare.j3270.config.Settings.setBlockSelectMode(blockSelectMode);
@@ -693,19 +708,63 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
@Override
protected void processKeyEvent(KeyEvent e) {
if (e.getID() == KeyEvent.KEY_TYPED) {
char ch = e.getKeyChar();
if (ch >= 0x20 && ch != 0x7F && ch != KeyEvent.CHAR_UNDEFINED
&& !e.isControlDown() && !e.isAltDown() && !e.isMetaDown()) {
if (client != null) {
ConnectionState state = client.getConnectionState();
if (state == ConnectionState.CONNECTED_NVT || state == ConnectionState.CONNECTED_NVT_CHAR || state == ConnectionState.CONNECTED_E_NVT) {
if (client != null) {
ConnectionState state = client.getConnectionState();
if (state.isNvt()) {
if (e.getID() == KeyEvent.KEY_TYPED) {
char ch = e.getKeyChar();
if (ch >= 0x20 && ch != 0x7F && ch != KeyEvent.CHAR_UNDEFINED
&& !e.isControlDown() && !e.isAltDown() && !e.isMetaDown()) {
try {
client.sendNVTChar(ch);
} catch (Exception ignored) {}
e.consume();
return;
} else if (state.isFullSession()) {
} else if (e.isControlDown() && !e.isAltDown() && !e.isMetaDown()) {
if (ch > 0 && ch < 0x20) {
try {
client.sendNVTChar(ch);
} catch (Exception ignored) {}
e.consume();
return;
}
}
} else if (e.getID() == KeyEvent.KEY_PRESSED) {
if (e.isControlDown() && !e.isAltDown() && !e.isMetaDown()) {
int code = e.getKeyCode();
if (code >= KeyEvent.VK_A && code <= KeyEvent.VK_Z) {
char ctrlChar = (char) (code - KeyEvent.VK_A + 1);
try {
client.sendNVTChar(ctrlChar);
} catch (Exception ignored) {}
e.consume();
return;
} else if (code == KeyEvent.VK_OPEN_BRACKET) { // Ctrl+[ = ESC
try {
client.sendNVTChar('\u001B');
} catch (Exception ignored) {}
e.consume();
return;
} else if (code == KeyEvent.VK_BACK_SLASH) { // Ctrl+\ = FS
try {
client.sendNVTChar('\u001C');
} catch (Exception ignored) {}
e.consume();
return;
} else if (code == KeyEvent.VK_CLOSE_BRACKET) { // Ctrl+] = GS
try {
client.sendNVTChar('\u001D');
} catch (Exception ignored) {}
e.consume();
return;
}
}
}
} else if (e.getID() == KeyEvent.KEY_TYPED) {
char ch = e.getKeyChar();
if (ch >= 0x20 && ch != 0x7F && ch != KeyEvent.CHAR_UNDEFINED
&& !e.isControlDown() && !e.isAltDown() && !e.isMetaDown()) {
if (state.isFullSession()) {
client.typeCharacter(ch);
refreshScreen();
e.consume();
@@ -731,7 +790,7 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
private void handleEnter() {
if (client != null) {
ConnectionState state = client.getConnectionState();
if (state == ConnectionState.CONNECTED_NVT || state == ConnectionState.CONNECTED_NVT_CHAR || state == ConnectionState.CONNECTED_E_NVT) {
if (state.isNvt()) {
try {
client.sendNVTString("\r\n");
} catch (Exception ignored) {}
@@ -744,6 +803,12 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
private void handleReset() {
if (client != null) {
if (client.getConnectionState().isNvt()) {
try {
client.sendNVTChar('\u001B');
} catch (Exception ignored) {}
return;
}
clearSearchHighlight();
clearSelection();
client.reset();
@@ -753,6 +818,16 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
private void handleTab(boolean shift) {
if (client != null && client.getConnectionState().isFullSession()) {
if (client.getConnectionState().isNvt()) {
try {
if (shift) {
client.sendNVTString("\u001B[Z");
} else {
client.sendNVTChar('\t');
}
} catch (Exception ignored) {}
return;
}
if (shift) client.backTab();
else client.tab();
refreshScreen();
@@ -761,6 +836,18 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
private void handleCursor(String dir) {
if (client != null && client.getConnectionState().isFullSession()) {
if (client.getConnectionState().isNvt()) {
try {
switch (dir) {
case "up": client.sendNVTString("\u001B[A"); break;
case "down": client.sendNVTString("\u001B[B"); break;
case "left": client.sendNVTString("\u001B[D"); break;
case "right": client.sendNVTString("\u001B[C"); break;
case "home": client.sendNVTString("\u001B[H"); break;
}
} catch (Exception ignored) {}
return;
}
switch (dir) {
case "up": client.cursorUp(); break;
case "down": client.cursorDown(); break;
@@ -774,11 +861,35 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
private void handlePF(int n) {
if (client != null && client.getConnectionState().isFullSession()) {
if (client.getConnectionState().isNvt()) {
try {
client.sendNVTString(getNvtFunctionKeySequence(n));
} catch (Exception ignored) {}
return;
}
client.sendPF(n);
refreshScreen();
}
}
private String getNvtFunctionKeySequence(int n) {
switch (n) {
case 1: return "\u001BOP";
case 2: return "\u001BOQ";
case 3: return "\u001BOR";
case 4: return "\u001BOS";
case 5: return "\u001B[15~";
case 6: return "\u001B[17~";
case 7: return "\u001B[18~";
case 8: return "\u001B[19~";
case 9: return "\u001B[20~";
case 10: return "\u001B[21~";
case 11: return "\u001B[23~";
case 12: return "\u001B[24~";
default: return "";
}
}
private void handlePA(int n) {
if (client != null && client.getConnectionState().isFullSession()) {
client.sendPA(n);
@@ -788,6 +899,12 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
private void handleEraseEOF() {
if (client != null && client.getConnectionState().isFullSession()) {
if (client.getConnectionState().isNvt()) {
try {
client.sendNVTString("\u001B[F");
} catch (Exception ignored) {}
return;
}
client.getInputProcessor().eraseEof();
refreshScreen();
}
@@ -795,6 +912,12 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
private void handleDelete() {
if (client != null && client.getConnectionState().isFullSession()) {
if (client.getConnectionState().isNvt()) {
try {
client.sendNVTString("\u001B[3~");
} catch (Exception ignored) {}
return;
}
client.getInputProcessor().deleteChar();
refreshScreen();
}
@@ -803,7 +926,7 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
private void handleBackspace() {
if (client != null) {
ConnectionState state = client.getConnectionState();
if (state == ConnectionState.CONNECTED_NVT || state == ConnectionState.CONNECTED_NVT_CHAR || state == ConnectionState.CONNECTED_E_NVT) {
if (state.isNvt()) {
try {
client.sendNVTChar('\b');
} catch (Exception ignored) {}
@@ -824,6 +947,12 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
private void handleClear() {
if (client != null && client.getConnectionState().isFullSession()) {
if (client.getConnectionState().isNvt()) {
try {
client.sendNVTChar('\u000C');
} catch (Exception ignored) {}
return;
}
client.sendClear();
refreshScreen();
}
@@ -0,0 +1,955 @@
package haus.nightmare.j3270.ui;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.border.TitledBorder;
import javax.swing.plaf.basic.BasicButtonUI;
import javax.swing.plaf.basic.BasicMenuBarUI;
import javax.swing.plaf.basic.BasicMenuItemUI;
import javax.swing.plaf.basic.BasicMenuUI;
import javax.swing.plaf.basic.BasicPopupMenuUI;
import javax.swing.plaf.basic.BasicTabbedPaneUI;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* Centralized theme manager for j3270 Java desktop UI.
* Provides high-contrast, platform-independent Light and Dark theme palettes,
* custom Swing UI delegates, and recursive styling helpers for all dialogs and menus.
*/
public final class ThemeManager {
private static UITheme currentTheme = UITheme.DARK;
private static final List<Consumer<UITheme>> themeChangeListeners = new ArrayList<>();
private ThemeManager() {}
public static UITheme getTheme() {
return currentTheme;
}
public static boolean isDark() {
return currentTheme == UITheme.DARK;
}
public static boolean isLight() {
return currentTheme == UITheme.LIGHT;
}
public static void setTheme(UITheme theme) {
if (theme == null) theme = UITheme.DARK;
if (currentTheme != theme) {
currentTheme = theme;
applyUIManagerDefaults(theme);
for (Consumer<UITheme> listener : new ArrayList<>(themeChangeListeners)) {
try {
listener.accept(theme);
} catch (Exception ignored) {}
}
}
}
public static void addThemeChangeListener(Consumer<UITheme> listener) {
if (listener != null && !themeChangeListeners.contains(listener)) {
themeChangeListeners.add(listener);
}
}
public static void removeThemeChangeListener(Consumer<UITheme> listener) {
themeChangeListeners.remove(listener);
}
// =========================================================================
// Color Palette Tokens
// =========================================================================
public static Color getBgMain() { return getBgMain(currentTheme); }
public static Color getBgMain(UITheme t) {
return t == UITheme.DARK ? new Color(28, 28, 28) : new Color(242, 242, 244);
}
public static Color getBgPanel() { return getBgPanel(currentTheme); }
public static Color getBgPanel(UITheme t) {
return t == UITheme.DARK ? new Color(36, 36, 36) : new Color(250, 250, 252);
}
public static Color getBgComponent() { return getBgComponent(currentTheme); }
public static Color getBgComponent(UITheme t) {
return t == UITheme.DARK ? new Color(46, 46, 46) : Color.WHITE;
}
public static Color getBgComponentLighter() { return getBgComponentLighter(currentTheme); }
public static Color getBgComponentLighter(UITheme t) {
return t == UITheme.DARK ? new Color(56, 56, 56) : new Color(236, 236, 238);
}
public static Color getFgMain() { return getFgMain(currentTheme); }
public static Color getFgMain(UITheme t) {
return t == UITheme.DARK ? new Color(238, 238, 238) : new Color(24, 24, 24);
}
public static Color getFgMuted() { return getFgMuted(currentTheme); }
public static Color getFgMuted(UITheme t) {
return t == UITheme.DARK ? new Color(175, 175, 175) : new Color(90, 90, 90);
}
public static Color getFgDisabled() { return getFgDisabled(currentTheme); }
public static Color getFgDisabled(UITheme t) {
return t == UITheme.DARK ? new Color(110, 110, 110) : new Color(160, 160, 160);
}
public static Color getBorder() { return getBorder(currentTheme); }
public static Color getBorder(UITheme t) {
return t == UITheme.DARK ? new Color(65, 65, 65) : new Color(205, 205, 205);
}
public static Color getBorderColor() { return getBorder(currentTheme); }
public static Color getBorderColor(UITheme t) { return getBorder(t); }
public static Color getBorderFocused() { return getBorderFocused(currentTheme); }
public static Color getBorderFocused(UITheme t) {
return t == UITheme.DARK ? new Color(80, 140, 230) : new Color(50, 115, 220);
}
public static Color getSelectionBg() { return getSelectionBg(currentTheme); }
public static Color getSelectionBg(UITheme t) {
return t == UITheme.DARK ? new Color(55, 105, 180) : new Color(60, 125, 215);
}
public static Color getSelectionFg() { return getSelectionFg(currentTheme); }
public static Color getSelectionFg(UITheme t) {
return Color.WHITE;
}
public static Color getMenuBarBg() { return getMenuBarBg(currentTheme); }
public static Color getMenuBarBg(UITheme t) {
return t == UITheme.DARK ? new Color(26, 26, 26) : new Color(240, 240, 242);
}
public static Color getMenuBarFg() { return getMenuBarFg(currentTheme); }
public static Color getMenuBarFg(UITheme t) {
return t == UITheme.DARK ? new Color(235, 235, 235) : new Color(25, 25, 25);
}
public static Color getMenuPopupBg() { return getMenuPopupBg(currentTheme); }
public static Color getMenuPopupBg(UITheme t) {
return t == UITheme.DARK ? new Color(38, 38, 38) : Color.WHITE;
}
public static Color getMenuItemFg() { return getMenuItemFg(currentTheme); }
public static Color getMenuItemFg(UITheme t) {
return t == UITheme.DARK ? new Color(230, 230, 230) : new Color(30, 30, 30);
}
public static Color getMenuItemHoverBg() { return getMenuItemHoverBg(currentTheme); }
public static Color getMenuItemHoverBg(UITheme t) {
return t == UITheme.DARK ? new Color(55, 95, 160) : new Color(220, 232, 250);
}
public static Color getMenuItemHoverFg() { return getMenuItemHoverFg(currentTheme); }
public static Color getMenuItemHoverFg(UITheme t) {
return t == UITheme.DARK ? Color.WHITE : new Color(15, 15, 15);
}
public static Color getTabBg() { return getTabBg(currentTheme); }
public static Color getTabBg(UITheme t) {
return t == UITheme.DARK ? new Color(34, 34, 34) : new Color(230, 230, 232);
}
public static Color getTabFg() { return getTabFg(currentTheme); }
public static Color getTabFg(UITheme t) {
return t == UITheme.DARK ? new Color(185, 185, 185) : new Color(75, 75, 75);
}
public static Color getTabSelectedBg() { return getTabSelectedBg(currentTheme); }
public static Color getTabSelectedBg(UITheme t) {
return t == UITheme.DARK ? new Color(50, 50, 50) : Color.WHITE;
}
public static Color getTabSelectedFg() { return getTabSelectedFg(currentTheme); }
public static Color getTabSelectedFg(UITheme t) {
return t == UITheme.DARK ? Color.WHITE : new Color(20, 20, 20);
}
public static Color getTableHeaderBg() { return getTableHeaderBg(currentTheme); }
public static Color getTableHeaderBg(UITheme t) {
return t == UITheme.DARK ? new Color(46, 46, 46) : new Color(232, 232, 236);
}
public static Color getTableHeaderFg() { return getTableHeaderFg(currentTheme); }
public static Color getTableHeaderFg(UITheme t) {
return t == UITheme.DARK ? new Color(235, 235, 235) : new Color(30, 30, 30);
}
public static Color getTableRowEven() { return getTableRowEven(currentTheme); }
public static Color getTableRowEven(UITheme t) {
return t == UITheme.DARK ? new Color(38, 38, 38) : Color.WHITE;
}
public static Color getTableRowOdd() { return getTableRowOdd(currentTheme); }
public static Color getTableRowOdd(UITheme t) {
return t == UITheme.DARK ? new Color(33, 33, 33) : new Color(246, 247, 249);
}
public static Color getTableGrid() { return getTableGrid(currentTheme); }
public static Color getTableGrid(UITheme t) {
return t == UITheme.DARK ? new Color(55, 55, 55) : new Color(225, 225, 225);
}
public static Color getCodeAreaBg() { return getCodeAreaBg(currentTheme); }
public static Color getCodeAreaBg(UITheme t) {
return t == UITheme.DARK ? new Color(22, 22, 22) : new Color(252, 252, 252);
}
public static Color getCodeAreaFg() { return getCodeAreaFg(currentTheme); }
public static Color getCodeAreaFg(UITheme t) {
return t == UITheme.DARK ? new Color(80, 230, 80) : new Color(0, 120, 0);
}
public static Color getStatusBarBg() { return getStatusBarBg(currentTheme); }
public static Color getStatusBarBg(UITheme t) {
return t == UITheme.DARK ? new Color(20, 20, 20) : new Color(235, 235, 238);
}
public static Color getStatusBarBorder() { return getStatusBarBorder(currentTheme); }
public static Color getStatusBarBorder(UITheme t) {
return t == UITheme.DARK ? new Color(42, 42, 42) : new Color(210, 210, 215);
}
public static Color getOiaFgNormal() { return getOiaFgNormal(currentTheme); }
public static Color getOiaFgNormal(UITheme t) {
return t == UITheme.DARK ? new Color(50, 205, 50) : new Color(0, 130, 0);
}
public static Color getOiaFgDim() { return getOiaFgDim(currentTheme); }
public static Color getOiaFgDim(UITheme t) {
return t == UITheme.DARK ? new Color(110, 110, 110) : new Color(90, 90, 90);
}
public static Color getOiaFgAlert() { return getOiaFgAlert(currentTheme); }
public static Color getOiaFgAlert(UITheme t) {
return t == UITheme.DARK ? new Color(255, 80, 80) : new Color(190, 20, 20);
}
public static Color getOiaFgWarn() { return getOiaFgWarn(currentTheme); }
public static Color getOiaFgWarn(UITheme t) {
return t == UITheme.DARK ? new Color(255, 190, 70) : new Color(180, 100, 0);
}
// =========================================================================
// Button Variants & Color helpers
// =========================================================================
public enum ButtonVariant {
DEFAULT,
PRIMARY,
CANCEL,
DANGER,
ACCENT
}
public static Color getButtonBg(ButtonVariant variant) {
return getButtonBg(variant, currentTheme, false, false);
}
public static Color getButtonBg(ButtonVariant variant, UITheme t) {
return getButtonBg(variant, t, false, false);
}
public static Color getButtonBg(ButtonVariant variant, UITheme t, boolean hover, boolean pressed) {
switch (variant) {
case PRIMARY:
if (pressed) return new Color(25, 105, 35);
if (hover) return new Color(38, 145, 48);
return new Color(32, 128, 42);
case DANGER:
if (pressed) return new Color(125, 30, 30);
if (hover) return new Color(165, 45, 45);
return new Color(145, 38, 38);
case ACCENT:
if (pressed) return new Color(35, 80, 145);
if (hover) return new Color(55, 115, 195);
return new Color(45, 100, 175);
case CANCEL:
case DEFAULT:
default:
if (t == UITheme.DARK) {
if (pressed) return new Color(42, 42, 42);
if (hover) return new Color(68, 68, 68);
return new Color(56, 56, 56);
} else {
if (pressed) return new Color(210, 210, 212);
if (hover) return new Color(228, 228, 232);
return new Color(238, 238, 240);
}
}
}
public static Color getButtonFg(ButtonVariant variant, UITheme t) {
switch (variant) {
case PRIMARY:
case DANGER:
case ACCENT:
return Color.WHITE;
case CANCEL:
case DEFAULT:
default:
return t == UITheme.DARK ? new Color(235, 235, 235) : new Color(30, 30, 30);
}
}
// =========================================================================
// UIManager Defaults Configuration
// =========================================================================
public static void applyUIManagerDefaults(UITheme theme) {
Color bgMain = getBgMain(theme);
Color bgPanel = getBgPanel(theme);
Color bgComp = getBgComponent(theme);
Color fgMain = getFgMain(theme);
Color fgMuted = getFgMuted(theme);
Color border = getBorder(theme);
Color selBg = getSelectionBg(theme);
Color selFg = getSelectionFg(theme);
UIManager.put("Panel.background", bgPanel);
UIManager.put("Panel.foreground", fgMain);
UIManager.put("Label.foreground", fgMain);
UIManager.put("TextField.background", bgComp);
UIManager.put("TextField.foreground", fgMain);
UIManager.put("TextField.caretForeground", fgMain);
UIManager.put("TextField.selectionBackground", selBg);
UIManager.put("TextField.selectionForeground", selFg);
UIManager.put("TextArea.background", bgComp);
UIManager.put("TextArea.foreground", fgMain);
UIManager.put("TextArea.caretForeground", fgMain);
UIManager.put("TextArea.selectionBackground", selBg);
UIManager.put("TextArea.selectionForeground", selFg);
UIManager.put("Button.background", getBgComponentLighter(theme));
UIManager.put("Button.foreground", fgMain);
UIManager.put("Button.select", selBg);
UIManager.put("ComboBox.background", bgComp);
UIManager.put("ComboBox.foreground", fgMain);
UIManager.put("ComboBox.selectionBackground", selBg);
UIManager.put("ComboBox.selectionForeground", selFg);
UIManager.put("Table.background", bgComp);
UIManager.put("Table.foreground", fgMain);
UIManager.put("Table.selectionBackground", selBg);
UIManager.put("Table.selectionForeground", selFg);
UIManager.put("Table.gridColor", getTableGrid(theme));
UIManager.put("TableHeader.background", getTableHeaderBg(theme));
UIManager.put("TableHeader.foreground", getTableHeaderFg(theme));
UIManager.put("ScrollPane.background", bgPanel);
UIManager.put("Viewport.background", bgComp);
UIManager.put("MenuBar.background", getMenuBarBg(theme));
UIManager.put("MenuBar.foreground", getMenuBarFg(theme));
UIManager.put("Menu.background", getMenuBarBg(theme));
UIManager.put("Menu.foreground", getMenuBarFg(theme));
UIManager.put("Menu.selectionBackground", selBg);
UIManager.put("Menu.selectionForeground", selFg);
UIManager.put("PopupMenu.background", getMenuPopupBg(theme));
UIManager.put("PopupMenu.foreground", getMenuItemFg(theme));
UIManager.put("MenuItem.background", getMenuPopupBg(theme));
UIManager.put("MenuItem.foreground", getMenuItemFg(theme));
UIManager.put("MenuItem.selectionBackground", selBg);
UIManager.put("MenuItem.selectionForeground", selFg);
UIManager.put("CheckBoxMenuItem.background", getMenuPopupBg(theme));
UIManager.put("CheckBoxMenuItem.foreground", getMenuItemFg(theme));
UIManager.put("CheckBoxMenuItem.selectionBackground", selBg);
UIManager.put("CheckBoxMenuItem.selectionForeground", selFg);
UIManager.put("RadioButtonMenuItem.background", getMenuPopupBg(theme));
UIManager.put("RadioButtonMenuItem.foreground", getMenuItemFg(theme));
UIManager.put("RadioButtonMenuItem.selectionBackground", selBg);
UIManager.put("RadioButtonMenuItem.selectionForeground", selFg);
UIManager.put("Separator.background", border);
UIManager.put("Separator.foreground", border);
UIManager.put("TabbedPane.background", getTabBg(theme));
UIManager.put("TabbedPane.foreground", getTabFg(theme));
UIManager.put("TabbedPane.selected", getTabSelectedBg(theme));
UIManager.put("TabbedPane.selectHighlight", getTabSelectedBg(theme));
UIManager.put("CheckBox.background", bgPanel);
UIManager.put("CheckBox.foreground", fgMain);
UIManager.put("RadioButton.background", bgPanel);
UIManager.put("RadioButton.foreground", fgMain);
UIManager.put("TitledBorder.titleColor", fgMain);
UIManager.put("OptionPane.background", bgPanel);
UIManager.put("OptionPane.messageForeground", fgMain);
}
// =========================================================================
// Component Styling & Custom UI Delegates
// =========================================================================
/**
* Styles a button with custom rendering, ensuring consistent high contrast
* and proper background color across Windows, macOS, and Linux.
*/
public static JButton styleButton(JButton button, ButtonVariant variant) {
if (button == null) return null;
button.setUI(new StyledButtonUI(variant));
button.setFocusPainted(false);
button.setOpaque(false);
button.setContentAreaFilled(false);
button.setBorder(BorderFactory.createEmptyBorder(6, 14, 6, 14));
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
return button;
}
public static JButton createStyledButton(String text, ButtonVariant variant, Runnable action) {
JButton btn = new JButton(text);
styleButton(btn, variant);
if (action != null) {
btn.addActionListener(e -> action.run());
}
return btn;
}
public static JTextField styleTextField(JTextField field) {
if (field == null) return null;
field.setBackground(getBgComponent());
field.setForeground(getFgMain());
field.setCaretColor(getFgMain());
field.setSelectionColor(getSelectionBg());
field.setSelectedTextColor(getSelectionFg());
field.setBorder(BorderFactory.createCompoundBorder(
new LineBorder(getBorder(), 1, true),
new EmptyBorder(4, 8, 4, 8)));
return field;
}
public static JTextArea styleTextArea(JTextArea area) {
if (area == null) return null;
area.setBackground(getCodeAreaBg());
area.setForeground(getCodeAreaFg());
area.setCaretColor(getFgMain());
area.setSelectionColor(getSelectionBg());
area.setSelectedTextColor(getSelectionFg());
return area;
}
public static JComboBox<?> styleComboBox(JComboBox<?> box) {
if (box == null) return null;
box.setBackground(getBgComponent());
box.setForeground(getFgMain());
box.setRenderer(new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
setBorder(new EmptyBorder(4, 8, 4, 8));
if (isSelected) {
setBackground(getSelectionBg());
setForeground(getSelectionFg());
} else {
setBackground(getBgComponent());
setForeground(getFgMain());
}
return this;
}
});
return box;
}
public static JTable styleTable(JTable table) {
if (table == null) return null;
table.setBackground(getBgComponent());
table.setForeground(getFgMain());
table.setSelectionBackground(getSelectionBg());
table.setSelectionForeground(getSelectionFg());
table.setGridColor(getTableGrid());
table.setRowHeight(24);
table.setDefaultRenderer(Object.class, new DefaultTableCellRenderer() {
@Override
public Component getTableCellRendererComponent(JTable tbl, Object val, boolean isSel, boolean hasFocus, int row, int col) {
super.getTableCellRendererComponent(tbl, val, isSel, hasFocus, row, col);
setBorder(new EmptyBorder(2, 6, 2, 6));
if (isSel) {
setBackground(getSelectionBg());
setForeground(getSelectionFg());
} else {
setBackground(row % 2 == 0 ? getTableRowEven() : getTableRowOdd());
setForeground(getFgMain());
}
return this;
}
});
JTableHeader header = table.getTableHeader();
if (header != null) {
header.setBackground(getTableHeaderBg());
header.setForeground(getTableHeaderFg());
header.setFont(header.getFont().deriveFont(Font.BOLD));
header.setDefaultRenderer(new DefaultTableCellRenderer() {
@Override
public Component getTableCellRendererComponent(JTable tbl, Object val, boolean isSel, boolean hasFocus, int row, int col) {
super.getTableCellRendererComponent(tbl, val, isSel, hasFocus, row, col);
setBackground(getTableHeaderBg());
setForeground(getTableHeaderFg());
setFont(getFont().deriveFont(Font.BOLD));
setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createMatteBorder(0, 0, 1, 1, getBorderColor()),
new EmptyBorder(4, 6, 4, 6)));
return this;
}
});
}
return table;
}
public static JTabbedPane styleTabbedPane(JTabbedPane tp) {
if (tp == null) return null;
tp.setUI(new StyledTabbedPaneUI());
tp.setBackground(getBgMain());
tp.setForeground(getFgMain());
return tp;
}
public static JMenuBar styleMenuBar(JMenuBar bar) {
if (bar == null) return null;
bar.setUI(new StyledMenuBarUI());
bar.setBackground(getMenuBarBg());
bar.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, getBorder()));
return bar;
}
public static JMenu styleMenu(JMenu menu) {
if (menu == null) return null;
menu.setUI(new StyledMenuUI());
menu.setForeground(getMenuBarFg());
menu.setBackground(getMenuBarBg());
menu.setOpaque(false);
return menu;
}
public static JMenuItem styleMenuItem(JMenuItem item) {
if (item == null) return null;
item.setUI(new StyledMenuItemUI());
item.setBackground(getMenuPopupBg());
item.setForeground(getMenuItemFg());
return item;
}
public static JPopupMenu stylePopupMenu(JPopupMenu popup) {
if (popup == null) return null;
popup.setUI(new StyledPopupMenuUI());
popup.setBackground(getMenuPopupBg());
popup.setBorder(BorderFactory.createCompoundBorder(
new LineBorder(getBorder(), 1),
new EmptyBorder(4, 0, 4, 0)));
return popup;
}
public static JScrollPane styleScrollPane(JScrollPane sp) {
if (sp == null) return null;
sp.setBackground(getBgPanel());
if (sp.getViewport() != null) {
sp.getViewport().setBackground(getBgComponent());
}
sp.setBorder(new LineBorder(getBorder(), 1));
return sp;
}
public static JSpinner styleSpinner(JSpinner sp) {
if (sp == null) return null;
sp.setBackground(getBgComponent());
sp.setForeground(getFgMain());
sp.setBorder(new LineBorder(getBorder(), 1));
JComponent editor = sp.getEditor();
if (editor instanceof JSpinner.DefaultEditor) {
JTextField tf = ((JSpinner.DefaultEditor) editor).getTextField();
tf.setBackground(getBgComponent());
tf.setForeground(getFgMain());
tf.setCaretColor(getFgMain());
tf.setBorder(new EmptyBorder(2, 4, 2, 4));
}
return sp;
}
public static JCheckBox styleCheckBox(JCheckBox cb) {
if (cb == null) return null;
cb.setOpaque(false);
cb.setForeground(getFgMain());
cb.setFocusPainted(false);
return cb;
}
public static JRadioButton styleRadioButton(JRadioButton rb) {
if (rb == null) return null;
rb.setOpaque(false);
rb.setForeground(getFgMain());
rb.setFocusPainted(false);
return rb;
}
public static Border createTitledBorder(String title) {
return BorderFactory.createTitledBorder(
new LineBorder(getBorder(), 1),
title,
TitledBorder.DEFAULT_JUSTIFICATION,
TitledBorder.DEFAULT_POSITION,
new Font(Font.SANS_SERIF, Font.BOLD, 12),
getFgMain());
}
// =========================================================================
// Recursive Component Styling
// =========================================================================
public static void applyTheme(Component comp) {
applyTheme(comp, currentTheme);
}
public static void applyTheme(Component comp, UITheme theme) {
if (comp == null) return;
// Skip swatch preview panels whose background is the user-configured color
if ("colorSwatch".equals(comp.getName())) {
return;
}
if (comp instanceof JMenuBar) {
styleMenuBar((JMenuBar) comp);
for (int i = 0; i < ((JMenuBar) comp).getMenuCount(); i++) {
applyTheme(((JMenuBar) comp).getMenu(i), theme);
}
return;
}
if (comp instanceof JMenu) {
styleMenu((JMenu) comp);
JMenu menu = (JMenu) comp;
for (int i = 0; i < menu.getItemCount(); i++) {
JMenuItem item = menu.getItem(i);
if (item != null) applyTheme(item, theme);
}
return;
}
if (comp instanceof JMenuItem) {
styleMenuItem((JMenuItem) comp);
return;
}
if (comp instanceof JPopupMenu) {
stylePopupMenu((JPopupMenu) comp);
for (Component child : ((JPopupMenu) comp).getComponents()) {
applyTheme(child, theme);
}
return;
}
if (comp instanceof JTabbedPane) {
styleTabbedPane((JTabbedPane) comp);
JTabbedPane tp = (JTabbedPane) comp;
for (int i = 0; i < tp.getTabCount(); i++) {
applyTheme(tp.getComponentAt(i), theme);
}
return;
}
if (comp instanceof JTable) {
styleTable((JTable) comp);
return;
}
if (comp instanceof JScrollPane) {
styleScrollPane((JScrollPane) comp);
JScrollPane sp = (JScrollPane) comp;
if (sp.getViewport() != null) {
applyTheme(sp.getViewport().getView(), theme);
}
return;
}
if (comp instanceof JButton) {
JButton btn = (JButton) comp;
if (!(btn.getUI() instanceof StyledButtonUI)) {
styleButton(btn, ButtonVariant.DEFAULT);
}
return;
}
if (comp instanceof JComboBox) {
styleComboBox((JComboBox<?>) comp);
return;
}
if (comp instanceof JSpinner) {
styleSpinner((JSpinner) comp);
return;
}
if (comp instanceof JTextArea) {
styleTextArea((JTextArea) comp);
return;
}
if (comp instanceof JTextField) {
styleTextField((JTextField) comp);
return;
}
if (comp instanceof JCheckBox) {
styleCheckBox((JCheckBox) comp);
return;
}
if (comp instanceof JRadioButton) {
styleRadioButton((JRadioButton) comp);
return;
}
if (comp instanceof JLabel) {
comp.setForeground(getFgMain(theme));
return;
}
if (comp instanceof JPanel) {
JPanel panel = (JPanel) comp;
if (panel.isOpaque()) {
panel.setBackground(getBgPanel(theme));
}
panel.setForeground(getFgMain(theme));
Border b = panel.getBorder();
if (b instanceof TitledBorder) {
TitledBorder tb = (TitledBorder) b;
tb.setTitleColor(getFgMain(theme));
tb.setBorder(new LineBorder(getBorder(theme), 1));
}
} else if (comp instanceof Container) {
comp.setBackground(getBgMain(theme));
comp.setForeground(getFgMain(theme));
}
if (comp instanceof Container) {
for (Component child : ((Container) comp).getComponents()) {
applyTheme(child, theme);
}
}
}
public static void applyThemeToWindow(Window window) {
if (window == null) return;
window.setBackground(getBgMain());
if (window instanceof RootPaneContainer) {
RootPaneContainer rpc = (RootPaneContainer) window;
if (rpc.getContentPane() != null) {
rpc.getContentPane().setBackground(getBgMain());
applyTheme(rpc.getContentPane());
}
if (rpc.getRootPane() != null && rpc.getRootPane().getJMenuBar() != null) {
applyTheme(rpc.getRootPane().getJMenuBar());
}
}
window.repaint();
}
// =========================================================================
// Custom UI Implementations
// =========================================================================
public static class StyledButtonUI extends BasicButtonUI {
private final ButtonVariant variant;
private boolean hover = false;
public StyledButtonUI(ButtonVariant variant) {
this.variant = variant != null ? variant : ButtonVariant.DEFAULT;
}
@Override
public void installUI(JComponent c) {
super.installUI(c);
AbstractButton b = (AbstractButton) c;
b.addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
hover = true;
b.repaint();
}
@Override
public void mouseExited(MouseEvent e) {
hover = false;
b.repaint();
}
});
}
@Override
public void paint(Graphics g, JComponent c) {
AbstractButton b = (AbstractButton) c;
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int w = c.getWidth();
int h = c.getHeight();
ButtonModel model = b.getModel();
boolean pressed = model.isArmed() && model.isPressed();
boolean enabled = b.isEnabled();
UITheme t = ThemeManager.getTheme();
Color bg = enabled ? ThemeManager.getButtonBg(variant, t, hover, pressed) : (t == UITheme.DARK ? new Color(40, 40, 40) : new Color(220, 220, 220));
Color fg = enabled ? ThemeManager.getButtonFg(variant, t) : ThemeManager.getFgDisabled(t);
// Background
g2.setColor(bg);
g2.fillRoundRect(0, 0, w, h, 6, 6);
// Border
if (variant == ButtonVariant.DEFAULT || variant == ButtonVariant.CANCEL) {
g2.setColor(ThemeManager.getBorder(t));
g2.drawRoundRect(0, 0, w - 1, h - 1, 6, 6);
}
// Text / Icon
FontMetrics fm = g2.getFontMetrics(b.getFont());
String text = b.getText();
if (text != null && !text.isEmpty()) {
g2.setColor(fg);
g2.setFont(b.getFont());
int tx = (w - fm.stringWidth(text)) / 2;
int ty = (h - fm.getHeight()) / 2 + fm.getAscent();
g2.drawString(text, tx, ty);
}
g2.dispose();
}
}
public static class StyledMenuBarUI extends BasicMenuBarUI {
@Override
public void paint(Graphics g, JComponent c) {
g.setColor(ThemeManager.getMenuBarBg());
g.fillRect(0, 0, c.getWidth(), c.getHeight());
}
}
public static class StyledMenuUI extends BasicMenuUI {
@Override
protected void paintBackground(Graphics g, JMenuItem menuItem, Color bgColor) {
ButtonModel model = menuItem.getModel();
if (model.isArmed() || model.isSelected()) {
g.setColor(ThemeManager.getMenuItemHoverBg());
g.fillRect(0, 0, menuItem.getWidth(), menuItem.getHeight());
} else {
g.setColor(ThemeManager.getMenuBarBg());
g.fillRect(0, 0, menuItem.getWidth(), menuItem.getHeight());
}
}
@Override
protected void paintText(Graphics g, JMenuItem menuItem, Rectangle textRect, String text) {
ButtonModel model = menuItem.getModel();
if (model.isArmed() || model.isSelected()) {
g.setColor(ThemeManager.getMenuItemHoverFg());
} else {
g.setColor(ThemeManager.getMenuBarFg());
}
super.paintText(g, menuItem, textRect, text);
}
}
public static class StyledMenuItemUI extends BasicMenuItemUI {
@Override
protected void paintBackground(Graphics g, JMenuItem menuItem, Color bgColor) {
ButtonModel model = menuItem.getModel();
if (model.isArmed() || model.isSelected()) {
g.setColor(ThemeManager.getSelectionBg());
g.fillRect(0, 0, menuItem.getWidth(), menuItem.getHeight());
} else {
g.setColor(ThemeManager.getMenuPopupBg());
g.fillRect(0, 0, menuItem.getWidth(), menuItem.getHeight());
}
}
@Override
protected void paintText(Graphics g, JMenuItem menuItem, Rectangle textRect, String text) {
ButtonModel model = menuItem.getModel();
if (model.isArmed() || model.isSelected()) {
g.setColor(ThemeManager.getSelectionFg());
} else {
g.setColor(ThemeManager.getMenuItemFg());
}
super.paintText(g, menuItem, textRect, text);
}
}
public static class StyledPopupMenuUI extends BasicPopupMenuUI {
@Override
public void paint(Graphics g, JComponent c) {
g.setColor(ThemeManager.getMenuPopupBg());
g.fillRect(0, 0, c.getWidth(), c.getHeight());
}
}
public static class StyledTabbedPaneUI extends BasicTabbedPaneUI {
@Override
protected void installDefaults() {
super.installDefaults();
tabInsets = new Insets(6, 16, 6, 16);
selectedTabPadInsets = new Insets(2, 2, 2, 2);
tabAreaInsets = new Insets(4, 4, 0, 4);
}
@Override
protected void paintTabBackground(Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(isSelected ? ThemeManager.getTabSelectedBg() : ThemeManager.getTabBg());
g2.fillRoundRect(x, y, w, h + 4, 6, 6);
g2.setColor(ThemeManager.getBorder());
g2.drawRoundRect(x, y, w - 1, h + 4, 6, 6);
g2.dispose();
}
@Override
protected void paintText(Graphics g, int tabPlacement, Font font, FontMetrics metrics, int tabIndex, String title, Rectangle textRect, boolean isSelected) {
g.setFont(font);
g.setColor(isSelected ? ThemeManager.getTabSelectedFg() : ThemeManager.getTabFg());
g.drawString(title, textRect.x, textRect.y + metrics.getAscent());
}
@Override
protected void paintContentBorder(Graphics g, int tabPlacement, int selectedIndex) {
int width = tabPane.getWidth();
int height = tabPane.getHeight();
Insets insets = tabPane.getInsets();
int x = insets.left;
int y = insets.top + calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
int w = width - insets.right - insets.left;
int h = height - insets.top - insets.bottom - calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
g.setColor(ThemeManager.getTabSelectedBg());
g.fillRect(x, y, w, h);
g.setColor(ThemeManager.getBorder());
g.drawRect(x, y, w - 1, h - 1);
}
}
}
@@ -0,0 +1,33 @@
package haus.nightmare.j3270.ui;
/**
* Supported UI themes for the j3270 Java desktop interface.
*/
public enum UITheme {
DARK("Dark Mode"),
LIGHT("Light Mode");
private final String displayName;
UITheme(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
@Override
public String toString() {
return displayName;
}
public static UITheme fromString(String name) {
if (name == null) return DARK;
String s = name.trim().toUpperCase();
if ("LIGHT".equals(s) || "LIGHT MODE".equals(s) || "LIGHT_MODE".equals(s)) {
return LIGHT;
}
return DARK;
}
}
@@ -26,25 +26,24 @@ public class UntrustedCertificateDialog extends JDialog {
private void buildUI(String host, int port, X509Certificate[] chain, CertificateException exception) {
JPanel mainPanel = new JPanel(new BorderLayout(12, 12));
mainPanel.setBorder(BorderFactory.createEmptyBorder(16, 16, 16, 16));
mainPanel.setBackground(new Color(30, 30, 30));
// Header
JPanel headerPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 0));
headerPanel.setBackground(new Color(30, 30, 30));
headerPanel.setOpaque(false);
JLabel iconLabel = new JLabel("⚠️");
iconLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 28));
headerPanel.add(iconLabel);
JPanel titleBox = new JPanel(new GridLayout(2, 1, 0, 2));
titleBox.setBackground(new Color(30, 30, 30));
titleBox.setOpaque(false);
JLabel titleLabel = new JLabel("Untrusted SSL Certificate");
titleLabel.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 16));
titleLabel.setForeground(new Color(255, 180, 80));
titleLabel.setForeground(ThemeManager.getOiaFgWarn());
titleBox.add(titleLabel);
JLabel subtitleLabel = new JLabel("The server certificate for " + host + ":" + port + " could not be verified.");
subtitleLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12));
subtitleLabel.setForeground(new Color(180, 180, 180));
subtitleLabel.setForeground(ThemeManager.getFgMuted());
titleBox.add(subtitleLabel);
headerPanel.add(titleBox);
mainPanel.add(headerPanel, BorderLayout.NORTH);
@@ -71,30 +70,27 @@ public class UntrustedCertificateDialog extends JDialog {
JTextArea detailsArea = new JTextArea(sb.toString());
detailsArea.setEditable(false);
detailsArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
detailsArea.setBackground(new Color(20, 20, 20));
detailsArea.setForeground(new Color(210, 210, 210));
ThemeManager.styleTextArea(detailsArea);
detailsArea.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
JScrollPane scrollPane = new JScrollPane(detailsArea);
scrollPane.setPreferredSize(new Dimension(520, 260));
scrollPane.setBorder(BorderFactory.createLineBorder(new Color(60, 60, 60)));
ThemeManager.styleScrollPane(scrollPane);
mainPanel.add(scrollPane, BorderLayout.CENTER);
// Buttons
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 0));
buttonPanel.setBackground(new Color(30, 30, 30));
buttonPanel.setOpaque(false);
JButton cancelBtn = new JButton("Cancel Connection");
cancelBtn.setBackground(new Color(60, 60, 60));
cancelBtn.setForeground(new Color(220, 220, 220));
ThemeManager.styleButton(cancelBtn, ThemeManager.ButtonVariant.CANCEL);
cancelBtn.addActionListener(e -> {
accepted = false;
dispose();
});
JButton trustBtn = new JButton("Connect Anyway");
trustBtn.setBackground(new Color(180, 100, 40));
trustBtn.setForeground(Color.WHITE);
ThemeManager.styleButton(trustBtn, ThemeManager.ButtonVariant.DANGER);
trustBtn.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 13));
trustBtn.addActionListener(e -> {
accepted = true;
@@ -106,6 +102,7 @@ public class UntrustedCertificateDialog extends JDialog {
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
setContentPane(mainPanel);
ThemeManager.applyThemeToWindow(this);
getRootPane().setDefaultButton(trustBtn);
}
@@ -0,0 +1,213 @@
package haus.nightmare.j3270.ui;
import haus.nightmare.j3270.config.Settings;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.io.File;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.jupiter.api.Assertions.*;
public class ThemeManagerTest {
@BeforeEach
public void setup() {
ThemeManager.setTheme(UITheme.DARK);
}
@Test
public void testUIThemeEnum() {
assertEquals("Dark Mode", UITheme.DARK.getDisplayName());
assertEquals("Light Mode", UITheme.LIGHT.getDisplayName());
assertEquals(UITheme.DARK, UITheme.fromString("DARK"));
assertEquals(UITheme.DARK, UITheme.fromString("dark"));
assertEquals(UITheme.DARK, UITheme.fromString("Dark Mode"));
assertEquals(UITheme.LIGHT, UITheme.fromString("LIGHT"));
assertEquals(UITheme.LIGHT, UITheme.fromString("light"));
assertEquals(UITheme.LIGHT, UITheme.fromString("Light Mode"));
assertEquals(UITheme.DARK, UITheme.fromString("invalid_value"));
}
@Test
public void testColorTokensContrast() {
for (UITheme theme : UITheme.values()) {
Color bg = ThemeManager.getBgMain(theme);
Color fg = ThemeManager.getFgMain(theme);
assertNotNull(bg);
assertNotNull(fg);
// Compute luminance contrast
double bgLum = (0.299 * bg.getRed() + 0.587 * bg.getGreen() + 0.114 * bg.getBlue());
double fgLum = (0.299 * fg.getRed() + 0.587 * fg.getGreen() + 0.114 * fg.getBlue());
double lumDiff = Math.abs(bgLum - fgLum);
// Minimum luminance difference to guarantee readability
assertTrue(lumDiff > 120, "Theme " + theme + " text contrast luminance difference " + lumDiff + " must be > 120");
// Verify Menu Bar colors
Color menuBg = ThemeManager.getMenuBarBg(theme);
Color menuFg = ThemeManager.getMenuItemFg(theme);
double menuBgLum = (0.299 * menuBg.getRed() + 0.587 * menuBg.getGreen() + 0.114 * menuBg.getBlue());
double menuFgLum = (0.299 * menuFg.getRed() + 0.587 * menuFg.getGreen() + 0.114 * menuFg.getBlue());
assertTrue(Math.abs(menuBgLum - menuFgLum) > 100, "Menu contrast in " + theme + " must be high");
// Verify Status Bar colors
Color sbBg = ThemeManager.getStatusBarBg(theme);
Color sbNormal = ThemeManager.getOiaFgNormal(theme);
assertNotNull(sbBg);
assertNotNull(sbNormal);
// Verify Button colors
Color btnDefBg = ThemeManager.getButtonBg(ThemeManager.ButtonVariant.DEFAULT, theme);
Color btnDefFg = ThemeManager.getButtonFg(ThemeManager.ButtonVariant.DEFAULT, theme);
assertNotNull(btnDefBg);
assertNotNull(btnDefFg);
Color btnPriBg = ThemeManager.getButtonBg(ThemeManager.ButtonVariant.PRIMARY, theme);
Color btnPriFg = ThemeManager.getButtonFg(ThemeManager.ButtonVariant.PRIMARY, theme);
assertNotNull(btnPriBg);
assertNotNull(btnPriFg);
}
}
@Test
public void testThemeChangeListeners() {
AtomicReference<UITheme> notifiedTheme = new AtomicReference<>(null);
ThemeManager.addThemeChangeListener(notifiedTheme::set);
ThemeManager.setTheme(UITheme.LIGHT);
assertEquals(UITheme.LIGHT, ThemeManager.getTheme());
assertEquals(UITheme.LIGHT, notifiedTheme.get());
assertTrue(ThemeManager.isLight());
assertFalse(ThemeManager.isDark());
ThemeManager.setTheme(UITheme.DARK);
assertEquals(UITheme.DARK, ThemeManager.getTheme());
assertEquals(UITheme.DARK, notifiedTheme.get());
assertTrue(ThemeManager.isDark());
assertFalse(ThemeManager.isLight());
}
@Test
public void testComponentStyling() {
JButton btn = new JButton("Test");
ThemeManager.styleButton(btn, ThemeManager.ButtonVariant.PRIMARY);
assertNotNull(btn.getUI());
assertTrue(btn.getUI() instanceof ThemeManager.StyledButtonUI);
JTextField tf = new JTextField("Test");
ThemeManager.styleTextField(tf);
assertNotNull(tf.getCaretColor());
JTextArea ta = new JTextArea("Test area");
ThemeManager.styleTextArea(ta);
assertNotNull(ta.getCaretColor());
JComboBox<String> cb = new JComboBox<>(new String[]{"A", "B"});
ThemeManager.styleComboBox(cb);
assertNotNull(cb.getUI());
JTable table = new JTable(new DefaultTableModel(new Object[]{"Col1"}, 1));
ThemeManager.styleTable(table);
assertNotNull(table.getSelectionBackground());
JTabbedPane tp = new JTabbedPane();
tp.addTab("Tab1", new JPanel());
ThemeManager.styleTabbedPane(tp);
assertNotNull(tp.getUI());
JMenuBar mb = new JMenuBar();
ThemeManager.styleMenuBar(mb);
JMenu menu = new JMenu("File");
ThemeManager.styleMenu(menu);
JMenuItem mi = new JMenuItem("Open");
ThemeManager.styleMenuItem(mi);
JPopupMenu popup = new JPopupMenu();
ThemeManager.stylePopupMenu(popup);
JCheckBox chk = new JCheckBox("Check");
ThemeManager.styleCheckBox(chk);
JRadioButton rb = new JRadioButton("Radio");
ThemeManager.styleRadioButton(rb);
Border tb = ThemeManager.createTitledBorder("Title");
assertNotNull(tb);
}
@Test
public void testRecursiveApplyTheme() {
JPanel root = new JPanel(new BorderLayout());
JButton b = new JButton("OK");
JTextField f = new JTextField("Data");
JTabbedPane tp = new JTabbedPane();
JPanel tabContent = new JPanel();
JLabel lbl = new JLabel("Label");
tabContent.add(lbl);
tp.addTab("T1", tabContent);
root.add(b, BorderLayout.NORTH);
root.add(f, BorderLayout.CENTER);
root.add(tp, BorderLayout.SOUTH);
ThemeManager.setTheme(UITheme.LIGHT);
ThemeManager.applyTheme(root);
assertEquals(ThemeManager.getBgPanel(UITheme.LIGHT), root.getBackground());
assertEquals(ThemeManager.getFgMain(UITheme.LIGHT), lbl.getForeground());
ThemeManager.setTheme(UITheme.DARK);
ThemeManager.applyTheme(root);
assertEquals(ThemeManager.getBgPanel(UITheme.DARK), root.getBackground());
assertEquals(ThemeManager.getFgMain(UITheme.DARK), lbl.getForeground());
}
@Test
public void testSettingsPersistence() throws Exception {
Settings.setJavaUiTheme(UITheme.LIGHT);
assertEquals(UITheme.LIGHT, Settings.getJavaUiTheme());
File tmpIni = File.createTempFile("j3270_test_config", ".ini");
tmpIni.deleteOnExit();
try {
// Write INI file with javaUiTheme = dark
try (PrintWriter pw = new PrintWriter(tmpIni)) {
pw.println("[j3270]");
pw.println("javaUiTheme = dark");
pw.println("fontFamily = Monospaced");
pw.println("fontSize = 16");
}
Settings.loadFromIniFile(tmpIni.getAbsolutePath());
assertEquals(UITheme.DARK, Settings.getJavaUiTheme());
// Switch to light and export
Settings.setJavaUiTheme(UITheme.LIGHT);
File exportFile = File.createTempFile("j3270_export_config", ".ini");
exportFile.deleteOnExit();
Settings.exportToIniFile(exportFile.getAbsolutePath());
String exportedContent = new String(Files.readAllBytes(exportFile.toPath()));
assertTrue(exportedContent.toLowerCase().contains("javauitheme = light"));
// Reload exported file
Settings.loadFromIniFile(exportFile.getAbsolutePath());
assertEquals(UITheme.LIGHT, Settings.getJavaUiTheme());
} finally {
tmpIni.delete();
}
}
}