Codepage, ECL, and some UI updates
This commit is contained in:
@@ -357,9 +357,12 @@ public class StatusBar extends JPanel {
|
||||
wordWrapStatus.setText("");
|
||||
}
|
||||
|
||||
// Field status: Numeric vs Alphanumeric
|
||||
// Field status: Numeric vs Alphanumeric vs DBCS
|
||||
if (state.isFullSession() && client.getScreenBuffer().isFormatted()) {
|
||||
if (client.getOIA().isNumeric()) {
|
||||
if (client.getOIA().isDBCS()) {
|
||||
fieldTypeStatus.setText("DBCS");
|
||||
fieldTypeStatus.setForeground(ThemeManager.getOiaStatusSysAvail(theme));
|
||||
} else if (client.getOIA().isNumeric()) {
|
||||
fieldTypeStatus.setText("NUM");
|
||||
fieldTypeStatus.setForeground(ThemeManager.getOiaFgWarn(theme));
|
||||
} else {
|
||||
|
||||
@@ -10,6 +10,12 @@ import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.*;
|
||||
import java.awt.event.*;
|
||||
import java.awt.event.InputMethodEvent;
|
||||
import java.awt.event.InputMethodListener;
|
||||
import java.awt.font.TextHitInfo;
|
||||
import java.awt.im.InputMethodRequests;
|
||||
import java.text.AttributedCharacterIterator;
|
||||
import java.text.CharacterIterator;
|
||||
|
||||
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||
|
||||
@@ -18,7 +24,8 @@ import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||
* Supports colors, bold, underline, reverse, blink, GOCA vector graphics,
|
||||
* programmed symbols, search highlighting, and all 3278/3279 attributes.
|
||||
*/
|
||||
public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
public class TerminalPanel extends JPanel implements java.awt.print.Printable, InputMethodListener, InputMethodRequests {
|
||||
|
||||
|
||||
private Telnet3270Client client;
|
||||
|
||||
@@ -216,6 +223,10 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
String cStyle = haus.nightmare.j3270.config.Settings.getCursorStyle();
|
||||
cursorStyle = "UNDERLINE".equalsIgnoreCase(cStyle) ? CursorStyle.UNDERLINE : CursorStyle.BLOCK;
|
||||
|
||||
enableInputMethods(true);
|
||||
addInputMethodListener(this);
|
||||
|
||||
|
||||
// Handle mouse clicks to position cursor, selection, and grab focus
|
||||
MouseAdapter mouseHandler = new MouseAdapter() {
|
||||
@Override
|
||||
@@ -715,6 +726,14 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
repaint();
|
||||
}
|
||||
|
||||
public void select(int startRow, int startCol, int endRow, int endCol) {
|
||||
selectionStartRow = startRow;
|
||||
selectionStartCol = startCol;
|
||||
selectionEndRow = endRow;
|
||||
selectionEndCol = endCol;
|
||||
repaint();
|
||||
}
|
||||
|
||||
public boolean hasSelection() {
|
||||
return selectionStartRow >= 0 && selectionEndRow >= 0 &&
|
||||
!(selectionStartRow == selectionEndRow && selectionStartCol == selectionEndCol);
|
||||
@@ -740,11 +759,23 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
for (int c = c1; c <= c2; c++) {
|
||||
int baddr = r * cols + c;
|
||||
ExtendedAttribute ea = sb.getCell(baddr);
|
||||
char ch = ea.ucs4;
|
||||
if (ea.isFieldAttribute() || ch <= 0x20 || ch == 0xFF) {
|
||||
if (ea.isFieldAttribute()) {
|
||||
line.append(' ');
|
||||
} else if (ea.db == ExtendedAttribute.DB_RIGHT) {
|
||||
if (c > c1) {
|
||||
continue; // Character already added by DB_LEFT
|
||||
} else {
|
||||
line.append(' ');
|
||||
}
|
||||
} else if (ea.db == ExtendedAttribute.DB_SO || ea.db == ExtendedAttribute.DB_SI) {
|
||||
line.append(' ');
|
||||
} else {
|
||||
line.append(ch);
|
||||
char ch = ea.ucs4;
|
||||
if (ch <= 0x20 || ch == 0xFF) {
|
||||
line.append(' ');
|
||||
} else {
|
||||
line.append(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Trim trailing whitespace on copied lines per HoD CutPasteOptions
|
||||
@@ -782,11 +813,23 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
prevRow = curRow;
|
||||
}
|
||||
ExtendedAttribute ea = sb.getCell(addr);
|
||||
char ch = ea.ucs4;
|
||||
if (ea.isFieldAttribute() || ch <= 0x20 || ch == 0xFF) {
|
||||
if (ea.isFieldAttribute()) {
|
||||
line.append(' ');
|
||||
} else if (ea.db == ExtendedAttribute.DB_RIGHT) {
|
||||
if (addr > startAddr) {
|
||||
continue; // Character already added by DB_LEFT
|
||||
} else {
|
||||
line.append(' ');
|
||||
}
|
||||
} else if (ea.db == ExtendedAttribute.DB_SO || ea.db == ExtendedAttribute.DB_SI) {
|
||||
line.append(' ');
|
||||
} else {
|
||||
line.append(ch);
|
||||
char ch = ea.ucs4;
|
||||
if (ch <= 0x20 || ch == 0xFF) {
|
||||
line.append(' ');
|
||||
} else {
|
||||
line.append(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
int end = line.length();
|
||||
@@ -2099,13 +2142,16 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
continue;
|
||||
}
|
||||
|
||||
byte tr = ea.tr != 0 ? ea.tr : (currentFieldEa != null ? currentFieldEa.tr : 0);
|
||||
boolean isOpaque = ((tr & 0xFF) == XAT_OPAQUE);
|
||||
|
||||
if (reverse) {
|
||||
Color tmp = fgColor;
|
||||
fgColor = (rawBg == 0) ? this.bgColor : bgColor;
|
||||
bgColor = tmp;
|
||||
g2.setColor(bgColor);
|
||||
g2.fillRect(x, y, cellWidth, cellHeight);
|
||||
} else if (bgIsExplicit || !bgColor.equals(this.bgColor)) {
|
||||
} else if (isOpaque || bgIsExplicit || !bgColor.equals(this.bgColor)) {
|
||||
g2.setColor(bgColor);
|
||||
g2.fillRect(x, y, cellWidth, cellHeight);
|
||||
}
|
||||
@@ -2120,7 +2166,7 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
if (cs >= 0x40 && client.getProgramSymbolManager() != null && !suppressGlyph) {
|
||||
haus.nightmare.lib3270j.graphics.ProgramSymbolSet.SymbolSlot slot = client.getProgramSymbolManager().getSymbol(cs, ea.ec & 0xFF);
|
||||
if (slot != null) {
|
||||
int symBg = (bgIsExplicit || reverse) ? bgColor.getRGB() : 0;
|
||||
int symBg = (bgIsExplicit || reverse || isOpaque) ? bgColor.getRGB() : 0;
|
||||
haus.nightmare.lib3270j.graphics.PixelBuffer symPb = slot.getScaledPixelBuffer(cellWidth, cellHeight, fgColor.getRGB(), symBg);
|
||||
java.awt.image.BufferedImage img = AwtPixelBufferBridge.toBufferedImage(symPb);
|
||||
if (img != null) {
|
||||
@@ -2130,7 +2176,8 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
}
|
||||
}
|
||||
|
||||
if (!drawnAsPs && !suppressGlyph) {
|
||||
if (!drawnAsPs && !suppressGlyph && ea.db != ExtendedAttribute.DB_RIGHT
|
||||
&& ea.db != ExtendedAttribute.DB_SO && ea.db != ExtendedAttribute.DB_SI) {
|
||||
char ch = ea.ucs4;
|
||||
if (ch > 0x20 && ch != 0xFF) {
|
||||
Font f = bold ? boldTerminalFont : terminalFont;
|
||||
@@ -2269,11 +2316,17 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
boolean isUnderline = (cursorStyle == CursorStyle.UNDERLINE) ||
|
||||
(client.getInputProcessor() != null && client.getInputProcessor().isInsertMode());
|
||||
g2.setColor(CURSOR_COLOR);
|
||||
ExtendedAttribute curEa = (curAddr >= 0 && curAddr < sb.getSize()) ? sb.getDisplayCell(curAddr) : null;
|
||||
boolean isDbcs = curEa != null && (curEa.db == ExtendedAttribute.DB_LEFT || curEa.db == ExtendedAttribute.DB_RIGHT);
|
||||
int cWidth = isDbcs ? (cellWidth * 2) : cellWidth;
|
||||
if (isDbcs && curEa.db == ExtendedAttribute.DB_RIGHT && curCol > 0) {
|
||||
cx -= cellWidth;
|
||||
}
|
||||
if (isUnderline) {
|
||||
int ulH = Math.max(2, cellHeight / 6);
|
||||
g2.fillRect(cx, cy + cellHeight - ulH, cellWidth, ulH);
|
||||
g2.fillRect(cx, cy + cellHeight - ulH, cWidth, ulH);
|
||||
} else {
|
||||
g2.fillRect(cx, cy, cellWidth, cellHeight);
|
||||
g2.fillRect(cx, cy, cWidth, cellHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2462,4 +2515,90 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
||||
paint(g2d);
|
||||
return PAGE_EXISTS;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Java Input Method Framework (IMF / IME) Support
|
||||
// =========================================================================
|
||||
|
||||
@Override
|
||||
public InputMethodRequests getInputMethodRequests() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inputMethodTextChanged(InputMethodEvent event) {
|
||||
if (client == null || !client.getConnectionState().isFullSession()) {
|
||||
event.consume();
|
||||
return;
|
||||
}
|
||||
AttributedCharacterIterator text = event.getText();
|
||||
if (text != null) {
|
||||
int committedCount = event.getCommittedCharacterCount();
|
||||
char c = text.first();
|
||||
for (int i = 0; i < committedCount && c != CharacterIterator.DONE; i++) {
|
||||
client.typeCharacter(c);
|
||||
c = text.next();
|
||||
}
|
||||
refreshScreen();
|
||||
}
|
||||
event.consume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void caretPositionChanged(InputMethodEvent event) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rectangle getTextLocation(TextHitInfo offset) {
|
||||
Point p;
|
||||
try {
|
||||
p = getLocationOnScreen();
|
||||
} catch (IllegalComponentStateException e) {
|
||||
p = new Point(0, 0);
|
||||
}
|
||||
int curRow = 0, curCol = 0;
|
||||
if (client != null && client.getScreenBuffer() != null) {
|
||||
int cols = client.getScreenBuffer().getCols();
|
||||
int curAddr = client.getScreenBuffer().getDisplayCursorAddress();
|
||||
if (cols > 0) {
|
||||
curRow = curAddr / cols;
|
||||
curCol = curAddr % cols;
|
||||
}
|
||||
}
|
||||
int cx = p.x + getRenderOffsetX() + curCol * cellWidth;
|
||||
int cy = p.y + getRenderOffsetY() + curRow * cellHeight;
|
||||
return new Rectangle(cx, cy, cellWidth, cellHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextHitInfo getLocationOffset(int x, int y) {
|
||||
return TextHitInfo.leading(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInsertPositionOffset() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex, AttributedCharacterIterator.Attribute[] attributes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCommittedTextLength() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+242
@@ -0,0 +1,242 @@
|
||||
package haus.nightmare.j3270.ui;
|
||||
|
||||
import haus.nightmare.j3270.J3270App;
|
||||
import haus.nightmare.j3270.config.Settings;
|
||||
import haus.nightmare.j3270.storage.HostStorage;
|
||||
import haus.nightmare.j3270.storage.SavedHost;
|
||||
import haus.nightmare.lib3270j.ConnectionConfig;
|
||||
import haus.nightmare.lib3270j.ConnectionState;
|
||||
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class AutoConnectSettingsAndTerminalCenteringTest {
|
||||
|
||||
private List<SavedHost> originalSavedHosts;
|
||||
private Settings.StartupBehavior originalStartupBehavior;
|
||||
private String originalAutoConnectHost;
|
||||
private int originalAutoConnectPort;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
originalSavedHosts = new ArrayList<>(HostStorage.getSavedHosts());
|
||||
originalStartupBehavior = Settings.getStartupBehavior();
|
||||
originalAutoConnectHost = Settings.getAutoConnectHost();
|
||||
originalAutoConnectPort = Settings.getAutoConnectPort();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
HostStorage.saveAll(originalSavedHosts);
|
||||
Settings.setStartupBehavior(originalStartupBehavior);
|
||||
Settings.setAutoConnectHost(originalAutoConnectHost);
|
||||
Settings.setAutoConnectPort(originalAutoConnectPort);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("TerminalPanel centering: left bound is flush when constrained, centered with left <= right when too big")
|
||||
public void testTerminalPanelCenteringLogic() {
|
||||
TerminalPanel panel = new TerminalPanel();
|
||||
panel.cellWidth = 10;
|
||||
panel.cellHeight = 20;
|
||||
|
||||
Telnet3270Client client = new Telnet3270Client(new ConnectionConfig("localhost", 23));
|
||||
client.getTelnetFSM().setConnectionState(ConnectionState.CONNECTED_3270);
|
||||
ScreenBuffer sb = client.getScreenBuffer();
|
||||
sb.erase(false);
|
||||
panel.setClient(client);
|
||||
|
||||
int cols = sb.getDisplayCols(); // 80
|
||||
int rows = sb.getDisplayRows(); // 24
|
||||
int gridW = cols * panel.cellWidth; // 800
|
||||
int gridH = rows * panel.cellHeight; // 480
|
||||
|
||||
// 1. Panel is smaller than grid: left bound must be 0 (flush left cells), vertical must be 0
|
||||
panel.setSize(gridW - 100, gridH - 50);
|
||||
assertEquals(0, panel.getRenderOffsetX(), "Left bound should be 0 when window width is constrained");
|
||||
assertEquals(0, panel.getRenderOffsetY(), "Top bound should be 0 when window height is constrained");
|
||||
|
||||
// 2. Panel exactly matches grid: offset must be 0
|
||||
panel.setSize(gridW, gridH);
|
||||
assertEquals(0, panel.getRenderOffsetX(), "Offset X must be 0 when width exactly matches grid");
|
||||
assertEquals(0, panel.getRenderOffsetY(), "Offset Y must be 0 when height exactly matches grid");
|
||||
|
||||
// 3. Panel is larger with odd extra (extra = 7)
|
||||
panel.setSize(gridW + 7, gridH + 11);
|
||||
int ox = panel.getRenderOffsetX();
|
||||
int leftSpace = ox;
|
||||
int rightSpace = (gridW + 7) - (ox + gridW);
|
||||
assertTrue(leftSpace <= rightSpace, "Left blank space (" + leftSpace + ") must be <= right space (" + rightSpace + ")");
|
||||
assertEquals(3, leftSpace);
|
||||
assertEquals(4, rightSpace);
|
||||
|
||||
int oy = panel.getRenderOffsetY();
|
||||
int topSpace = oy;
|
||||
int bottomSpace = (gridH + 11) - (oy + gridH);
|
||||
assertTrue(topSpace <= bottomSpace, "Top space must be <= bottom space");
|
||||
assertEquals(5, topSpace);
|
||||
assertEquals(6, bottomSpace);
|
||||
|
||||
// 4. Panel is larger with even extra (extra = 6)
|
||||
panel.setSize(gridW + 6, gridH + 8);
|
||||
assertEquals(3, panel.getRenderOffsetX());
|
||||
assertEquals(4, panel.getRenderOffsetY());
|
||||
|
||||
panel.dispose();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("guardedPack does not resize window when window is too small")
|
||||
public void testGuardedPackDoesNotResizeWhenTooSmall() {
|
||||
try {
|
||||
JFrame frame = new JFrame();
|
||||
TerminalPanel panel = new TerminalPanel();
|
||||
panel.cellWidth = 10;
|
||||
panel.cellHeight = 20;
|
||||
|
||||
Telnet3270Client client = new Telnet3270Client(new ConnectionConfig("localhost", 23));
|
||||
client.getTelnetFSM().setConnectionState(ConnectionState.CONNECTED_3270);
|
||||
panel.setClient(client);
|
||||
|
||||
frame.getContentPane().add(panel);
|
||||
frame.setSize(400, 300);
|
||||
frame.addNotify(); // makes frame displayable
|
||||
|
||||
// Set size explicitly smaller than preferred size (800+ x 480+)
|
||||
frame.setSize(400, 300);
|
||||
Dimension sizeBefore = frame.getSize();
|
||||
|
||||
// Run guardedPack
|
||||
panel.guardedPack();
|
||||
|
||||
// Window should not have expanded
|
||||
Dimension sizeAfter = frame.getSize();
|
||||
assertEquals(sizeBefore.width, sizeAfter.width, "Window width should NOT be enlarged when too small");
|
||||
assertEquals(sizeBefore.height, sizeAfter.height, "Window height should NOT be enlarged when too small");
|
||||
|
||||
frame.dispose();
|
||||
panel.dispose();
|
||||
} catch (HeadlessException ignored) {
|
||||
// Environment without X display
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("SettingsDialog Startup & Auto-Connect presents saved connections dropdown and updates auto-connect host")
|
||||
public void testSettingsDialogSavedConnectionsDropdown() throws Exception {
|
||||
SavedHost host1 = new SavedHost("id-test-1", "Host Alpha", "alpha.mainframe.test", 23, 2, 24, 80, "LU1", true, "TSO", false, true, true, "BOTH", "037", "NONE", "", 0, "", "");
|
||||
SavedHost host2 = new SavedHost("id-test-2", "Host Beta", "beta.mainframe.test", 992, 4, 43, 80, "LU2", false, "VM", true, false, true, "BOTH", "037", "NONE", "", 0, "", "");
|
||||
|
||||
List<SavedHost> testHosts = new ArrayList<>();
|
||||
testHosts.add(host1);
|
||||
testHosts.add(host2);
|
||||
HostStorage.saveAll(testHosts);
|
||||
Settings.setStartupBehavior(Settings.StartupBehavior.AUTO_CONNECT);
|
||||
|
||||
J3270App app;
|
||||
try {
|
||||
app = new J3270App();
|
||||
} catch (HeadlessException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
SettingsDialog dialog = new SettingsDialog(app);
|
||||
|
||||
JComboBox<SavedHost> combo = dialog.getSavedHostBox();
|
||||
assertNotNull(combo, "savedHostBox should not be null");
|
||||
assertEquals(2, combo.getItemCount(), "Dropdown should contain all saved hosts");
|
||||
|
||||
// Host Alpha was autoConnect=true, so it should be initially selected
|
||||
SavedHost initialSel = (SavedHost) combo.getSelectedItem();
|
||||
assertNotNull(initialSel);
|
||||
assertEquals("id-test-1", initialSel.getId());
|
||||
|
||||
// Switch to Host Beta
|
||||
combo.setSelectedIndex(1);
|
||||
|
||||
// Trigger applySettings via reflection
|
||||
java.lang.reflect.Method applyMethod = SettingsDialog.class.getDeclaredMethod("applySettings");
|
||||
applyMethod.setAccessible(true);
|
||||
applyMethod.invoke(dialog);
|
||||
|
||||
// Verify Host Beta is now the auto-connect host
|
||||
SavedHost auto = HostStorage.getAutoConnectHost();
|
||||
assertNotNull(auto, "Auto-connect host should exist");
|
||||
assertEquals("id-test-2", auto.getId());
|
||||
assertEquals("Host Beta", auto.getName());
|
||||
assertEquals("beta.mainframe.test", auto.getHost());
|
||||
assertEquals(992, auto.getPort());
|
||||
|
||||
SavedHost loadedAlpha = HostStorage.findById("id-test-1");
|
||||
assertNotNull(loadedAlpha);
|
||||
assertFalse(loadedAlpha.isAutoConnect(), "Host Alpha should no longer have autoConnect = true");
|
||||
|
||||
// Verify Settings autoConnectHost/Port synchronized
|
||||
assertEquals("beta.mainframe.test", Settings.getAutoConnectHost());
|
||||
assertEquals(992, Settings.getAutoConnectPort());
|
||||
|
||||
// Switch startup behavior to SHOW_CONNECT and apply
|
||||
Field sbField = SettingsDialog.class.getDeclaredField("startupBehaviorBox");
|
||||
sbField.setAccessible(true);
|
||||
@SuppressWarnings("unchecked")
|
||||
JComboBox<Settings.StartupBehavior> sbBox = (JComboBox<Settings.StartupBehavior>) sbField.get(dialog);
|
||||
sbBox.setSelectedItem(Settings.StartupBehavior.SHOW_CONNECT);
|
||||
|
||||
applyMethod.invoke(dialog);
|
||||
assertNull(HostStorage.getAutoConnectHost(), "When startup behavior is SHOW_CONNECT, auto-connect host should be cleared");
|
||||
|
||||
dialog.dispose();
|
||||
app.dispose();
|
||||
} finally {
|
||||
HostStorage.saveAll(originalSavedHosts);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Window size is adjusted once on connect for selected model and left untouched on onScreenSizeChanged")
|
||||
public void testWindowSizedOnceForModelAndNotOnScreenSizeChanged() {
|
||||
try {
|
||||
JFrame frame = new JFrame();
|
||||
TerminalPanel panel = new TerminalPanel();
|
||||
panel.cellWidth = 10;
|
||||
panel.cellHeight = 20;
|
||||
|
||||
frame.getContentPane().add(panel);
|
||||
frame.addNotify();
|
||||
|
||||
// Pack for Model 4 (43 rows, 80 cols)
|
||||
panel.packForModel(43, 80);
|
||||
Dimension model4Size = frame.getSize();
|
||||
assertTrue(model4Size.height >= 43 * 20, "Height should accommodate 43 rows");
|
||||
assertTrue(model4Size.width >= 80 * 10, "Width should accommodate 80 cols");
|
||||
|
||||
// Pack for Model 5 (27 rows, 132 cols)
|
||||
panel.packForModel(27, 132);
|
||||
Dimension model5Size = frame.getSize();
|
||||
assertTrue(model5Size.width >= 132 * 10, "Width should accommodate 132 cols");
|
||||
|
||||
// Set custom size and verify guardedPack leaves the window alone
|
||||
frame.setSize(600, 400);
|
||||
Dimension fixedSize = frame.getSize();
|
||||
|
||||
panel.guardedPack();
|
||||
assertEquals(fixedSize.width, frame.getWidth());
|
||||
assertEquals(fixedSize.height, frame.getHeight());
|
||||
|
||||
frame.dispose();
|
||||
panel.dispose();
|
||||
} catch (HeadlessException ignored) {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package haus.nightmare.j3270.ui;
|
||||
|
||||
import haus.nightmare.lib3270j.ConnectionConfig;
|
||||
import haus.nightmare.lib3270j.ConnectionState;
|
||||
import haus.nightmare.lib3270j.Telnet3270Client;
|
||||
import haus.nightmare.lib3270j.input.InputProcessor;
|
||||
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.InputMethodEvent;
|
||||
import java.text.AttributedString;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class DBCSInputAndClipboardTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("Clipboard selection skips DB_RIGHT cells to avoid duplicating wide characters")
|
||||
public void testDBCSClipboardCopyNoDoubling() {
|
||||
TerminalPanel panel = new TerminalPanel();
|
||||
panel.setSize(800, 600);
|
||||
|
||||
ConnectionConfig config = new ConnectionConfig("localhost", 23);
|
||||
config.setCodePage("930");
|
||||
Telnet3270Client client = new Telnet3270Client(config);
|
||||
client.getTelnetFSM().setConnectionState(ConnectionState.CONNECTED_3270);
|
||||
|
||||
ScreenBuffer sb = client.getScreenBuffer();
|
||||
sb.erase(false);
|
||||
|
||||
// Put a 2-cell DBCS character at cells 0 and 1, followed by SBCS 'A' at cell 2
|
||||
ExtendedAttribute cell0 = sb.getCell(0);
|
||||
cell0.db = ExtendedAttribute.DB_LEFT;
|
||||
cell0.ucs4 = '\u3000';
|
||||
|
||||
ExtendedAttribute cell1 = sb.getCell(1);
|
||||
cell1.db = ExtendedAttribute.DB_RIGHT;
|
||||
cell1.ucs4 = '\u3000';
|
||||
|
||||
ExtendedAttribute cell2 = sb.getCell(2);
|
||||
cell2.db = ExtendedAttribute.DB_NONE;
|
||||
cell2.ucs4 = 'A';
|
||||
|
||||
panel.setClient(client);
|
||||
|
||||
// Stream selection covering cells 0..2 (row 0, col 0..2)
|
||||
panel.select(0, 0, 0, 2);
|
||||
String selected = panel.getSelectedText();
|
||||
|
||||
assertEquals("\u3000A", selected, "Stream selection must not duplicate the DBCS character");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Block selection skips DB_RIGHT cells to avoid duplicating wide characters")
|
||||
public void testDBCSBlockSelectionNoDoubling() {
|
||||
TerminalPanel panel = new TerminalPanel();
|
||||
panel.setSize(800, 600);
|
||||
panel.setBlockSelectMode(true);
|
||||
|
||||
ConnectionConfig config = new ConnectionConfig("localhost", 23);
|
||||
config.setCodePage("930");
|
||||
Telnet3270Client client = new Telnet3270Client(config);
|
||||
client.getTelnetFSM().setConnectionState(ConnectionState.CONNECTED_3270);
|
||||
|
||||
ScreenBuffer sb = client.getScreenBuffer();
|
||||
sb.erase(false);
|
||||
|
||||
// Put a 2-cell DBCS character at cells 0 and 1, followed by SBCS 'B' at cell 2
|
||||
ExtendedAttribute cell0 = sb.getCell(0);
|
||||
cell0.db = ExtendedAttribute.DB_LEFT;
|
||||
cell0.ucs4 = '\u3000';
|
||||
|
||||
ExtendedAttribute cell1 = sb.getCell(1);
|
||||
cell1.db = ExtendedAttribute.DB_RIGHT;
|
||||
cell1.ucs4 = '\u3000';
|
||||
|
||||
ExtendedAttribute cell2 = sb.getCell(2);
|
||||
cell2.db = ExtendedAttribute.DB_NONE;
|
||||
cell2.ucs4 = 'B';
|
||||
|
||||
panel.setClient(client);
|
||||
|
||||
panel.select(0, 0, 0, 2);
|
||||
String selected = panel.getSelectedText();
|
||||
|
||||
assertEquals("\u3000B", selected, "Block selection must not duplicate the DBCS character");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("InputMethodRequests returns cursor bounds and processes committed text")
|
||||
public void testDBCSInputMethodRequestsAndEvents() {
|
||||
TerminalPanel panel = new TerminalPanel();
|
||||
panel.setSize(800, 600);
|
||||
|
||||
ConnectionConfig config = new ConnectionConfig("localhost", 23);
|
||||
config.setCodePage("930");
|
||||
Telnet3270Client client = new Telnet3270Client(config);
|
||||
client.getTelnetFSM().setConnectionState(ConnectionState.CONNECTED_3270);
|
||||
|
||||
panel.setClient(client);
|
||||
|
||||
assertNotNull(panel.getInputMethodRequests(), "getInputMethodRequests must return this");
|
||||
|
||||
Rectangle loc = panel.getTextLocation(null);
|
||||
assertNotNull(loc, "Text location must not be null");
|
||||
assertTrue(loc.width > 0, "Cursor width must be positive");
|
||||
assertTrue(loc.height > 0, "Cursor height must be positive");
|
||||
|
||||
// Simulate committed IME text '\u3000'
|
||||
AttributedString as = new AttributedString("\u3000");
|
||||
InputMethodEvent imeEvent = new InputMethodEvent(panel, InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
|
||||
as.getIterator(), 1, null, null);
|
||||
panel.inputMethodTextChanged(imeEvent);
|
||||
|
||||
ScreenBuffer sb = client.getScreenBuffer();
|
||||
assertEquals(ExtendedAttribute.DB_LEFT, sb.getCell(0).db);
|
||||
assertEquals('\u3000', sb.getCell(0).ucs4);
|
||||
assertEquals(ExtendedAttribute.DB_RIGHT, sb.getCell(1).db);
|
||||
assertEquals('\u3000', sb.getCell(1).ucs4);
|
||||
assertEquals(2, sb.getCursorAddress(), "Cursor should advance by 2 after typing 2-cell DBCS");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("InputProcessor backspace over DBCS character deletes both cells")
|
||||
public void testInputProcessorDBCSTypingAndBackspace() {
|
||||
ConnectionConfig config = new ConnectionConfig("localhost", 23);
|
||||
config.setCodePage("930");
|
||||
Telnet3270Client client = new Telnet3270Client(config);
|
||||
client.getTelnetFSM().setConnectionState(ConnectionState.CONNECTED_3270);
|
||||
|
||||
InputProcessor input = client.getInputProcessor();
|
||||
ScreenBuffer sb = client.getScreenBuffer();
|
||||
sb.erase(false);
|
||||
sb.setCursorAddress(0);
|
||||
|
||||
// Type DBCS character
|
||||
input.typeCharacter('\u3000');
|
||||
assertEquals(2, sb.getCursorAddress());
|
||||
assertEquals(ExtendedAttribute.DB_LEFT, sb.getCell(0).db);
|
||||
assertEquals(ExtendedAttribute.DB_RIGHT, sb.getCell(1).db);
|
||||
|
||||
// Backspace over DBCS character
|
||||
input.backspace();
|
||||
assertEquals(0, sb.getCursorAddress(), "Cursor should back up 2 cells");
|
||||
assertEquals(0, sb.getCell(0).ucs4, "Cell 0 should be deleted (null)");
|
||||
assertEquals(0, sb.getCell(1).ucs4, "Cell 1 should be deleted (null)");
|
||||
assertEquals(ExtendedAttribute.DB_NONE, sb.getCell(0).db);
|
||||
assertEquals(ExtendedAttribute.DB_NONE, sb.getCell(1).db);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user