Resize debugging
Build and Test j3270 / Build JAR & Run Tests (Java 21) (push) Successful in 39s
Build and Test j3270 / Build JAR & Run Tests (Java 17) (push) Successful in 47s
Build and Test j3270 / Build JAR & Run Tests (Java 11) (push) Successful in 1m36s

This commit is contained in:
2026-09-21 16:26:13 +00:00
parent aa724fbcef
commit d05f1a4c45
3 changed files with 180 additions and 39 deletions
@@ -806,7 +806,19 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
String tlsIndicator = config.isUseTls() ? (config.isTlsVerifyCert() ? " [TLS]" : " [TLS/Unverified]") : ""; String tlsIndicator = config.isUseTls() ? (config.isTlsVerifyCert() ? " [TLS]" : " [TLS/Unverified]") : "";
setTitle("j3270 — " + config.getHost() + ":" + config.getPort() + tlsIndicator); setTitle("j3270 — " + config.getHost() + ":" + config.getPort() + tlsIndicator);
terminalPanel.guardedPack(); int targetRows = 24;
int targetCols = 80;
TerminalModel model = config.getModel();
if (model != null) {
if (model.isDynamic()) {
targetRows = config.getDynamicRows();
targetCols = config.getDynamicCols();
} else {
targetRows = model.getAlternateRows();
targetCols = model.getAlternateCols();
}
}
terminalPanel.packForModel(targetRows, targetCols);
new Thread(() -> { new Thread(() -> {
try { try {
@@ -903,7 +915,8 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
String dev = (client != null && client.getTelnetFSM() != null && client.getTelnetFSM().getConnectedLu() != null) ? String dev = (client != null && client.getTelnetFSM() != null && client.getTelnetFSM().getConnectedLu() != null) ?
" [" + client.getTelnetFSM().getConnectedLu() + "]" : ""; " [" + client.getTelnetFSM().getConnectedLu() + "]" : "";
setTitle("j3270 — " + lastHost + ":" + lastPort + tlsIndicator + dev); setTitle("j3270 — " + lastHost + ":" + lastPort + tlsIndicator + dev);
terminalPanel.guardedPack(); terminalPanel.revalidate();
terminalPanel.repaint();
terminalPanel.requestFocusInWindow(); terminalPanel.requestFocusInWindow();
} }
}); });
@@ -954,7 +967,8 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
@Override @Override
public void onScreenSizeChanged(int rows, int cols) { public void onScreenSizeChanged(int rows, int cols) {
SwingUtilities.invokeLater(() -> { SwingUtilities.invokeLater(() -> {
terminalPanel.guardedPack(); terminalPanel.revalidate();
terminalPanel.repaint();
statusBar.updateStatus(); statusBar.updateStatus();
}); });
} }
@@ -11,12 +11,16 @@ import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import javax.swing.table.DefaultTableModel; import javax.swing.table.DefaultTableModel;
import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader; import javax.swing.table.JTableHeader;
import javax.swing.border.LineBorder; import javax.swing.border.LineBorder;
import javax.swing.border.TitledBorder; import javax.swing.border.TitledBorder;
import haus.nightmare.j3270.storage.HostStorage;
import haus.nightmare.j3270.storage.SavedHost;
public class SettingsDialog extends JDialog { public class SettingsDialog extends JDialog {
@@ -58,8 +62,7 @@ public class SettingsDialog extends JDialog {
// Connection tab // Connection tab
private JComboBox<Settings.StartupBehavior> startupBehaviorBox; private JComboBox<Settings.StartupBehavior> startupBehaviorBox;
private JPanel autoConnectPanel; private JPanel autoConnectPanel;
private JTextField hostField; private JComboBox<SavedHost> savedHostBox;
private JTextField portField;
private JCheckBox autoReconnectCheck; private JCheckBox autoReconnectCheck;
private JSpinner dynamicRowsSpinner; private JSpinner dynamicRowsSpinner;
private JSpinner dynamicColsSpinner; private JSpinner dynamicColsSpinner;
@@ -588,22 +591,33 @@ public class SettingsDialog extends JDialog {
acGbc.insets = new Insets(5, 5, 5, 5); acGbc.insets = new Insets(5, 5, 5, 5);
acGbc.anchor = GridBagConstraints.WEST; acGbc.anchor = GridBagConstraints.WEST;
JLabel hostLabel = new JLabel("Host:"); JLabel savedConnLabel = new JLabel("Saved Connection:");
acGbc.gridx = 0; acGbc.gridx = 0;
acGbc.gridy = 0; acGbc.gridy = 0;
autoConnectPanel.add(hostLabel, acGbc); autoConnectPanel.add(savedConnLabel, acGbc);
hostField = new JTextField(Settings.getAutoConnectHost(), 15); savedHostBox = new JComboBox<>();
savedHostBox.setRenderer(new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (value instanceof SavedHost) {
SavedHost sh = (SavedHost) value;
if (sh.getName() != null && !sh.getName().trim().isEmpty()) {
setText(sh.getName() + " (" + sh.getHost() + ":" + sh.getPort() + ")");
} else {
setText(sh.getHost() + ":" + sh.getPort());
}
} else if (value == null) {
setText("(No saved connections)");
}
return this;
}
});
refreshSavedHostsBox();
acGbc.gridx = 1; acGbc.gridx = 1;
autoConnectPanel.add(hostField, acGbc); acGbc.fill = GridBagConstraints.HORIZONTAL;
autoConnectPanel.add(savedHostBox, acGbc);
JLabel portLabel = new JLabel("Port:");
acGbc.gridx = 2;
autoConnectPanel.add(portLabel, acGbc);
portField = new JTextField(String.valueOf(Settings.getAutoConnectPort()), 4);
acGbc.gridx = 3;
autoConnectPanel.add(portField, acGbc);
gbc.gridx = 0; gbc.gridx = 0;
gbc.gridy = 1; gbc.gridy = 1;
@@ -658,6 +672,30 @@ public class SettingsDialog extends JDialog {
return panel; return panel;
} }
private void refreshSavedHostsBox() {
if (savedHostBox == null) return;
savedHostBox.removeAllItems();
List<SavedHost> savedHosts = HostStorage.getSavedHosts();
SavedHost currentAutoHost = HostStorage.getAutoConnectHost();
SavedHost toSelect = null;
for (SavedHost sh : savedHosts) {
savedHostBox.addItem(sh);
if (currentAutoHost != null && Objects.equals(sh.getId(), currentAutoHost.getId())) {
toSelect = sh;
}
}
if (toSelect != null) {
savedHostBox.setSelectedItem(toSelect);
savedHostBox.setEnabled(true);
} else if (!savedHosts.isEmpty()) {
savedHostBox.setSelectedIndex(0);
savedHostBox.setEnabled(true);
} else {
savedHostBox.addItem(null);
savedHostBox.setEnabled(false);
}
}
private JPanel createEntryAssistPanel() { private JPanel createEntryAssistPanel() {
JPanel panel = new JPanel(); JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
@@ -1187,11 +1225,32 @@ public class SettingsDialog extends JDialog {
if (behavior != null) { if (behavior != null) {
Settings.setStartupBehavior(behavior); Settings.setStartupBehavior(behavior);
} }
if (behavior == Settings.StartupBehavior.AUTO_CONNECT && hostField != null && portField != null) { if (behavior == Settings.StartupBehavior.AUTO_CONNECT && savedHostBox != null) {
Settings.setAutoConnectHost(hostField.getText().trim()); Object sel = savedHostBox.getSelectedItem();
try { if (sel instanceof SavedHost) {
Settings.setAutoConnectPort(Integer.parseInt(portField.getText().trim())); SavedHost selected = (SavedHost) sel;
} catch (NumberFormatException ignored) { List<SavedHost> hosts = HostStorage.getSavedHosts();
for (SavedHost h : hosts) {
h.setAutoConnect(Objects.equals(h.getId(), selected.getId()));
}
HostStorage.saveAll(hosts);
Settings.setAutoConnectHost(selected.getHost());
Settings.setAutoConnectPort(selected.getPort());
Settings.setAutoConnectTls(selected.isUseTls());
Settings.setAutoConnectVerifyCert(selected.isTlsVerifyCert());
Settings.setAutoConnectTn3270e(selected.isTn3270e());
}
} else if (behavior != Settings.StartupBehavior.AUTO_CONNECT) {
List<SavedHost> hosts = HostStorage.getSavedHosts();
boolean modified = false;
for (SavedHost h : hosts) {
if (h.isAutoConnect()) {
h.setAutoConnect(false);
modified = true;
}
}
if (modified) {
HostStorage.saveAll(hosts);
} }
} }
} }
@@ -1371,12 +1430,7 @@ public class SettingsDialog extends JDialog {
autoConnectPanel.setVisible(startupBehaviorBox.getSelectedItem() == Settings.StartupBehavior.AUTO_CONNECT); autoConnectPanel.setVisible(startupBehaviorBox.getSelectedItem() == Settings.StartupBehavior.AUTO_CONNECT);
} }
} }
if (hostField != null) { refreshSavedHostsBox();
hostField.setText(Settings.getAutoConnectHost());
}
if (portField != null) {
portField.setText(String.valueOf(Settings.getAutoConnectPort()));
}
if (autoReconnectCheck != null) { if (autoReconnectCheck != null) {
autoReconnectCheck.setSelected(Settings.getAutoConnectAutoReconnect()); autoReconnectCheck.setSelected(Settings.getAutoConnectAutoReconnect());
} }
@@ -1494,6 +1548,10 @@ public class SettingsDialog extends JDialog {
return btnExport; return btnExport;
} }
public JComboBox<SavedHost> getSavedHostBox() {
return savedHostBox;
}
// ========== Export Config ========== // ========== Export Config ==========
private void exportConfig() { private void exportConfig() {
@@ -29,7 +29,7 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
int cellHeight; int cellHeight;
private int fontAscent; private int fontAscent;
private int fontDescent; private int fontDescent;
private int padding = 4; private int padding = 1;
private int currentFontSize = 16; private int currentFontSize = 16;
// Cursor state // Cursor state
@@ -96,6 +96,8 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
private long lastGraphicsUpdateCount = -1; private long lastGraphicsUpdateCount = -1;
private java.awt.image.BufferedImage cachedGraphicsImage = null; private java.awt.image.BufferedImage cachedGraphicsImage = null;
private boolean resizeGuard = false; private boolean resizeGuard = false;
private Integer targetModelRows = null;
private Integer targetModelCols = null;
private StatusBar statusBar = null; private StatusBar statusBar = null;
// ========== Phase 25: Hotspots & Mouse Wheel ========== // ========== Phase 25: Hotspots & Mouse Wheel ==========
@@ -134,13 +136,21 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
/** /**
* Compute the horizontal render offset to center the grid within the panel. * Compute the horizontal render offset to center the grid within the panel.
* The left bound of the window is the left bound of the left cells when constrained.
* Only if the window is too big is there blank space on the left, and it is equal
* to or less than the space on the right at all times.
*/ */
int getRenderOffsetX() { int getRenderOffsetX() {
int termCols = 80; int termCols = 80;
if (client != null) termCols = client.getScreenBuffer().getDisplayCols(); if (client != null && client.getScreenBuffer() != null) {
termCols = client.getScreenBuffer().getDisplayCols();
}
int gridW = termCols * cellWidth; int gridW = termCols * cellWidth;
int extra = getWidth() - gridW; int extra = getWidth() - gridW;
return Math.max(padding, extra / 2); if (extra > 0) {
return extra / 2;
}
return 0;
} }
/** /**
@@ -148,10 +158,15 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
*/ */
int getRenderOffsetY() { int getRenderOffsetY() {
int termRows = 24; int termRows = 24;
if (client != null) termRows = client.getScreenBuffer().getDisplayRows(); if (client != null && client.getScreenBuffer() != null) {
termRows = client.getScreenBuffer().getDisplayRows();
}
int gridH = termRows * cellHeight; int gridH = termRows * cellHeight;
int extra = getHeight() - gridH; int extra = getHeight() - gridH;
return Math.max(padding, extra / 2); if (extra > 0) {
return extra / 2;
}
return 0;
} }
// Default Host color mapping aligned 1:1 with IBM Host On-Demand ColorRemapModel3270 // Default Host color mapping aligned 1:1 with IBM Host On-Demand ColorRemapModel3270
@@ -1017,6 +1032,33 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
} }
public void packForModel(int modelRows, int modelCols) {
if (isDisplayable() && !SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeLater(() -> packForModel(modelRows, modelCols));
return;
}
resizeGuard = true;
updateCellSize();
this.targetModelRows = modelRows;
this.targetModelCols = modelCols;
revalidate();
Container top = getTopLevelAncestor();
if (top instanceof java.awt.Window) {
java.awt.Window win = (java.awt.Window) top;
if (win instanceof java.awt.Frame) {
if ((((java.awt.Frame) win).getExtendedState() & java.awt.Frame.MAXIMIZED_BOTH) == 0) {
win.pack();
}
} else {
win.pack();
}
}
this.targetModelRows = null;
this.targetModelCols = null;
repaint();
SwingUtilities.invokeLater(() -> resizeGuard = false);
}
public void guardedPack() { public void guardedPack() {
if (isDisplayable() && !SwingUtilities.isEventDispatchThread()) { if (isDisplayable() && !SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeLater(this::guardedPack); SwingUtilities.invokeLater(this::guardedPack);
@@ -1025,12 +1067,35 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
resizeGuard = true; resizeGuard = true;
revalidate(); revalidate();
Container top = getTopLevelAncestor(); Container top = getTopLevelAncestor();
if (top instanceof java.awt.Frame) { if (top instanceof java.awt.Window) {
if ((((java.awt.Frame) top).getExtendedState() & java.awt.Frame.MAXIMIZED_BOTH) == 0) { java.awt.Window win = (java.awt.Window) top;
((java.awt.Window) top).pack(); if (win.isShowing()) {
int termCols = 80;
int termRows = 24;
if (client != null && client.getScreenBuffer() != null) {
termCols = client.getScreenBuffer().getDisplayCols();
termRows = client.getScreenBuffer().getDisplayRows();
}
int gridW = termCols * cellWidth;
int gridH = termRows * cellHeight;
Dimension winSize = win.getSize();
Dimension winPref = win.getPreferredSize();
boolean tooSmall = winSize.width < winPref.width || winSize.height < winPref.height
|| (getWidth() > 0 && getWidth() < gridW)
|| (getHeight() > 0 && getHeight() < gridH);
if (tooSmall) {
repaint();
SwingUtilities.invokeLater(() -> resizeGuard = false);
return;
}
}
if (win instanceof java.awt.Frame) {
if ((((java.awt.Frame) win).getExtendedState() & java.awt.Frame.MAXIMIZED_BOTH) == 0) {
win.pack();
}
} else {
win.pack();
} }
} else if (top instanceof java.awt.Window) {
((java.awt.Window) top).pack();
} }
SwingUtilities.invokeLater(() -> resizeGuard = false); SwingUtilities.invokeLater(() -> resizeGuard = false);
} }
@@ -1873,7 +1938,11 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
@Override @Override
public Dimension getPreferredSize() { public Dimension getPreferredSize() {
if (client != null) { if (targetModelRows != null && targetModelCols != null) {
return new Dimension(targetModelCols * cellWidth + padding * 2,
targetModelRows * cellHeight + padding * 2);
}
if (client != null && client.getScreenBuffer() != null) {
ScreenBuffer sb = client.getScreenBuffer(); ScreenBuffer sb = client.getScreenBuffer();
int displayCols = sb.getDisplayCols(); int displayCols = sb.getDisplayCols();
int displayRows = sb.getDisplayRows(); int displayRows = sb.getDisplayRows();