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]") : "";
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(() -> {
try {
@@ -903,7 +915,8 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
String dev = (client != null && client.getTelnetFSM() != null && client.getTelnetFSM().getConnectedLu() != null) ?
" [" + client.getTelnetFSM().getConnectedLu() + "]" : "";
setTitle("j3270 — " + lastHost + ":" + lastPort + tlsIndicator + dev);
terminalPanel.guardedPack();
terminalPanel.revalidate();
terminalPanel.repaint();
terminalPanel.requestFocusInWindow();
}
});
@@ -954,7 +967,8 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
@Override
public void onScreenSizeChanged(int rows, int cols) {
SwingUtilities.invokeLater(() -> {
terminalPanel.guardedPack();
terminalPanel.revalidate();
terminalPanel.repaint();
statusBar.updateStatus();
});
}
@@ -11,12 +11,16 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader;
import javax.swing.border.LineBorder;
import javax.swing.border.TitledBorder;
import haus.nightmare.j3270.storage.HostStorage;
import haus.nightmare.j3270.storage.SavedHost;
public class SettingsDialog extends JDialog {
@@ -58,8 +62,7 @@ public class SettingsDialog extends JDialog {
// Connection tab
private JComboBox<Settings.StartupBehavior> startupBehaviorBox;
private JPanel autoConnectPanel;
private JTextField hostField;
private JTextField portField;
private JComboBox<SavedHost> savedHostBox;
private JCheckBox autoReconnectCheck;
private JSpinner dynamicRowsSpinner;
private JSpinner dynamicColsSpinner;
@@ -588,22 +591,33 @@ public class SettingsDialog extends JDialog {
acGbc.insets = new Insets(5, 5, 5, 5);
acGbc.anchor = GridBagConstraints.WEST;
JLabel hostLabel = new JLabel("Host:");
JLabel savedConnLabel = new JLabel("Saved Connection:");
acGbc.gridx = 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;
autoConnectPanel.add(hostField, 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);
acGbc.fill = GridBagConstraints.HORIZONTAL;
autoConnectPanel.add(savedHostBox, acGbc);
gbc.gridx = 0;
gbc.gridy = 1;
@@ -658,6 +672,30 @@ public class SettingsDialog extends JDialog {
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() {
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
@@ -1187,11 +1225,32 @@ public class SettingsDialog extends JDialog {
if (behavior != null) {
Settings.setStartupBehavior(behavior);
}
if (behavior == Settings.StartupBehavior.AUTO_CONNECT && hostField != null && portField != null) {
Settings.setAutoConnectHost(hostField.getText().trim());
try {
Settings.setAutoConnectPort(Integer.parseInt(portField.getText().trim()));
} catch (NumberFormatException ignored) {
if (behavior == Settings.StartupBehavior.AUTO_CONNECT && savedHostBox != null) {
Object sel = savedHostBox.getSelectedItem();
if (sel instanceof SavedHost) {
SavedHost selected = (SavedHost) sel;
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);
}
}
if (hostField != null) {
hostField.setText(Settings.getAutoConnectHost());
}
if (portField != null) {
portField.setText(String.valueOf(Settings.getAutoConnectPort()));
}
refreshSavedHostsBox();
if (autoReconnectCheck != null) {
autoReconnectCheck.setSelected(Settings.getAutoConnectAutoReconnect());
}
@@ -1494,6 +1548,10 @@ public class SettingsDialog extends JDialog {
return btnExport;
}
public JComboBox<SavedHost> getSavedHostBox() {
return savedHostBox;
}
// ========== Export Config ==========
private void exportConfig() {
@@ -29,7 +29,7 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
int cellHeight;
private int fontAscent;
private int fontDescent;
private int padding = 4;
private int padding = 1;
private int currentFontSize = 16;
// Cursor state
@@ -96,6 +96,8 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
private long lastGraphicsUpdateCount = -1;
private java.awt.image.BufferedImage cachedGraphicsImage = null;
private boolean resizeGuard = false;
private Integer targetModelRows = null;
private Integer targetModelCols = null;
private StatusBar statusBar = null;
// ========== 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.
* 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 termCols = 80;
if (client != null) termCols = client.getScreenBuffer().getDisplayCols();
if (client != null && client.getScreenBuffer() != null) {
termCols = client.getScreenBuffer().getDisplayCols();
}
int gridW = termCols * cellWidth;
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 termRows = 24;
if (client != null) termRows = client.getScreenBuffer().getDisplayRows();
if (client != null && client.getScreenBuffer() != null) {
termRows = client.getScreenBuffer().getDisplayRows();
}
int gridH = termRows * cellHeight;
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
@@ -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() {
if (isDisplayable() && !SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeLater(this::guardedPack);
@@ -1025,12 +1067,35 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
resizeGuard = true;
revalidate();
Container top = getTopLevelAncestor();
if (top instanceof java.awt.Frame) {
if ((((java.awt.Frame) top).getExtendedState() & java.awt.Frame.MAXIMIZED_BOTH) == 0) {
((java.awt.Window) top).pack();
if (top instanceof java.awt.Window) {
java.awt.Window win = (java.awt.Window) top;
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);
}
@@ -1873,7 +1938,11 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
@Override
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();
int displayCols = sb.getDisplayCols();
int displayRows = sb.getDisplayRows();