Codepage, ECL, and some UI updates
This commit is contained in:
@@ -357,9 +357,12 @@ public class StatusBar extends JPanel {
|
|||||||
wordWrapStatus.setText("");
|
wordWrapStatus.setText("");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Field status: Numeric vs Alphanumeric
|
// Field status: Numeric vs Alphanumeric vs DBCS
|
||||||
if (state.isFullSession() && client.getScreenBuffer().isFormatted()) {
|
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.setText("NUM");
|
||||||
fieldTypeStatus.setForeground(ThemeManager.getOiaFgWarn(theme));
|
fieldTypeStatus.setForeground(ThemeManager.getOiaFgWarn(theme));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -10,6 +10,12 @@ import javax.swing.*;
|
|||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.datatransfer.*;
|
import java.awt.datatransfer.*;
|
||||||
import java.awt.event.*;
|
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.*;
|
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,
|
* Supports colors, bold, underline, reverse, blink, GOCA vector graphics,
|
||||||
* programmed symbols, search highlighting, and all 3278/3279 attributes.
|
* 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;
|
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();
|
String cStyle = haus.nightmare.j3270.config.Settings.getCursorStyle();
|
||||||
cursorStyle = "UNDERLINE".equalsIgnoreCase(cStyle) ? CursorStyle.UNDERLINE : CursorStyle.BLOCK;
|
cursorStyle = "UNDERLINE".equalsIgnoreCase(cStyle) ? CursorStyle.UNDERLINE : CursorStyle.BLOCK;
|
||||||
|
|
||||||
|
enableInputMethods(true);
|
||||||
|
addInputMethodListener(this);
|
||||||
|
|
||||||
|
|
||||||
// Handle mouse clicks to position cursor, selection, and grab focus
|
// Handle mouse clicks to position cursor, selection, and grab focus
|
||||||
MouseAdapter mouseHandler = new MouseAdapter() {
|
MouseAdapter mouseHandler = new MouseAdapter() {
|
||||||
@Override
|
@Override
|
||||||
@@ -715,6 +726,14 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
|||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void select(int startRow, int startCol, int endRow, int endCol) {
|
||||||
|
selectionStartRow = startRow;
|
||||||
|
selectionStartCol = startCol;
|
||||||
|
selectionEndRow = endRow;
|
||||||
|
selectionEndCol = endCol;
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean hasSelection() {
|
public boolean hasSelection() {
|
||||||
return selectionStartRow >= 0 && selectionEndRow >= 0 &&
|
return selectionStartRow >= 0 && selectionEndRow >= 0 &&
|
||||||
!(selectionStartRow == selectionEndRow && selectionStartCol == selectionEndCol);
|
!(selectionStartRow == selectionEndRow && selectionStartCol == selectionEndCol);
|
||||||
@@ -740,13 +759,25 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
|||||||
for (int c = c1; c <= c2; c++) {
|
for (int c = c1; c <= c2; c++) {
|
||||||
int baddr = r * cols + c;
|
int baddr = r * cols + c;
|
||||||
ExtendedAttribute ea = sb.getCell(baddr);
|
ExtendedAttribute ea = sb.getCell(baddr);
|
||||||
|
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 {
|
||||||
char ch = ea.ucs4;
|
char ch = ea.ucs4;
|
||||||
if (ea.isFieldAttribute() || ch <= 0x20 || ch == 0xFF) {
|
if (ch <= 0x20 || ch == 0xFF) {
|
||||||
line.append(' ');
|
line.append(' ');
|
||||||
} else {
|
} else {
|
||||||
line.append(ch);
|
line.append(ch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Trim trailing whitespace on copied lines per HoD CutPasteOptions
|
// Trim trailing whitespace on copied lines per HoD CutPasteOptions
|
||||||
int end = line.length();
|
int end = line.length();
|
||||||
while (end > 0 && line.charAt(end - 1) == ' ') {
|
while (end > 0 && line.charAt(end - 1) == ' ') {
|
||||||
@@ -782,13 +813,25 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
|||||||
prevRow = curRow;
|
prevRow = curRow;
|
||||||
}
|
}
|
||||||
ExtendedAttribute ea = sb.getCell(addr);
|
ExtendedAttribute ea = sb.getCell(addr);
|
||||||
|
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 {
|
||||||
char ch = ea.ucs4;
|
char ch = ea.ucs4;
|
||||||
if (ea.isFieldAttribute() || ch <= 0x20 || ch == 0xFF) {
|
if (ch <= 0x20 || ch == 0xFF) {
|
||||||
line.append(' ');
|
line.append(' ');
|
||||||
} else {
|
} else {
|
||||||
line.append(ch);
|
line.append(ch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
int end = line.length();
|
int end = line.length();
|
||||||
while (end > 0 && line.charAt(end - 1) == ' ') {
|
while (end > 0 && line.charAt(end - 1) == ' ') {
|
||||||
end--;
|
end--;
|
||||||
@@ -2099,13 +2142,16 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
byte tr = ea.tr != 0 ? ea.tr : (currentFieldEa != null ? currentFieldEa.tr : 0);
|
||||||
|
boolean isOpaque = ((tr & 0xFF) == XAT_OPAQUE);
|
||||||
|
|
||||||
if (reverse) {
|
if (reverse) {
|
||||||
Color tmp = fgColor;
|
Color tmp = fgColor;
|
||||||
fgColor = (rawBg == 0) ? this.bgColor : bgColor;
|
fgColor = (rawBg == 0) ? this.bgColor : bgColor;
|
||||||
bgColor = tmp;
|
bgColor = tmp;
|
||||||
g2.setColor(bgColor);
|
g2.setColor(bgColor);
|
||||||
g2.fillRect(x, y, cellWidth, cellHeight);
|
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.setColor(bgColor);
|
||||||
g2.fillRect(x, y, cellWidth, cellHeight);
|
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) {
|
if (cs >= 0x40 && client.getProgramSymbolManager() != null && !suppressGlyph) {
|
||||||
haus.nightmare.lib3270j.graphics.ProgramSymbolSet.SymbolSlot slot = client.getProgramSymbolManager().getSymbol(cs, ea.ec & 0xFF);
|
haus.nightmare.lib3270j.graphics.ProgramSymbolSet.SymbolSlot slot = client.getProgramSymbolManager().getSymbol(cs, ea.ec & 0xFF);
|
||||||
if (slot != null) {
|
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);
|
haus.nightmare.lib3270j.graphics.PixelBuffer symPb = slot.getScaledPixelBuffer(cellWidth, cellHeight, fgColor.getRGB(), symBg);
|
||||||
java.awt.image.BufferedImage img = AwtPixelBufferBridge.toBufferedImage(symPb);
|
java.awt.image.BufferedImage img = AwtPixelBufferBridge.toBufferedImage(symPb);
|
||||||
if (img != null) {
|
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;
|
char ch = ea.ucs4;
|
||||||
if (ch > 0x20 && ch != 0xFF) {
|
if (ch > 0x20 && ch != 0xFF) {
|
||||||
Font f = bold ? boldTerminalFont : terminalFont;
|
Font f = bold ? boldTerminalFont : terminalFont;
|
||||||
@@ -2269,11 +2316,17 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
|
|||||||
boolean isUnderline = (cursorStyle == CursorStyle.UNDERLINE) ||
|
boolean isUnderline = (cursorStyle == CursorStyle.UNDERLINE) ||
|
||||||
(client.getInputProcessor() != null && client.getInputProcessor().isInsertMode());
|
(client.getInputProcessor() != null && client.getInputProcessor().isInsertMode());
|
||||||
g2.setColor(CURSOR_COLOR);
|
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) {
|
if (isUnderline) {
|
||||||
int ulH = Math.max(2, cellHeight / 6);
|
int ulH = Math.max(2, cellHeight / 6);
|
||||||
g2.fillRect(cx, cy + cellHeight - ulH, cellWidth, ulH);
|
g2.fillRect(cx, cy + cellHeight - ulH, cWidth, ulH);
|
||||||
} else {
|
} 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);
|
paint(g2d);
|
||||||
return PAGE_EXISTS;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -131,7 +131,18 @@ public class ECLField extends haus.nightmare.lib3270j.ecl.ECLField implements EC
|
|||||||
return delegate != null ? (short) (delegate.getAttribute() & 0xFF) : 0;
|
return delegate != null ? (short) (delegate.getAttribute() & 0xFF) : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public short getAttribute() { return GetAttribute(); }
|
public boolean IsDBCS() {
|
||||||
|
return delegate != null && delegate.isDBCS();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDBCS() { return IsDBCS(); }
|
||||||
|
|
||||||
|
public boolean IsDBCSOnly() {
|
||||||
|
return delegate != null && delegate.isDBCSOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDBCSOnly() { return IsDBCSOnly(); }
|
||||||
|
|
||||||
public haus.nightmare.lib3270j.ecl.ECLField getDelegate() { return delegate; }
|
public haus.nightmare.lib3270j.ecl.ECLField getDelegate() { return delegate; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -511,4 +511,61 @@ public class ECLPS implements ECLConstants, Serializable, Cloneable {
|
|||||||
public int GetScreen(char[] buffer, int len, int row, int col, int length, int plane) {
|
public int GetScreen(char[] buffer, int len, int row, int col, int length, int plane) {
|
||||||
return delegate.GetScreen(buffer, len, row, col, length, plane);
|
return delegate.GetScreen(buffer, len, row, col, length, plane);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== DBCS Character Status ==========
|
||||||
|
|
||||||
|
public boolean IsDBCSChar(int pos) {
|
||||||
|
return delegate != null && delegate.IsDBCSChar(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isDBCSChar(int pos) { return IsDBCSChar(pos); }
|
||||||
|
|
||||||
|
public boolean IsDBCSChar(int row, int col) {
|
||||||
|
return delegate != null && delegate.IsDBCSChar(row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDBCSChar(int row, int col) { return IsDBCSChar(row, col); }
|
||||||
|
|
||||||
|
public boolean IsDBCS1stChar(int pos) {
|
||||||
|
return delegate != null && delegate.IsDBCS1stChar(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDBCS1stChar(int pos) { return IsDBCS1stChar(pos); }
|
||||||
|
|
||||||
|
public boolean IsDBCS1stChar(int row, int col) {
|
||||||
|
return delegate != null && delegate.IsDBCS1stChar(row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDBCS1stChar(int row, int col) { return IsDBCS1stChar(row, col); }
|
||||||
|
|
||||||
|
public boolean IsDBCS2ndChar(int pos) {
|
||||||
|
return delegate != null && delegate.IsDBCS2ndChar(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDBCS2ndChar(int pos) { return IsDBCS2ndChar(pos); }
|
||||||
|
|
||||||
|
public boolean IsDBCS2ndChar(int row, int col) {
|
||||||
|
return delegate != null && delegate.IsDBCS2ndChar(row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDBCS2ndChar(int row, int col) { return IsDBCS2ndChar(row, col); }
|
||||||
|
|
||||||
|
public boolean IsSOChar(int pos) {
|
||||||
|
return delegate != null && delegate.IsSOChar(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSOChar(int pos) { return IsSOChar(pos); }
|
||||||
|
|
||||||
|
public boolean IsSIChar(int pos) {
|
||||||
|
return delegate != null && delegate.IsSIChar(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSIChar(int pos) { return IsSIChar(pos); }
|
||||||
|
|
||||||
|
public boolean IsSOSIChar(int pos) {
|
||||||
|
return delegate != null && delegate.IsSOSIChar(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSOSIChar(int pos) { return IsSOSIChar(pos); }
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,12 +27,16 @@ public abstract class AbstractDBCSCodePage extends AbstractCodePage {
|
|||||||
int[] sbcsMapping, String nioCharsetName) {
|
int[] sbcsMapping, String nioCharsetName) {
|
||||||
super(id, description, cpgid, cgcsgid, sbcsMapping);
|
super(id, description, cpgid, cgcsgid, sbcsMapping);
|
||||||
if (nioCharsetName != null) {
|
if (nioCharsetName != null) {
|
||||||
|
for (String candidate : nioCharsetName.split(",")) {
|
||||||
|
candidate = candidate.trim();
|
||||||
try {
|
try {
|
||||||
if (Charset.isSupported(nioCharsetName)) {
|
if (Charset.isSupported(candidate)) {
|
||||||
this.nioCharset = Charset.forName(nioCharsetName);
|
this.nioCharset = Charset.forName(candidate);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Initialize ideographic space (0x4040 <-> \u3000)
|
// Initialize ideographic space (0x4040 <-> \u3000)
|
||||||
registerDbcsPair(0x4040, '\u3000');
|
registerDbcsPair(0x4040, '\u3000');
|
||||||
}
|
}
|
||||||
@@ -250,4 +254,78 @@ public abstract class AbstractDBCSCodePage extends AbstractCodePage {
|
|||||||
public byte[] utf8WithSOSIToEbcdic(String s) {
|
public byte[] utf8WithSOSIToEbcdic(String s) {
|
||||||
return stringToEbcdic(s, true);
|
return stringToEbcdic(s, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Double-byte character set Graphic Character Set Global ID (DBGID/CGCSGID) matching HoD DBGIDTable.
|
||||||
|
*/
|
||||||
|
public int getDbCgcsgid() {
|
||||||
|
switch (getCpgid()) {
|
||||||
|
case 930:
|
||||||
|
case 939:
|
||||||
|
case 290:
|
||||||
|
return 370; // 0x0172
|
||||||
|
case 933:
|
||||||
|
return 934; // 0x03A6
|
||||||
|
case 935:
|
||||||
|
return 937; // 0x03A9
|
||||||
|
case 937:
|
||||||
|
return 935; // 0x03A7
|
||||||
|
case 1390:
|
||||||
|
case 1399:
|
||||||
|
case 1364:
|
||||||
|
case 1371:
|
||||||
|
case 1388:
|
||||||
|
return 65535; // 0xFFFF
|
||||||
|
default:
|
||||||
|
return 370;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Double-byte character set Code Page Global ID (DBCS CPGID) matching HoD DBGIDTable.
|
||||||
|
*/
|
||||||
|
public int getDbCpgid() {
|
||||||
|
switch (getCpgid()) {
|
||||||
|
case 930:
|
||||||
|
case 939:
|
||||||
|
case 290:
|
||||||
|
case 1390:
|
||||||
|
case 1399:
|
||||||
|
return 300; // 0x012C
|
||||||
|
case 933:
|
||||||
|
case 1364:
|
||||||
|
return 834; // 0x0342
|
||||||
|
case 935:
|
||||||
|
case 1388:
|
||||||
|
return 837; // 0x0345
|
||||||
|
case 937:
|
||||||
|
case 1371:
|
||||||
|
return 835; // 0x0343
|
||||||
|
default:
|
||||||
|
return 300;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Double-byte character set section start byte matching HoD DBCSSectTable.
|
||||||
|
*/
|
||||||
|
public byte getDbcsSectionStart() {
|
||||||
|
return 0x41;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Double-byte character set section end byte matching HoD DBCSSectTable.
|
||||||
|
*/
|
||||||
|
public byte getDbcsSectionEnd() {
|
||||||
|
switch (getCpgid()) {
|
||||||
|
case 290:
|
||||||
|
case 930:
|
||||||
|
case 935:
|
||||||
|
case 939:
|
||||||
|
return 0x7F;
|
||||||
|
default:
|
||||||
|
return (byte) 0xFE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -311,6 +311,12 @@ public class CodePageRegistry {
|
|||||||
register(new Cp1388());
|
register(new Cp1388());
|
||||||
register(new Cp1371());
|
register(new Cp1371());
|
||||||
|
|
||||||
|
// Japanese Katakana & Latin SBCS / DBCS Euro
|
||||||
|
register(new Cp290());
|
||||||
|
register(new Cp1027());
|
||||||
|
register(new Cp1390());
|
||||||
|
register(new Cp1399());
|
||||||
|
|
||||||
// Setup Extended Aliases
|
// Setup Extended Aliases
|
||||||
addAlias("ar", "420");
|
addAlias("ar", "420");
|
||||||
addAlias("arabic", "420");
|
addAlias("arabic", "420");
|
||||||
@@ -360,10 +366,12 @@ public class CodePageRegistry {
|
|||||||
addAlias("zh-tw-ext", "1371");
|
addAlias("zh-tw-ext", "1371");
|
||||||
|
|
||||||
// HoD Converter aliases mapping all HoD converter names to CodePage / Charset
|
// HoD Converter aliases mapping all HoD converter names to CodePage / Charset
|
||||||
addAlias("1390", "930");
|
addAlias("1390", "1390");
|
||||||
addAlias("1390jis2004", "930");
|
addAlias("1390jis2004", "1390");
|
||||||
addAlias("1399", "939");
|
addAlias("1399", "1399");
|
||||||
addAlias("1399jis2004", "939");
|
addAlias("1399jis2004", "1399");
|
||||||
|
addAlias("290", "290");
|
||||||
|
addAlias("1027", "1027");
|
||||||
addAlias("937macau", "937");
|
addAlias("937macau", "937");
|
||||||
addAlias("1364", "933");
|
addAlias("1364", "933");
|
||||||
addAlias("1379", "937");
|
addAlias("1379", "937");
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 1027 (Japanese Latin Extended SBCS).
|
||||||
|
* Conforms 1:1 to IBM Host On-Demand com.ibm.eNetwork.HOD.converters.ja.ByteToCharCp939.singleByteToChar.
|
||||||
|
* CCSID / CPGID: 1027, GCSGID: 1172.
|
||||||
|
*/
|
||||||
|
public class Cp1027 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "1027";
|
||||||
|
public static final String DESCRIPTION = "Japanese Latin Extended - EBCDIC";
|
||||||
|
public static final int CPGID = 1027;
|
||||||
|
public static final int GCSGID = 1172;
|
||||||
|
|
||||||
|
private static final String SBCS_STR =
|
||||||
|
"\u0000\u0001\u0002\u0003\u009c\t\u0086\u007f\u0097\u008d\u008e\u000b\f\r\u000e\u000f" +
|
||||||
|
"\u0010\u0011\u0012\u0013\u009d\u0085\b\u0087\u0018\u0019\u0092\u008f\u001c\u001d\u001e\u001f" +
|
||||||
|
"\u0080\u0081\u0082\u0083\u0084\n\u0017\u001b\u0088\u0089\u008a\u008b\u008c\u0005\u0006\u0007" +
|
||||||
|
"\u0090\u0091\u0016\u0093\u0094\u0095\u0096\u0004\u0098\u0099\u009a\u009b\u0014\u0015\u009e\u001a" +
|
||||||
|
" \ufffd\uff61\uff62\uff63\uff64\uff65\uff66\uff67\uff68\u00a2.<(+|" +
|
||||||
|
"&\uff69\uff6a\uff6b\uff6c\uff6d\uff6e\uff6f\uff70\uff71!$*);\u00ac" +
|
||||||
|
"-/\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79\ufffd,%_>?" +
|
||||||
|
"\uff7a\uff7b\uff7c\uff7d\uff7e\uff7f\uff80\uff81\uff82`:#@'=\"" +
|
||||||
|
"\ufffdabcdefghi\uff83\uff84\uff85\uff86\uff87\uff88" +
|
||||||
|
"\ufffdjklmnopqr\uff89\uff8a\uff8b\uff8c\uff8d\uff8e" +
|
||||||
|
"\u203e~stuvwxyz\uff8f\uff90\uff91[\uff92\uff93^" +
|
||||||
|
"\u00a3\u00a5\uff94\uff95\uff96\uff97\uff98\uff99\uff9a\uff9b\uff9c\uff9d]\uff9e\uff9f" +
|
||||||
|
"{ABCDEFGHI\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd" +
|
||||||
|
"}JKLMNOPQR\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd" +
|
||||||
|
"\\\ufffdSTUVWXYZ\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd" +
|
||||||
|
"0123456789\ufffd\ufffd\ufffd\ufffd\ufffd\u009f";
|
||||||
|
|
||||||
|
public static final int[] MAPPING = new int[256];
|
||||||
|
static {
|
||||||
|
for (int i = 0; i < 256; i++) {
|
||||||
|
MAPPING[i] = (i < SBCS_STR.length()) ? SBCS_STR.charAt(i) : i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cp1027() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 1390 (Japanese Katakana Extended Mixed DBCS with Euro support: host SBCS CP290+Euro / DBCS CP16684).
|
||||||
|
* CCSID / CPGID: 1390, GCSGID: 1172.
|
||||||
|
*/
|
||||||
|
public class Cp1390 extends AbstractDBCSCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "1390";
|
||||||
|
public static final String DESCRIPTION = "Japanese Katakana Extended Mixed DBCS (with Euro)";
|
||||||
|
public static final int CPGID = 1390;
|
||||||
|
public static final int GCSGID = 1172;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = Cp290.MAPPING.clone();
|
||||||
|
static {
|
||||||
|
// Euro symbol at 0xE1
|
||||||
|
MAPPING[0xE1] = 0x20AC;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cp1390() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, MAPPING, "x-IBM1390,IBM1390,Cp1390,x-IBM930,IBM930,Cp930");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 1399 (Japanese Latin Extended Mixed DBCS with Euro support: host SBCS CP1027+Euro / DBCS CP16684).
|
||||||
|
* CCSID / CPGID: 1399, GCSGID: 1172.
|
||||||
|
*/
|
||||||
|
public class Cp1399 extends AbstractDBCSCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "1399";
|
||||||
|
public static final String DESCRIPTION = "Japanese Latin Extended Mixed DBCS (with Euro)";
|
||||||
|
public static final int CPGID = 1399;
|
||||||
|
public static final int GCSGID = 1172;
|
||||||
|
|
||||||
|
public static final int[] MAPPING = Cp1027.MAPPING.clone();
|
||||||
|
static {
|
||||||
|
// Euro symbol at 0xE1
|
||||||
|
MAPPING[0xE1] = 0x20AC;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cp1399() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, MAPPING, "x-IBM1399,IBM1399,Cp1399,x-IBM939,IBM939,Cp939");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package haus.nightmare.lib3270j.charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IBM Code Page 290 (Japanese Katakana Extended SBCS).
|
||||||
|
* Conforms 1:1 to IBM Host On-Demand com.ibm.eNetwork.HOD.converters.ja.ByteToCharCp290.
|
||||||
|
* CCSID / CPGID: 290, GCSGID: 1172.
|
||||||
|
*/
|
||||||
|
public class Cp290 extends AbstractCodePage {
|
||||||
|
|
||||||
|
public static final String ID = "290";
|
||||||
|
public static final String DESCRIPTION = "Japanese Katakana Extended - EBCDIC";
|
||||||
|
public static final int CPGID = 290;
|
||||||
|
public static final int GCSGID = 1172;
|
||||||
|
|
||||||
|
private static final String SBCS_STR =
|
||||||
|
"\u0000\u0001\u0002\u0003\u009c\t\u0086\u007f\u0097\u008d\u008e\u000b\f\r\u000e\u000f" +
|
||||||
|
"\u0010\u0011\u0012\u0013\u009d\u0085\b\u0087\u0018\u0019\u0092\u008f\u001c\u001d\u001e\u001f" +
|
||||||
|
"\u0080\u0081\u0082\u0083\u0084\n\u0017\u001b\u0088\u0089\u008a\u008b\u008c\u0005\u0006\u0007" +
|
||||||
|
"\u0090\u0091\u0016\u0093\u0094\u0095\u0096\u0004\u0098\u0099\u009a\u009b\u0014\u0015\u009e\u001a" +
|
||||||
|
" \uff61\uff62\uff63\uff64\uff65\uff66\uff67\uff68\uff69\u00a3.<(+|" +
|
||||||
|
"&\uff6a\uff6b\uff6c\uff6d\uff6e\uff6f\ufffd\uff70\ufffd!\u00a5*);\u00ac" +
|
||||||
|
"-/\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\ufffd,%_>?" +
|
||||||
|
"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000:#@'=\"" +
|
||||||
|
"\u0000\uff71\uff72\uff73\uff74\uff75\uff76\uff77\uff78\uff79\uff7a\u0000\uff7b\uff7c\uff7d\uff7e" +
|
||||||
|
"\uff7f\uff80\uff81\uff82\uff83\uff84\uff85\uff86\uff87\uff88\uff89\u0000\ufffd\uff8a\uff8b\uff8c" +
|
||||||
|
"\u0000\u203e\uff8d\uff8e\uff8f\uff90\uff91\uff92\uff93\uff94\uff95\u0000\uff96\uff97\uff98\uff99" +
|
||||||
|
"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\uff9a\uff9b\uff9c\uff9d\uff9e\uff9f" +
|
||||||
|
"\u0000ABCDEFGHI\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd" +
|
||||||
|
"\u0000JKLMNOPQR\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd" +
|
||||||
|
"$\ufffdSTUVWXYZ\ufffd\ufffd\ufffd\ufffd\ufffd\ufffd" +
|
||||||
|
"0123456789\ufffd\ufffd\ufffd\ufffd\ufffd\u009f";
|
||||||
|
|
||||||
|
public static final int[] MAPPING = new int[256];
|
||||||
|
static {
|
||||||
|
for (int i = 0; i < 256; i++) {
|
||||||
|
MAPPING[i] = (i < SBCS_STR.length()) ? SBCS_STR.charAt(i) : i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cp290() {
|
||||||
|
super(ID, DESCRIPTION, CPGID, GCSGID, MAPPING);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,6 @@ public class Cp930 extends AbstractDBCSCodePage {
|
|||||||
public static final int GCSGID = 1172;
|
public static final int GCSGID = 1172;
|
||||||
|
|
||||||
public Cp930() {
|
public Cp930() {
|
||||||
super(ID, DESCRIPTION, CPGID, GCSGID, Cp037.MAPPING, "x-IBM930");
|
super(ID, DESCRIPTION, CPGID, GCSGID, Cp290.MAPPING, "x-IBM930,IBM930,Cp930");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,6 @@ public class Cp939 extends AbstractDBCSCodePage {
|
|||||||
public static final int GCSGID = 1172;
|
public static final int GCSGID = 1172;
|
||||||
|
|
||||||
public Cp939() {
|
public Cp939() {
|
||||||
super(ID, DESCRIPTION, CPGID, GCSGID, Cp037.MAPPING, "x-IBM939");
|
super(ID, DESCRIPTION, CPGID, GCSGID, Cp1027.MAPPING, "x-IBM939,IBM939,Cp939");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,35 @@ public class EbcdicTranslator {
|
|||||||
return isDBCSCodePage();
|
return isDBCSCodePage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized int getDbCgcsgid() {
|
||||||
|
if (activeCodePage instanceof AbstractDBCSCodePage) {
|
||||||
|
return ((AbstractDBCSCodePage) activeCodePage).getDbCgcsgid();
|
||||||
|
}
|
||||||
|
return 370;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized int getDbCpgid() {
|
||||||
|
if (activeCodePage instanceof AbstractDBCSCodePage) {
|
||||||
|
return ((AbstractDBCSCodePage) activeCodePage).getDbCpgid();
|
||||||
|
}
|
||||||
|
return 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized byte getDbcsSectionStart() {
|
||||||
|
if (activeCodePage instanceof AbstractDBCSCodePage) {
|
||||||
|
return ((AbstractDBCSCodePage) activeCodePage).getDbcsSectionStart();
|
||||||
|
}
|
||||||
|
return 0x41;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized byte getDbcsSectionEnd() {
|
||||||
|
if (activeCodePage instanceof AbstractDBCSCodePage) {
|
||||||
|
return ((AbstractDBCSCodePage) activeCodePage).getDbcsSectionEnd();
|
||||||
|
}
|
||||||
|
return 0x7F;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Custom Character Translation Overrides
|
// Custom Character Translation Overrides
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|||||||
+158
-1
@@ -60,6 +60,8 @@ public class DataStreamProcessor {
|
|||||||
private byte currentGr = 0;
|
private byte currentGr = 0;
|
||||||
private byte currentCs = 0;
|
private byte currentCs = 0;
|
||||||
private byte currentOl = 0;
|
private byte currentOl = 0;
|
||||||
|
private byte currentTr = 0;
|
||||||
|
private boolean inDBCS = false;
|
||||||
|
|
||||||
/** Functional interface for sending output back through the telnet stack. */
|
/** Functional interface for sending output back through the telnet stack. */
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
@@ -252,6 +254,7 @@ public class DataStreamProcessor {
|
|||||||
currentGr = 0;
|
currentGr = 0;
|
||||||
currentCs = 0;
|
currentCs = 0;
|
||||||
currentOl = 0;
|
currentOl = 0;
|
||||||
|
currentTr = 0;
|
||||||
break;
|
break;
|
||||||
case CMD_WSF:
|
case CMD_WSF:
|
||||||
case SNA_CMD_WSF:
|
case SNA_CMD_WSF:
|
||||||
@@ -395,6 +398,8 @@ public class DataStreamProcessor {
|
|||||||
currentGr = 0;
|
currentGr = 0;
|
||||||
currentCs = 0;
|
currentCs = 0;
|
||||||
currentOl = 0;
|
currentOl = 0;
|
||||||
|
currentTr = 0;
|
||||||
|
inDBCS = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eraseFirst) {
|
if (eraseFirst) {
|
||||||
@@ -405,6 +410,8 @@ public class DataStreamProcessor {
|
|||||||
currentGr = 0;
|
currentGr = 0;
|
||||||
currentCs = 0;
|
currentCs = 0;
|
||||||
currentOl = 0;
|
currentOl = 0;
|
||||||
|
currentTr = 0;
|
||||||
|
inDBCS = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process orders and data starting at byte 2
|
// Process orders and data starting at byte 2
|
||||||
@@ -435,6 +442,7 @@ public class DataStreamProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case ORDER_SF: { // Start Field
|
case ORDER_SF: { // Start Field
|
||||||
|
inDBCS = false;
|
||||||
if (pos + 1 >= end) {
|
if (pos + 1 >= end) {
|
||||||
pos = end;
|
pos = end;
|
||||||
break;
|
break;
|
||||||
@@ -456,6 +464,7 @@ public class DataStreamProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case ORDER_SFE: { // Start Field Extended
|
case ORDER_SFE: { // Start Field Extended
|
||||||
|
inDBCS = false;
|
||||||
if (pos + 1 >= end) {
|
if (pos + 1 >= end) {
|
||||||
pos = end;
|
pos = end;
|
||||||
break;
|
break;
|
||||||
@@ -502,6 +511,7 @@ public class DataStreamProcessor {
|
|||||||
currentGr = 0;
|
currentGr = 0;
|
||||||
currentCs = 0;
|
currentCs = 0;
|
||||||
currentOl = 0;
|
currentOl = 0;
|
||||||
|
currentTr = 0;
|
||||||
break;
|
break;
|
||||||
case XA_FOREGROUND:
|
case XA_FOREGROUND:
|
||||||
currentFg = (byte) attrValue;
|
currentFg = (byte) attrValue;
|
||||||
@@ -530,6 +540,9 @@ public class DataStreamProcessor {
|
|||||||
case 0x84:
|
case 0x84:
|
||||||
currentOl = (byte) attrValue;
|
currentOl = (byte) attrValue;
|
||||||
break;
|
break;
|
||||||
|
case XA_TRANSPARENCY:
|
||||||
|
currentTr = (byte) attrValue;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
pos += 3;
|
pos += 3;
|
||||||
lastWasOrder = true;
|
lastWasOrder = true;
|
||||||
@@ -591,6 +604,44 @@ public class DataStreamProcessor {
|
|||||||
if (toAddr >= size)
|
if (toAddr >= size)
|
||||||
toAddr = toAddr % size;
|
toAddr = toAddr % size;
|
||||||
|
|
||||||
|
boolean isDbcsSession = (translator != null && translator.isDBCS());
|
||||||
|
if (isDbcsSession && (inDBCS || currentCs == ExtendedAttribute.CS_DBCS) && pos + 4 < end) {
|
||||||
|
int fillChar2 = data[pos + 4] & 0xFF;
|
||||||
|
char uni = (translator.getCodePage() != null) ? translator.getCodePage().dbcsToUnicode(fillChar, fillChar2) : '?';
|
||||||
|
do {
|
||||||
|
ExtendedAttribute ea1 = screen.getCell(baddr);
|
||||||
|
ea1.fa = 0;
|
||||||
|
ea1.ec = (byte) fillChar;
|
||||||
|
ea1.ucs4 = uni;
|
||||||
|
ea1.fg = currentFg;
|
||||||
|
ea1.bg = currentBg;
|
||||||
|
ea1.gr = (byte) currentGr;
|
||||||
|
ea1.cs = ExtendedAttribute.CS_DBCS;
|
||||||
|
ea1.db = ExtendedAttribute.DB_LEFT;
|
||||||
|
ea1.ol = currentOl;
|
||||||
|
ea1.tr = currentTr;
|
||||||
|
baddr = (baddr + 1) % size;
|
||||||
|
if (baddr == toAddr) break;
|
||||||
|
|
||||||
|
ExtendedAttribute ea2 = screen.getCell(baddr);
|
||||||
|
ea2.fa = 0;
|
||||||
|
ea2.ec = (byte) fillChar2;
|
||||||
|
ea2.ucs4 = uni;
|
||||||
|
ea2.fg = currentFg;
|
||||||
|
ea2.bg = currentBg;
|
||||||
|
ea2.gr = (byte) currentGr;
|
||||||
|
ea2.cs = ExtendedAttribute.CS_DBCS;
|
||||||
|
ea2.db = ExtendedAttribute.DB_RIGHT;
|
||||||
|
ea2.ol = currentOl;
|
||||||
|
ea2.tr = currentTr;
|
||||||
|
baddr = (baddr + 1) % size;
|
||||||
|
} while (baddr != toAddr);
|
||||||
|
screen.setBufferAddress(baddr);
|
||||||
|
pos += 5;
|
||||||
|
lastWasOrder = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle GE (graphic escape) prefix
|
// Handle GE (graphic escape) prefix
|
||||||
byte fillCs = currentCs;
|
byte fillCs = currentCs;
|
||||||
char fillUcs4 = 0;
|
char fillUcs4 = 0;
|
||||||
@@ -614,7 +665,9 @@ public class DataStreamProcessor {
|
|||||||
ea.bg = currentBg;
|
ea.bg = currentBg;
|
||||||
ea.gr = (byte) currentGr;
|
ea.gr = (byte) currentGr;
|
||||||
ea.cs = fillCs;
|
ea.cs = fillCs;
|
||||||
|
ea.db = ExtendedAttribute.DB_NONE;
|
||||||
ea.ol = currentOl;
|
ea.ol = currentOl;
|
||||||
|
ea.tr = currentTr;
|
||||||
baddr = (baddr + 1) % size;
|
baddr = (baddr + 1) % size;
|
||||||
} while (baddr != toAddr);
|
} while (baddr != toAddr);
|
||||||
screen.setBufferAddress(baddr);
|
screen.setBufferAddress(baddr);
|
||||||
@@ -644,6 +697,7 @@ public class DataStreamProcessor {
|
|||||||
ea.gr = 0;
|
ea.gr = 0;
|
||||||
ea.cs = 0;
|
ea.cs = 0;
|
||||||
ea.ol = 0;
|
ea.ol = 0;
|
||||||
|
ea.tr = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
baddr = (baddr + 1) % size;
|
baddr = (baddr + 1) % size;
|
||||||
@@ -669,6 +723,7 @@ public class DataStreamProcessor {
|
|||||||
ea.bg = currentBg;
|
ea.bg = currentBg;
|
||||||
ea.gr = (byte) currentGr;
|
ea.gr = (byte) currentGr;
|
||||||
ea.ol = currentOl;
|
ea.ol = currentOl;
|
||||||
|
ea.tr = currentTr;
|
||||||
baddr = (baddr + 1) % size;
|
baddr = (baddr + 1) % size;
|
||||||
screen.setBufferAddress(baddr);
|
screen.setBufferAddress(baddr);
|
||||||
pos += 2;
|
pos += 2;
|
||||||
@@ -677,6 +732,98 @@ public class DataStreamProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
|
boolean isDbcsSession = (translator != null && translator.isDBCS());
|
||||||
|
if (isDbcsSession && b == 0x0E) { // Shift-Out (enter DBCS mode)
|
||||||
|
inDBCS = true;
|
||||||
|
ExtendedAttribute ea = screen.getCell(baddr);
|
||||||
|
ea.fa = 0;
|
||||||
|
ea.ec = (byte) 0x0E;
|
||||||
|
ea.ucs4 = ' ';
|
||||||
|
ea.cs = ExtendedAttribute.CS_BASE;
|
||||||
|
ea.db = ExtendedAttribute.DB_SO;
|
||||||
|
ea.fg = currentFg;
|
||||||
|
ea.bg = currentBg;
|
||||||
|
ea.gr = (byte) currentGr;
|
||||||
|
ea.ol = currentOl;
|
||||||
|
ea.tr = currentTr;
|
||||||
|
baddr = (baddr + 1) % size;
|
||||||
|
screen.setBufferAddress(baddr);
|
||||||
|
pos += 1;
|
||||||
|
lastWasOrder = false;
|
||||||
|
break;
|
||||||
|
} else if (isDbcsSession && b == 0x0F) { // Shift-In (exit DBCS mode)
|
||||||
|
inDBCS = false;
|
||||||
|
ExtendedAttribute ea = screen.getCell(baddr);
|
||||||
|
ea.fa = 0;
|
||||||
|
ea.ec = (byte) 0x0F;
|
||||||
|
ea.ucs4 = ' ';
|
||||||
|
ea.cs = ExtendedAttribute.CS_BASE;
|
||||||
|
ea.db = ExtendedAttribute.DB_SI;
|
||||||
|
ea.fg = currentFg;
|
||||||
|
ea.bg = currentBg;
|
||||||
|
ea.gr = (byte) currentGr;
|
||||||
|
ea.ol = currentOl;
|
||||||
|
ea.tr = currentTr;
|
||||||
|
baddr = (baddr + 1) % size;
|
||||||
|
screen.setBufferAddress(baddr);
|
||||||
|
pos += 1;
|
||||||
|
lastWasOrder = false;
|
||||||
|
break;
|
||||||
|
} else if (isDbcsSession && (inDBCS || currentCs == ExtendedAttribute.CS_DBCS)) {
|
||||||
|
if (pos + 1 < end) {
|
||||||
|
int b2 = data[pos + 1] & 0xFF;
|
||||||
|
if (b2 == 0x0F) { // Orphaned single byte before SI
|
||||||
|
ExtendedAttribute ea = screen.getCell(baddr);
|
||||||
|
ea.fa = 0;
|
||||||
|
ea.ec = (byte) b;
|
||||||
|
ea.ucs4 = translator.ebcdicToUnicode(b);
|
||||||
|
ea.fg = currentFg;
|
||||||
|
ea.bg = currentBg;
|
||||||
|
ea.gr = (byte) currentGr;
|
||||||
|
ea.cs = currentCs;
|
||||||
|
ea.ol = currentOl;
|
||||||
|
ea.tr = currentTr;
|
||||||
|
ea.db = ExtendedAttribute.DB_NONE;
|
||||||
|
baddr = (baddr + 1) % size;
|
||||||
|
screen.setBufferAddress(baddr);
|
||||||
|
pos += 1;
|
||||||
|
lastWasOrder = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
char uni = (translator.getCodePage() != null) ? translator.getCodePage().dbcsToUnicode(b, b2) : '?';
|
||||||
|
ExtendedAttribute ea1 = screen.getCell(baddr);
|
||||||
|
ea1.fa = 0;
|
||||||
|
ea1.ec = (byte) b;
|
||||||
|
ea1.ucs4 = uni;
|
||||||
|
ea1.fg = currentFg;
|
||||||
|
ea1.bg = currentBg;
|
||||||
|
ea1.gr = (byte) currentGr;
|
||||||
|
ea1.cs = ExtendedAttribute.CS_DBCS;
|
||||||
|
ea1.db = ExtendedAttribute.DB_LEFT;
|
||||||
|
ea1.ol = currentOl;
|
||||||
|
ea1.tr = currentTr;
|
||||||
|
|
||||||
|
int nextAddr = (baddr + 1) % size;
|
||||||
|
ExtendedAttribute ea2 = screen.getCell(nextAddr);
|
||||||
|
ea2.fa = 0;
|
||||||
|
ea2.ec = (byte) b2;
|
||||||
|
ea2.ucs4 = uni;
|
||||||
|
ea2.fg = currentFg;
|
||||||
|
ea2.bg = currentBg;
|
||||||
|
ea2.gr = (byte) currentGr;
|
||||||
|
ea2.cs = ExtendedAttribute.CS_DBCS;
|
||||||
|
ea2.db = ExtendedAttribute.DB_RIGHT;
|
||||||
|
ea2.ol = currentOl;
|
||||||
|
ea2.tr = currentTr;
|
||||||
|
|
||||||
|
baddr = (baddr + 2) % size;
|
||||||
|
screen.setBufferAddress(baddr);
|
||||||
|
pos += 2;
|
||||||
|
lastWasOrder = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Regular data byte (EBCDIC character)
|
// Regular data byte (EBCDIC character)
|
||||||
// Format control codes display as nulls/blanks per 3270 spec
|
// Format control codes display as nulls/blanks per 3270 spec
|
||||||
boolean isFCOrder = (b == FCORDER_NULL || b == FCORDER_FF ||
|
boolean isFCOrder = (b == FCORDER_NULL || b == FCORDER_FF ||
|
||||||
@@ -688,14 +835,18 @@ public class DataStreamProcessor {
|
|||||||
|
|
||||||
if (isFCOrder) {
|
if (isFCOrder) {
|
||||||
ea.ec = 0; // Display as null/blank
|
ea.ec = 0; // Display as null/blank
|
||||||
|
ea.ucs4 = 0;
|
||||||
} else {
|
} else {
|
||||||
ea.ec = (byte) b;
|
ea.ec = (byte) b;
|
||||||
|
ea.ucs4 = (translator != null) ? translator.ebcdicToUnicode(b) : (char) b;
|
||||||
}
|
}
|
||||||
ea.fg = currentFg;
|
ea.fg = currentFg;
|
||||||
ea.bg = currentBg;
|
ea.bg = currentBg;
|
||||||
ea.gr = (byte) currentGr;
|
ea.gr = (byte) currentGr;
|
||||||
ea.cs = currentCs;
|
ea.cs = currentCs;
|
||||||
|
ea.db = ExtendedAttribute.DB_NONE;
|
||||||
ea.ol = currentOl;
|
ea.ol = currentOl;
|
||||||
|
ea.tr = currentTr;
|
||||||
|
|
||||||
baddr = (baddr + 1) % size;
|
baddr = (baddr + 1) % size;
|
||||||
screen.setBufferAddress(baddr);
|
screen.setBufferAddress(baddr);
|
||||||
@@ -796,7 +947,7 @@ public class DataStreamProcessor {
|
|||||||
// Buffer contents depending on Reply Mode
|
// Buffer contents depending on Reply Mode
|
||||||
if (mode == SF_SRM_XFIELD || mode == SF_SRM_CHAR) {
|
if (mode == SF_SRM_XFIELD || mode == SF_SRM_CHAR) {
|
||||||
// Extended Field or Character Mode
|
// Extended Field or Character Mode
|
||||||
byte curFg = 0, curBg = 0, curGr = 0, curCs = 0;
|
byte curFg = 0, curBg = 0, curGr = 0, curCs = 0, curTr = 0;
|
||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
ExtendedAttribute ea = screen.getCell(i);
|
ExtendedAttribute ea = screen.getCell(i);
|
||||||
if (ea.isFieldAttribute()) {
|
if (ea.isFieldAttribute()) {
|
||||||
@@ -807,6 +958,7 @@ public class DataStreamProcessor {
|
|||||||
if (ea.gr != 0) count++;
|
if (ea.gr != 0) count++;
|
||||||
if (ea.cs != 0) count++;
|
if (ea.cs != 0) count++;
|
||||||
if (ea.ol != 0) count++;
|
if (ea.ol != 0) count++;
|
||||||
|
if (ea.tr != 0) count++;
|
||||||
|
|
||||||
outputWrite(ORDER_SFE);
|
outputWrite(ORDER_SFE);
|
||||||
outputWrite(count);
|
outputWrite(count);
|
||||||
@@ -825,6 +977,7 @@ public class DataStreamProcessor {
|
|||||||
}
|
}
|
||||||
if (ea.cs != 0) { outputWrite(XA_CHARSET); outputWrite(ea.cs & 0xFF); }
|
if (ea.cs != 0) { outputWrite(XA_CHARSET); outputWrite(ea.cs & 0xFF); }
|
||||||
if (ea.ol != 0) { outputWrite(XA_OUTLINING); outputWrite(ea.ol & 0xFF); }
|
if (ea.ol != 0) { outputWrite(XA_OUTLINING); outputWrite(ea.ol & 0xFF); }
|
||||||
|
if (ea.tr != 0) { outputWrite(XA_TRANSPARENCY); outputWrite(ea.tr & 0xFF); }
|
||||||
} else {
|
} else {
|
||||||
if (mode == SF_SRM_CHAR) {
|
if (mode == SF_SRM_CHAR) {
|
||||||
// In character mode, output SA if character attributes differ
|
// In character mode, output SA if character attributes differ
|
||||||
@@ -850,6 +1003,10 @@ public class DataStreamProcessor {
|
|||||||
outputWrite(ORDER_SA); outputWrite(XA_CHARSET); outputWrite(ea.cs & 0xFF);
|
outputWrite(ORDER_SA); outputWrite(XA_CHARSET); outputWrite(ea.cs & 0xFF);
|
||||||
curCs = ea.cs;
|
curCs = ea.cs;
|
||||||
}
|
}
|
||||||
|
if (ea.tr != curTr) {
|
||||||
|
outputWrite(ORDER_SA); outputWrite(XA_TRANSPARENCY); outputWrite(ea.tr & 0xFF);
|
||||||
|
curTr = ea.tr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
outputWrite(ea.ec & 0xFF);
|
outputWrite(ea.ec & 0xFF);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ public class QueryReplyBuilder {
|
|||||||
QR_DDM, // 0x95 - Distributed Data Management (file transfer)
|
QR_DDM, // 0x95 - Distributed Data Management (file transfer)
|
||||||
QR_AUXDA, // 0x99 - Auxiliary Devices
|
QR_AUXDA, // 0x99 - Auxiliary Devices
|
||||||
QR_IMP_PART, // 0xa6 - Implicit Partition Sizes
|
QR_IMP_PART, // 0xa6 - Implicit Partition Sizes
|
||||||
|
QR_TRANSPARENCY, // 0xa8 - Background Transparency
|
||||||
};
|
};
|
||||||
|
|
||||||
// Vector graphics query reply codes matching HOD QueryReply3270Constants.java QR_3270_WITHOUT_DCBS_SUMMARY_STRING
|
// Vector graphics query reply codes matching HOD QueryReply3270Constants.java QR_3270_WITHOUT_DCBS_SUMMARY_STRING
|
||||||
@@ -218,6 +219,9 @@ public class QueryReplyBuilder {
|
|||||||
// Implicit Partition (0xA6)
|
// Implicit Partition (0xA6)
|
||||||
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
||||||
|
|
||||||
|
// Background Transparency (0xA8)
|
||||||
|
appendQueryReply(out, QR_TRANSPARENCY, STATIC_QR_TRANSPARENCY);
|
||||||
|
|
||||||
log.info("Built " + out.size() + " bytes of base query replies (graphicsMode=" + graphicsMode + ")");
|
log.info("Built " + out.size() + " bytes of base query replies (graphicsMode=" + graphicsMode + ")");
|
||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
@@ -245,9 +249,9 @@ public class QueryReplyBuilder {
|
|||||||
appendQueryReply(out, QR_DDM, buildDdm(4096));
|
appendQueryReply(out, QR_DDM, buildDdm(4096));
|
||||||
appendQueryReply(out, QR_AUXDA, STATIC_QR_AUXDA);
|
appendQueryReply(out, QR_AUXDA, STATIC_QR_AUXDA);
|
||||||
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
||||||
|
appendQueryReply(out, QR_TRANSPARENCY, STATIC_QR_TRANSPARENCY); // 0xA8
|
||||||
|
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
if (graphicsMode.isVectorGraphicsEnabled()) {
|
||||||
appendQueryReply(out, QR_TRANSPARENCY, STATIC_QR_TRANSPARENCY); // 0xA8
|
|
||||||
appendQueryReply(out, QR_SEGMENT, STATIC_QR_SEGMENT); // 0xB0
|
appendQueryReply(out, QR_SEGMENT, STATIC_QR_SEGMENT); // 0xB0
|
||||||
appendQueryReply(out, QR_PROCEDURE, STATIC_QR_PROCEDURE); // 0xB1
|
appendQueryReply(out, QR_PROCEDURE, STATIC_QR_PROCEDURE); // 0xB1
|
||||||
appendQueryReply(out, QR_LINETYPE, STATIC_QR_LINETYPE); // 0xB2
|
appendQueryReply(out, QR_LINETYPE, STATIC_QR_LINETYPE); // 0xB2
|
||||||
@@ -317,11 +321,7 @@ public class QueryReplyBuilder {
|
|||||||
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
||||||
break;
|
break;
|
||||||
case QR_TRANSPARENCY: // 0xA8
|
case QR_TRANSPARENCY: // 0xA8
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
|
||||||
appendQueryReply(out, QR_TRANSPARENCY, STATIC_QR_TRANSPARENCY);
|
appendQueryReply(out, QR_TRANSPARENCY, STATIC_QR_TRANSPARENCY);
|
||||||
} else {
|
|
||||||
appendQueryReply(out, QR_NULL, new byte[0]);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case QR_SEGMENT: // 0xB0
|
case QR_SEGMENT: // 0xB0
|
||||||
if (graphicsMode.isVectorGraphicsEnabled()) {
|
if (graphicsMode.isVectorGraphicsEnabled()) {
|
||||||
@@ -390,10 +390,17 @@ public class QueryReplyBuilder {
|
|||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
int[] codes = graphicsMode.isVectorGraphicsEnabled() ? SUPPORTED_QR_VECTOR : SUPPORTED_QR_BASE;
|
int[] codes = graphicsMode.isVectorGraphicsEnabled() ? SUPPORTED_QR_VECTOR : SUPPORTED_QR_BASE;
|
||||||
boolean isDbcs = (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS());
|
boolean isDbcs = (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS());
|
||||||
|
boolean wroteDbcs = false;
|
||||||
for (int code : codes) {
|
for (int code : codes) {
|
||||||
|
if (isDbcs && !wroteDbcs && code == QR_DDM) {
|
||||||
|
out.write(QR_OUTLINING);
|
||||||
|
out.write(QR_DBCS_ASIA);
|
||||||
|
wroteDbcs = true;
|
||||||
|
}
|
||||||
out.write(code);
|
out.write(code);
|
||||||
if (isDbcs && code == QR_OUTLINING) {
|
if (isDbcs && code == QR_OUTLINING && !wroteDbcs) {
|
||||||
out.write(QR_DBCS_ASIA); // 0x91
|
out.write(QR_DBCS_ASIA); // 0x91
|
||||||
|
wroteDbcs = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
@@ -441,7 +448,7 @@ public class QueryReplyBuilder {
|
|||||||
|
|
||||||
public int getCharWidth(int rows) {
|
public int getCharWidth(int rows) {
|
||||||
if (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS()) {
|
if (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS()) {
|
||||||
return 12;
|
return 12; // 0x0C
|
||||||
}
|
}
|
||||||
return SW_3279_2; // 9
|
return SW_3279_2; // 9
|
||||||
}
|
}
|
||||||
@@ -452,6 +459,9 @@ public class QueryReplyBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getCharHeight(int rows) {
|
public int getCharHeight(int rows) {
|
||||||
|
if (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS()) {
|
||||||
|
return 24; // 0x18
|
||||||
|
}
|
||||||
return graphicsMode.isVectorGraphicsEnabled() ? 0x10 : SH_3279_2;
|
return graphicsMode.isVectorGraphicsEnabled() ? 0x10 : SH_3279_2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -477,9 +487,54 @@ public class QueryReplyBuilder {
|
|||||||
|
|
||||||
int cgcsgid = 0x02B9;
|
int cgcsgid = 0x02B9;
|
||||||
int cpgid = 0x0025;
|
int cpgid = 0x0025;
|
||||||
|
boolean isDbcs = false;
|
||||||
if (screen != null && screen.getTranslator() != null) {
|
if (screen != null && screen.getTranslator() != null) {
|
||||||
cgcsgid = screen.getTranslator().getCgcsgid();
|
cgcsgid = screen.getTranslator().getCgcsgid();
|
||||||
cpgid = screen.getTranslator().getCpgid();
|
cpgid = screen.getTranslator().getCpgid();
|
||||||
|
isDbcs = screen.getTranslator().isDBCS();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDbcs) {
|
||||||
|
int dbCgcsgid = screen.getTranslator().getDbCgcsgid();
|
||||||
|
int dbCpgid = screen.getTranslator().getDbCpgid();
|
||||||
|
byte secStart = screen.getTranslator().getDbcsSectionStart();
|
||||||
|
byte secEnd = screen.getTranslator().getDbcsSectionEnd();
|
||||||
|
|
||||||
|
// 42-byte DBCS Character Sets QR payload (total 46 bytes structured field, matching HoD DS3270.java xxxqrcs)
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream(42);
|
||||||
|
out.write(0x8E); // flags: GE | DBCS / 2-byte charsets (0x8E)
|
||||||
|
out.write(0x00); // flags 2
|
||||||
|
out.write(charW); // SDW (12)
|
||||||
|
out.write(charH); // SDH (24)
|
||||||
|
out.write(0x00); out.write(0x00); out.write(0x00); out.write(0x00); // 4 reserved bytes
|
||||||
|
out.write(0x0B); // DL = 11 bytes per descriptor
|
||||||
|
|
||||||
|
// Descriptor 1: Base SBCS (11 bytes)
|
||||||
|
out.write(0x00); out.write(0x00); out.write(0x00);
|
||||||
|
out.write(0x00); out.write(0x00);
|
||||||
|
out.write(0x00); out.write(0x00);
|
||||||
|
out.write((cgcsgid >> 8) & 0xFF); out.write(cgcsgid & 0xFF);
|
||||||
|
out.write((cpgid >> 8) & 0xFF); out.write(cpgid & 0xFF);
|
||||||
|
|
||||||
|
// Descriptor 2: DBCS Set 1 (11 bytes)
|
||||||
|
out.write(0x01); // Set ID = 1
|
||||||
|
out.write(0x00); // Flags
|
||||||
|
out.write(0x80); // Local ID = 0x80 (DBCS)
|
||||||
|
out.write((charW * 2) & 0xFF); // Double cell width (24)
|
||||||
|
out.write(charH & 0xFF); // Cell height (24)
|
||||||
|
out.write(secStart); // Section start (e.g. 0x41)
|
||||||
|
out.write(secEnd); // Section end (e.g. 0xFE or 0x7F)
|
||||||
|
out.write((dbCgcsgid >> 8) & 0xFF); out.write(dbCgcsgid & 0xFF);
|
||||||
|
out.write((dbCpgid >> 8) & 0xFF); out.write(dbCpgid & 0xFF);
|
||||||
|
|
||||||
|
// Descriptor 3: APL / Text (11 bytes)
|
||||||
|
out.write(0x01); out.write(0x00); out.write(0xF1);
|
||||||
|
out.write(0x00); out.write(0x00);
|
||||||
|
out.write(0x00); out.write(0x00);
|
||||||
|
out.write(0x03); out.write(0xC3); // CGCSGID 963
|
||||||
|
out.write(0x01); out.write(0x36); // CPGID 310
|
||||||
|
|
||||||
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (graphicsMode.isProgrammedSymbolsEnabled()) {
|
if (graphicsMode.isProgrammedSymbolsEnabled()) {
|
||||||
@@ -531,6 +586,7 @@ public class QueryReplyBuilder {
|
|||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public byte[] buildColor() {
|
public byte[] buildColor() {
|
||||||
return STATIC_QR_COLOR.clone();
|
return STATIC_QR_COLOR.clone();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,27 @@ package haus.nightmare.lib3270j.ecl;
|
|||||||
*/
|
*/
|
||||||
public interface ECLConstants {
|
public interface ECLConstants {
|
||||||
|
|
||||||
// Presentation space logical planes
|
// Presentation space logical planes (Bitmask & ordinal definitions matching IBM Host On-Demand)
|
||||||
int PLANE_TEXT = 1; // Unicode/EBCDIC characters
|
int PLANE_TEXT = 1; // Unicode/EBCDIC characters
|
||||||
int PLANE_COLOR = 2; // Color attributes
|
int PLANE_COLOR = 2; // Color attributes
|
||||||
int PLANE_HILITE = 3; // Extended highlighting
|
int PLANE_HILITE = 3; // Extended highlighting
|
||||||
int PLANE_EXTENDED = 4; // Extended character sets / outlining
|
int PLANE_EXTENDED = 4; // Extended character sets / outlining
|
||||||
int PLANE_FIELD = 5; // Field attribute definition bytes
|
int PLANE_FIELD = 5; // Field attribute definition bytes
|
||||||
int PLANE_DBCS = 6; // Double-byte character plane
|
int PLANE_DBCS = 6; // Double-byte character plane (lib3270j convention)
|
||||||
|
int PLANE_GRID = 8; // Grid lines / outlining plane
|
||||||
|
|
||||||
|
// Canonical IBM Host On-Demand ECLConstants plane bitmasks
|
||||||
|
int TEXT_PLANE = 1;
|
||||||
|
int COLOR_PLANE = 2;
|
||||||
|
int DBCS_PLANE = 4;
|
||||||
|
int GRID_PLANE = 8;
|
||||||
|
int FIELD_PLANE = 16;
|
||||||
|
int EXFIELD_PLANE = 32;
|
||||||
|
int NLS_PLANE = 64;
|
||||||
|
int HOST_PLANE = 128;
|
||||||
|
int RAWTEXT_PLANE = 256;
|
||||||
|
int COLOR_ATTRIBUTES_PLANE = 512;
|
||||||
|
int ALL_PLANES = 319;
|
||||||
|
|
||||||
// Color definitions (IBM 3279 standard 16-color palette)
|
// Color definitions (IBM 3279 standard 16-color palette)
|
||||||
char COLOR_NEUTRAL_BLACK = 0;
|
char COLOR_NEUTRAL_BLACK = 0;
|
||||||
|
|||||||
@@ -295,6 +295,54 @@ public class ECLField {
|
|||||||
}
|
}
|
||||||
public void Erase() { erase(); }
|
public void Erase() { erase(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether this field contains any DBCS characters or SO/SI delimiters.
|
||||||
|
*/
|
||||||
|
public boolean isDBCS() {
|
||||||
|
if (ps == null || length <= 0) return false;
|
||||||
|
ScreenBuffer sb = ps.getScreenBuffer();
|
||||||
|
if (sb == null) return false;
|
||||||
|
int size = sb.getRows() * sb.getCols();
|
||||||
|
if (size <= 0) return false;
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
int addr = (dataStart + i) % size;
|
||||||
|
ExtendedAttribute ea = sb.getCell(addr);
|
||||||
|
if (ea != null && (ea.db == ExtendedAttribute.DB_LEFT || ea.db == ExtendedAttribute.DB_RIGHT
|
||||||
|
|| ea.db == ExtendedAttribute.DB_SO || ea.db == ExtendedAttribute.DB_SI)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public boolean IsDBCS() { return isDBCS(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether this field contains only DBCS characters (excluding nulls / spaces).
|
||||||
|
*/
|
||||||
|
public boolean isDBCSOnly() {
|
||||||
|
if (ps == null || length <= 0) return false;
|
||||||
|
ScreenBuffer sb = ps.getScreenBuffer();
|
||||||
|
if (sb == null) return false;
|
||||||
|
int size = sb.getRows() * sb.getCols();
|
||||||
|
if (size <= 0) return false;
|
||||||
|
boolean hasDBCS = false;
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
int addr = (dataStart + i) % size;
|
||||||
|
ExtendedAttribute ea = sb.getCell(addr);
|
||||||
|
if (ea != null) {
|
||||||
|
if (ea.db == ExtendedAttribute.DB_LEFT || ea.db == ExtendedAttribute.DB_RIGHT
|
||||||
|
|| ea.db == ExtendedAttribute.DB_SO || ea.db == ExtendedAttribute.DB_SI) {
|
||||||
|
hasDBCS = true;
|
||||||
|
} else if (ea.ec != 0 && ea.ec != 0x40 && ea.ucs4 != 0 && ea.ucs4 != ' ') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hasDBCS;
|
||||||
|
}
|
||||||
|
public boolean IsDBCSOnly() { return isDBCSOnly(); }
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|||||||
@@ -25,9 +25,22 @@ public class ECLFieldList extends Vector<ECLField> {
|
|||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ECLFieldList(ECLPS ps) {
|
||||||
|
this(ps, ps != null ? ps.getScreenBuffer() : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLField getField(int index) {
|
||||||
|
return (index >= 0 && index < size()) ? get(index) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECLField GetField(int index) {
|
||||||
|
return getField(index);
|
||||||
|
}
|
||||||
|
|
||||||
public ECLPS getPS() { return ps; }
|
public ECLPS getPS() { return ps; }
|
||||||
public ScreenBuffer getScreenBuffer() { return screen; }
|
public ScreenBuffer getScreenBuffer() { return screen; }
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refresh the field list by scanning the presentation space buffer.
|
* Refresh the field list by scanning the presentation space buffer.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -264,10 +264,12 @@ public class ECLPS implements ECLConstants {
|
|||||||
case PLANE_TEXT:
|
case PLANE_TEXT:
|
||||||
if (ea.isFieldAttribute()) {
|
if (ea.isFieldAttribute()) {
|
||||||
destBuffer[i] = ' ';
|
destBuffer[i] = ' ';
|
||||||
|
} else if (ea.db == ExtendedAttribute.DB_SO || ea.db == ExtendedAttribute.DB_SI) {
|
||||||
|
destBuffer[i] = ' ';
|
||||||
} else if (ea.ucs4 != 0) {
|
} else if (ea.ucs4 != 0) {
|
||||||
destBuffer[i] = (char) ea.ucs4;
|
destBuffer[i] = (char) ea.ucs4;
|
||||||
} else if (ea.ec != 0) {
|
} else if (ea.ec != 0) {
|
||||||
destBuffer[i] = translator.ebcdicToUnicode(ea.ec & 0xFF);
|
destBuffer[i] = (translator != null) ? translator.ebcdicToUnicode(ea.ec & 0xFF) : (char) (ea.ec & 0xFF);
|
||||||
} else {
|
} else {
|
||||||
destBuffer[i] = ' ';
|
destBuffer[i] = ' ';
|
||||||
}
|
}
|
||||||
@@ -278,12 +280,43 @@ public class ECLPS implements ECLConstants {
|
|||||||
case PLANE_HILITE:
|
case PLANE_HILITE:
|
||||||
destBuffer[i] = (char) (ea.gr & 0xFF);
|
destBuffer[i] = (char) (ea.gr & 0xFF);
|
||||||
break;
|
break;
|
||||||
case PLANE_EXTENDED:
|
case DBCS_PLANE: // 4 (HoD DBCS_PLANE) or PLANE_EXTENDED for SBCS
|
||||||
|
if (translator != null && translator.isDBCS()) {
|
||||||
|
if (ea.db == ExtendedAttribute.DB_LEFT) {
|
||||||
|
destBuffer[i] = (char) 0xC0; // DB_DBHIGH (first half of DBCS)
|
||||||
|
} else if (ea.db == ExtendedAttribute.DB_RIGHT) {
|
||||||
|
destBuffer[i] = (char) 0x80; // DB_DBCS (second half of DBCS)
|
||||||
|
} else if (ea.db == ExtendedAttribute.DB_SO) {
|
||||||
|
destBuffer[i] = (char) 0x20; // DB_SO
|
||||||
|
} else if (ea.db == ExtendedAttribute.DB_SI) {
|
||||||
|
destBuffer[i] = (char) 0x10; // DB_SI
|
||||||
|
} else {
|
||||||
|
destBuffer[i] = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
destBuffer[i] = (char) (ea.cs & 0xFF);
|
destBuffer[i] = (char) (ea.cs & 0xFF);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case PLANE_FIELD:
|
case PLANE_FIELD:
|
||||||
|
case FIELD_PLANE: // 16
|
||||||
destBuffer[i] = ea.isFieldAttribute() ? (char) (ea.fa & 0xFF) : 0;
|
destBuffer[i] = ea.isFieldAttribute() ? (char) (ea.fa & 0xFF) : 0;
|
||||||
break;
|
break;
|
||||||
|
case PLANE_DBCS: // 6
|
||||||
|
if (ea.db == ExtendedAttribute.DB_LEFT) {
|
||||||
|
destBuffer[i] = (char) 0xC0;
|
||||||
|
} else if (ea.db == ExtendedAttribute.DB_RIGHT) {
|
||||||
|
destBuffer[i] = (char) 0x80;
|
||||||
|
} else if (ea.db == ExtendedAttribute.DB_SO) {
|
||||||
|
destBuffer[i] = (char) 0x20;
|
||||||
|
} else if (ea.db == ExtendedAttribute.DB_SI) {
|
||||||
|
destBuffer[i] = (char) 0x10;
|
||||||
|
} else {
|
||||||
|
destBuffer[i] = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GRID_PLANE: // 8 (HoD GRID_PLANE / Outlining)
|
||||||
|
destBuffer[i] = (char) (ea.ol & 0xFF);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
destBuffer[i] = ' ';
|
destBuffer[i] = ' ';
|
||||||
break;
|
break;
|
||||||
@@ -292,6 +325,107 @@ public class ECLPS implements ECLConstants {
|
|||||||
return copyLen;
|
return copyLen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized boolean IsDBCSChar(int pos) {
|
||||||
|
if (screen == null) return false;
|
||||||
|
int size = getSize();
|
||||||
|
if (size <= 0) return false;
|
||||||
|
pos = ((pos % size) + size) % size;
|
||||||
|
ExtendedAttribute ea = screen.getCell(pos);
|
||||||
|
return ea != null && (ea.db == ExtendedAttribute.DB_LEFT || ea.db == ExtendedAttribute.DB_RIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean isDBCSChar(int pos) {
|
||||||
|
return IsDBCSChar(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean IsDBCSChar(int row, int col) {
|
||||||
|
if (screen == null) return false;
|
||||||
|
return IsDBCSChar(screen.rowColToAddress(row, col));
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean isDBCSChar(int row, int col) {
|
||||||
|
return IsDBCSChar(row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean IsDBCS1stChar(int pos) {
|
||||||
|
if (screen == null) return false;
|
||||||
|
int size = getSize();
|
||||||
|
if (size <= 0) return false;
|
||||||
|
pos = ((pos % size) + size) % size;
|
||||||
|
ExtendedAttribute ea = screen.getCell(pos);
|
||||||
|
return ea != null && ea.db == ExtendedAttribute.DB_LEFT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean isDBCS1stChar(int pos) {
|
||||||
|
return IsDBCS1stChar(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean IsDBCS1stChar(int row, int col) {
|
||||||
|
if (screen == null) return false;
|
||||||
|
return IsDBCS1stChar(screen.rowColToAddress(row, col));
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean isDBCS1stChar(int row, int col) {
|
||||||
|
return IsDBCS1stChar(row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean IsDBCS2ndChar(int pos) {
|
||||||
|
if (screen == null) return false;
|
||||||
|
int size = getSize();
|
||||||
|
if (size <= 0) return false;
|
||||||
|
pos = ((pos % size) + size) % size;
|
||||||
|
ExtendedAttribute ea = screen.getCell(pos);
|
||||||
|
return ea != null && ea.db == ExtendedAttribute.DB_RIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean isDBCS2ndChar(int pos) {
|
||||||
|
return IsDBCS2ndChar(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean IsDBCS2ndChar(int row, int col) {
|
||||||
|
if (screen == null) return false;
|
||||||
|
return IsDBCS2ndChar(screen.rowColToAddress(row, col));
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean isDBCS2ndChar(int row, int col) {
|
||||||
|
return IsDBCS2ndChar(row, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean IsSOChar(int pos) {
|
||||||
|
if (screen == null) return false;
|
||||||
|
int size = getSize();
|
||||||
|
if (size <= 0) return false;
|
||||||
|
pos = ((pos % size) + size) % size;
|
||||||
|
ExtendedAttribute ea = screen.getCell(pos);
|
||||||
|
return ea != null && (ea.db == ExtendedAttribute.DB_SO || (ea.ec & 0xFF) == 0x0E);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean isSOChar(int pos) {
|
||||||
|
return IsSOChar(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean IsSIChar(int pos) {
|
||||||
|
if (screen == null) return false;
|
||||||
|
int size = getSize();
|
||||||
|
if (size <= 0) return false;
|
||||||
|
pos = ((pos % size) + size) % size;
|
||||||
|
ExtendedAttribute ea = screen.getCell(pos);
|
||||||
|
return ea != null && (ea.db == ExtendedAttribute.DB_SI || (ea.ec & 0xFF) == 0x0F);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean isSIChar(int pos) {
|
||||||
|
return IsSIChar(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean IsSOSIChar(int pos) {
|
||||||
|
return IsSOChar(pos) || IsSIChar(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized boolean isSOSIChar(int pos) {
|
||||||
|
return IsSOSIChar(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public synchronized int GetScreenRect(char[] cArray, int len, int sRow, int sCol, int eRow, int eCol, int plane) {
|
public synchronized int GetScreenRect(char[] cArray, int len, int sRow, int sCol, int eRow, int eCol, int plane) {
|
||||||
if (cArray == null || len <= 0 || screen == null) return 0;
|
if (cArray == null || len <= 0 || screen == null) return 0;
|
||||||
int minR = Math.max(1, Math.min(sRow, eRow));
|
int minR = Math.max(1, Math.min(sRow, eRow));
|
||||||
|
|||||||
@@ -816,15 +816,14 @@ public class FTDft {
|
|||||||
|
|
||||||
for (int i = 0; i < lineStr.length(); i++) {
|
for (int i = 0; i < lineStr.length(); i++) {
|
||||||
char ch = lineStr.charAt(i);
|
char ch = lineStr.charAt(i);
|
||||||
int ebc = translator != null ? translator.unicodeToEbcdic(ch) : -1;
|
int dbcsVal = (isDbcs && translator != null) ? translator.unicodeToDbcs(ch) : -1;
|
||||||
boolean charIsDbcs = isDbcs && (ch > 0xFF || ebc > 0xFF);
|
if (dbcsVal != -1) {
|
||||||
if (charIsDbcs) {
|
|
||||||
if (!inDbcsState && !noSo) {
|
if (!inDbcsState && !noSo) {
|
||||||
converted.write(0x0E); // Shift-Out
|
converted.write(0x0E); // Shift-Out
|
||||||
inDbcsState = true;
|
inDbcsState = true;
|
||||||
}
|
}
|
||||||
converted.write((ebc >> 8) & 0xFF);
|
converted.write((dbcsVal >> 8) & 0xFF);
|
||||||
converted.write(ebc & 0xFF);
|
converted.write(dbcsVal & 0xFF);
|
||||||
} else {
|
} else {
|
||||||
if (inDbcsState && !noSo) {
|
if (inDbcsState && !noSo) {
|
||||||
converted.write(0x0F); // Shift-In
|
converted.write(0x0F); // Shift-In
|
||||||
|
|||||||
@@ -292,9 +292,10 @@ public class InputProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translate character to EBCDIC (or APL Graphic Escape if in APL Keyboard Mode)
|
// Translate character to EBCDIC (or APL Graphic Escape if in APL Keyboard Mode, or DBCS)
|
||||||
boolean isAplChar = false;
|
boolean isAplChar = false;
|
||||||
int ebc = -1;
|
int ebc = -1;
|
||||||
|
int dbcs = -1;
|
||||||
if (aplKeyboardMode) {
|
if (aplKeyboardMode) {
|
||||||
int aplCode = getAplCodeForChar(ch);
|
int aplCode = getAplCodeForChar(ch);
|
||||||
if (aplCode >= 0) {
|
if (aplCode >= 0) {
|
||||||
@@ -303,10 +304,62 @@ public class InputProcessor {
|
|||||||
isAplChar = true;
|
isAplChar = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ebc < 0) {
|
if (ebc < 0 && translator != null && translator.isDBCS()) {
|
||||||
ebc = translator.unicodeToEbcdic(ch);
|
dbcs = translator.unicodeToDbcs(ch);
|
||||||
|
}
|
||||||
|
if (ebc < 0 && dbcs < 0) {
|
||||||
|
ebc = (translator != null) ? translator.unicodeToEbcdic(ch) : (int) ch;
|
||||||
|
}
|
||||||
|
if (ebc < 0 && dbcs < 0) return;
|
||||||
|
|
||||||
|
if (dbcs >= 0) {
|
||||||
|
// DBCS character typing
|
||||||
|
if (insertMode) {
|
||||||
|
if (!screen.insertChar(baddr, ch)) {
|
||||||
|
if (oia != null) {
|
||||||
|
oia.setInputInhibited(ECLConstants.INHIBIT_OVERFLOW);
|
||||||
|
}
|
||||||
|
setKeyboardLocked(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int nextPos = screen.incrementAddress(baddr);
|
||||||
|
if (screen.isFormatted() && screen.getCell(nextPos).isFieldAttribute()) {
|
||||||
|
// Cannot fit 2-cell DBCS at the last position of the field
|
||||||
|
if (oia != null) {
|
||||||
|
oia.setInputInhibited(ECLConstants.INHIBIT_OVERFLOW);
|
||||||
|
}
|
||||||
|
setKeyboardLocked(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int b1 = (dbcs >> 8) & 0xFF;
|
||||||
|
int b2 = dbcs & 0xFF;
|
||||||
|
ExtendedAttribute ea1 = screen.getCell(baddr);
|
||||||
|
ea1.fa = 0;
|
||||||
|
ea1.ec = (byte) b1;
|
||||||
|
ea1.ucs4 = ch;
|
||||||
|
ea1.cs = ExtendedAttribute.CS_DBCS;
|
||||||
|
ea1.db = ExtendedAttribute.DB_LEFT;
|
||||||
|
|
||||||
|
ExtendedAttribute ea2 = screen.getCell(nextPos);
|
||||||
|
ea2.fa = 0;
|
||||||
|
ea2.ec = (byte) b2;
|
||||||
|
ea2.ucs4 = ch;
|
||||||
|
ea2.cs = ExtendedAttribute.CS_DBCS;
|
||||||
|
ea2.db = ExtendedAttribute.DB_RIGHT;
|
||||||
|
|
||||||
|
if (screen.isFormatted()) {
|
||||||
|
int faAddr = screen.findFieldAttribute(baddr);
|
||||||
|
if (faAddr >= 0) {
|
||||||
|
screen.getCell(faAddr).fa = (byte) (screen.getCell(faAddr).fa | FA_MODIFY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
screen.setCursorAddress(screen.incrementAddress(nextPos));
|
||||||
|
}
|
||||||
|
screen.markAllChanged();
|
||||||
|
screen.updateDisplaySnapshot();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (ebc < 0) return;
|
|
||||||
|
|
||||||
if (insertMode) {
|
if (insertMode) {
|
||||||
// Insert mode: shift characters right from cursor to end of field
|
// Insert mode: shift characters right from cursor to end of field
|
||||||
@@ -1476,38 +1529,12 @@ public class InputProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void deleteChar() {
|
public void deleteChar() {
|
||||||
if (!screen.isFormatted()) return;
|
|
||||||
int addr = screen.getCursorAddress();
|
int addr = screen.getCursorAddress();
|
||||||
int size = screen.getRows() * screen.getCols();
|
if (screen.isFormatted()) {
|
||||||
if (size <= 0) return;
|
|
||||||
addr = ((addr % size) + size) % size;
|
|
||||||
|
|
||||||
// Cannot delete a field attribute
|
|
||||||
if (screen.getCell(addr).isFieldAttribute()) return;
|
|
||||||
|
|
||||||
byte faVal = screen.getFieldAttributeAt(addr);
|
byte faVal = screen.getFieldAttributeAt(addr);
|
||||||
if (faIsProtected(faVal & 0xFF)) return;
|
if (faIsProtected(faVal & 0xFF)) return;
|
||||||
|
|
||||||
// Shift characters left within the field
|
|
||||||
int shiftAddr = addr;
|
|
||||||
int count = 0;
|
|
||||||
while (count < size) {
|
|
||||||
int next = screen.incrementAddress(shiftAddr);
|
|
||||||
if (screen.getCell(next).isFieldAttribute()) {
|
|
||||||
screen.getCell(shiftAddr).ec = 0;
|
|
||||||
screen.getCell(shiftAddr).ucs4 = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
screen.getCell(shiftAddr).ec = screen.getCell(next).ec;
|
|
||||||
screen.getCell(shiftAddr).ucs4 = screen.getCell(next).ucs4;
|
|
||||||
shiftAddr = next;
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
int faAddr = screen.findFieldAttribute(addr);
|
|
||||||
if (faAddr >= 0) {
|
|
||||||
screen.getCell(faAddr).fa = (byte) (screen.getCell(faAddr).fa | FA_MODIFY);
|
|
||||||
}
|
}
|
||||||
|
screen.deleteChar(addr);
|
||||||
screen.markAllChanged();
|
screen.markAllChanged();
|
||||||
screen.updateDisplaySnapshot();
|
screen.updateDisplaySnapshot();
|
||||||
}
|
}
|
||||||
@@ -1525,6 +1552,9 @@ public class InputProcessor {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (screen.getCell(prev).db == ExtendedAttribute.DB_RIGHT) {
|
||||||
|
prev = screen.decrementAddress(prev);
|
||||||
|
}
|
||||||
screen.setCursorAddress(prev);
|
screen.setCursorAddress(prev);
|
||||||
deleteChar();
|
deleteChar();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,6 +97,11 @@ public class ExtendedAttribute {
|
|||||||
/** Validation attribute. */
|
/** Validation attribute. */
|
||||||
public byte vl;
|
public byte vl;
|
||||||
|
|
||||||
|
// Transparency attribute values (0x46 / XA_TRANSPARENCY)
|
||||||
|
public static final byte TR_DEFAULT = 0x00;
|
||||||
|
public static final byte TR_OR = (byte) 0xF0;
|
||||||
|
public static final byte TR_OPAQUE = (byte) 0xFF;
|
||||||
|
|
||||||
/** Transparency attribute. */
|
/** Transparency attribute. */
|
||||||
public byte tr;
|
public byte tr;
|
||||||
|
|
||||||
|
|||||||
@@ -727,6 +727,14 @@ public class ScreenBuffer {
|
|||||||
for (int i = 0; i < size; i++) {
|
for (int i = 0; i < size; i++) {
|
||||||
ExtendedAttribute ea = buffer[i];
|
ExtendedAttribute ea = buffer[i];
|
||||||
if (!ea.isFieldAttribute()) {
|
if (!ea.isFieldAttribute()) {
|
||||||
|
if (ea.db == ExtendedAttribute.DB_LEFT || ea.db == ExtendedAttribute.DB_RIGHT) {
|
||||||
|
// Preserve decoded DBCS character
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ea.db == ExtendedAttribute.DB_SO || ea.db == ExtendedAttribute.DB_SI) {
|
||||||
|
ea.ucs4 = ' ';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (ea.ec == 0) {
|
if (ea.ec == 0) {
|
||||||
ea.ucs4 = 0;
|
ea.ucs4 = 0;
|
||||||
} else if (ea.cs == 1 || ea.cs == 0x04) { // CS_APL or CS_GE
|
} else if (ea.cs == 1 || ea.cs == 0x04) { // CS_APL or CS_GE
|
||||||
@@ -736,6 +744,9 @@ public class ScreenBuffer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (translator != null && translator.isDBCS()) {
|
||||||
|
processSOSI();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private char getAplGraphic(int ec) {
|
private char getAplGraphic(int ec) {
|
||||||
@@ -1124,7 +1135,7 @@ public class ScreenBuffer {
|
|||||||
int count = 0;
|
int count = 0;
|
||||||
while (count < size) {
|
while (count < size) {
|
||||||
int next = incrementAddress(shiftAddr);
|
int next = incrementAddress(shiftAddr);
|
||||||
if (getCell(next).isFieldAttribute()) {
|
if (getCell(next).isFieldAttribute() || count == size - 1) {
|
||||||
getCell(shiftAddr).clear();
|
getCell(shiftAddr).clear();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
+204
@@ -0,0 +1,204 @@
|
|||||||
|
package haus.nightmare.lib3270j.datastream;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import haus.nightmare.lib3270j.TerminalModel;
|
||||||
|
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||||
|
import haus.nightmare.lib3270j.ecl.ECLConstants;
|
||||||
|
import haus.nightmare.lib3270j.ecl.ECLField;
|
||||||
|
import haus.nightmare.lib3270j.ecl.ECLFieldList;
|
||||||
|
import haus.nightmare.lib3270j.ecl.ECLPS;
|
||||||
|
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||||
|
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||||
|
|
||||||
|
public class DBCSDataStreamFullTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDBCSDataStreamShiftOutShiftIn() {
|
||||||
|
EbcdicTranslator translator = new EbcdicTranslator("930");
|
||||||
|
ScreenBuffer screen = new ScreenBuffer(TerminalModel.IBM_3279_4, translator);
|
||||||
|
DataStreamProcessor processor = new DataStreamProcessor(screen, translator);
|
||||||
|
|
||||||
|
// Data stream:
|
||||||
|
// EW, WCC, SBA (0, 0), SO (0x0E), 0x40 0x40 (\u3000 ideographic space), SI (0x0F)
|
||||||
|
byte[] stream = new byte[] {
|
||||||
|
(byte) CMD_EW,
|
||||||
|
(byte) 0xC3,
|
||||||
|
(byte) ORDER_SBA,
|
||||||
|
(byte) 0x40, (byte) 0x40,
|
||||||
|
(byte) 0x0E, // Shift-Out
|
||||||
|
(byte) 0x40, (byte) 0x40, // DBCS ideographic space
|
||||||
|
(byte) 0x0F // Shift-In
|
||||||
|
};
|
||||||
|
|
||||||
|
processor.processRecord(stream, 0, stream.length, true);
|
||||||
|
|
||||||
|
ExtendedAttribute cell0 = screen.getCell(0);
|
||||||
|
assertEquals(ExtendedAttribute.DB_SO, cell0.db, "Cell 0 must be DB_SO");
|
||||||
|
|
||||||
|
ExtendedAttribute cell1 = screen.getCell(1);
|
||||||
|
assertEquals(ExtendedAttribute.DB_LEFT, cell1.db, "Cell 1 must be DB_LEFT");
|
||||||
|
assertEquals('\u3000', cell1.ucs4, "Cell 1 ucs4 must be \\u3000");
|
||||||
|
|
||||||
|
ExtendedAttribute cell2 = screen.getCell(2);
|
||||||
|
assertEquals(ExtendedAttribute.DB_RIGHT, cell2.db, "Cell 2 must be DB_RIGHT");
|
||||||
|
assertEquals('\u3000', cell2.ucs4, "Cell 2 ucs4 must be \\u3000");
|
||||||
|
|
||||||
|
ExtendedAttribute cell3 = screen.getCell(3);
|
||||||
|
assertEquals(ExtendedAttribute.DB_SI, cell3.db, "Cell 3 must be DB_SI");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDBCSRepeatToAddressOrder() {
|
||||||
|
EbcdicTranslator translator = new EbcdicTranslator("930");
|
||||||
|
ScreenBuffer screen = new ScreenBuffer(TerminalModel.IBM_3279_4, translator);
|
||||||
|
DataStreamProcessor processor = new DataStreamProcessor(screen, translator);
|
||||||
|
|
||||||
|
// In DBCS mode (after SO), ORDER_RA consumes 2 data bytes and fills pairs
|
||||||
|
byte[] stream = new byte[] {
|
||||||
|
(byte) CMD_EW,
|
||||||
|
(byte) 0xC3,
|
||||||
|
(byte) ORDER_SBA,
|
||||||
|
(byte) 0x40, (byte) 0x40,
|
||||||
|
(byte) 0x0E, // SO at 0
|
||||||
|
(byte) ORDER_RA,
|
||||||
|
(byte) 0x40, (byte) 0x4B, // RA up to address 11 (covers cells 1..10)
|
||||||
|
(byte) 0x40, (byte) 0x40, // 2-byte repeat char (\u3000)
|
||||||
|
(byte) 0x0F // SI at 11
|
||||||
|
};
|
||||||
|
|
||||||
|
processor.processRecord(stream, 0, stream.length, true);
|
||||||
|
|
||||||
|
assertEquals(ExtendedAttribute.DB_SO, screen.getCell(0).db);
|
||||||
|
for (int i = 1; i < 11; i += 2) {
|
||||||
|
assertEquals(ExtendedAttribute.DB_LEFT, screen.getCell(i).db, "Cell " + i + " must be DB_LEFT");
|
||||||
|
assertEquals('\u3000', screen.getCell(i).ucs4);
|
||||||
|
assertEquals(ExtendedAttribute.DB_RIGHT, screen.getCell(i + 1).db, "Cell " + (i + 1) + " must be DB_RIGHT");
|
||||||
|
assertEquals('\u3000', screen.getCell(i + 1).ucs4);
|
||||||
|
}
|
||||||
|
assertEquals(ExtendedAttribute.DB_SI, screen.getCell(11).db);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testECLPlanesDBCSAndOutlining() {
|
||||||
|
EbcdicTranslator translator = new EbcdicTranslator("930");
|
||||||
|
ScreenBuffer screen = new ScreenBuffer(TerminalModel.IBM_3279_4, translator);
|
||||||
|
DataStreamProcessor processor = new DataStreamProcessor(screen, translator);
|
||||||
|
|
||||||
|
byte[] stream = new byte[] {
|
||||||
|
(byte) CMD_EW,
|
||||||
|
(byte) 0xC3,
|
||||||
|
(byte) ORDER_SBA,
|
||||||
|
(byte) 0x40, (byte) 0x40,
|
||||||
|
(byte) 0x0E, // SO at 0
|
||||||
|
(byte) 0x40, (byte) 0x40, // DBCS at 1, 2
|
||||||
|
(byte) 0x0F // SI at 3
|
||||||
|
};
|
||||||
|
processor.processRecord(stream, 0, stream.length, true);
|
||||||
|
|
||||||
|
// Add outlining to cell 1
|
||||||
|
screen.getCell(1).ol = (byte) (ExtendedAttribute.OL_BOX);
|
||||||
|
|
||||||
|
ECLPS eclPS = new ECLPS(screen, null, translator);
|
||||||
|
|
||||||
|
char[] dbcsPlane = new char[4];
|
||||||
|
int count = eclPS.getPlane(ECLConstants.DBCS_PLANE, dbcsPlane, 0, 4);
|
||||||
|
assertEquals(4, count);
|
||||||
|
assertEquals(0x20, dbcsPlane[0], "SO must be 0x20 in DBCS plane");
|
||||||
|
assertEquals(0xC0, dbcsPlane[1], "DB_LEFT must be 0xC0 (DB_DBHIGH)");
|
||||||
|
assertEquals(0x80, dbcsPlane[2], "DB_RIGHT must be 0x80 (DB_DBCS)");
|
||||||
|
assertEquals(0x10, dbcsPlane[3], "SI must be 0x10 in DBCS plane");
|
||||||
|
|
||||||
|
char[] gridPlane = new char[4];
|
||||||
|
eclPS.getPlane(ECLConstants.GRID_PLANE, gridPlane, 0, 4);
|
||||||
|
assertEquals((char) (ExtendedAttribute.OL_BOX & 0xFF), gridPlane[1]);
|
||||||
|
|
||||||
|
// Helper methods
|
||||||
|
assertTrue(eclPS.IsSOChar(0));
|
||||||
|
assertFalse(eclPS.IsDBCSChar(0));
|
||||||
|
|
||||||
|
assertTrue(eclPS.IsDBCSChar(1));
|
||||||
|
assertTrue(eclPS.IsDBCS1stChar(1));
|
||||||
|
assertFalse(eclPS.IsDBCS2ndChar(1));
|
||||||
|
|
||||||
|
assertTrue(eclPS.IsDBCSChar(2));
|
||||||
|
assertFalse(eclPS.IsDBCS1stChar(2));
|
||||||
|
assertTrue(eclPS.IsDBCS2ndChar(2));
|
||||||
|
|
||||||
|
assertTrue(eclPS.IsSIChar(3));
|
||||||
|
assertFalse(eclPS.IsDBCSChar(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testECLFieldDBCSMethods() {
|
||||||
|
EbcdicTranslator translator = new EbcdicTranslator("930");
|
||||||
|
ScreenBuffer screen = new ScreenBuffer(TerminalModel.IBM_3279_4, translator);
|
||||||
|
DataStreamProcessor processor = new DataStreamProcessor(screen, translator);
|
||||||
|
|
||||||
|
// Field with SO + DBCS + SI
|
||||||
|
byte[] stream = new byte[] {
|
||||||
|
(byte) CMD_EW,
|
||||||
|
(byte) 0xC3,
|
||||||
|
(byte) ORDER_SBA,
|
||||||
|
(byte) 0x40, (byte) 0x40,
|
||||||
|
(byte) ORDER_SF,
|
||||||
|
(byte) FA_PRINTABLE, // Field at 0
|
||||||
|
(byte) 0x0E, // SO at 1
|
||||||
|
(byte) 0x40, (byte) 0x40, // DBCS at 2, 3
|
||||||
|
(byte) 0x0F // SI at 4
|
||||||
|
};
|
||||||
|
processor.processRecord(stream, 0, stream.length, true);
|
||||||
|
|
||||||
|
ECLPS eclPS = new ECLPS(screen, null, translator);
|
||||||
|
ECLFieldList fieldList = new ECLFieldList(eclPS);
|
||||||
|
fieldList.Refresh();
|
||||||
|
assertEquals(1, fieldList.size());
|
||||||
|
|
||||||
|
ECLField field = fieldList.getField(0);
|
||||||
|
assertTrue(field.isDBCS(), "Field should report isDBCS = true");
|
||||||
|
assertTrue(field.isDBCSOnly(), "Field should report isDBCSOnly = true");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testQueryReplyBuilderDBCSCharsets() {
|
||||||
|
EbcdicTranslator translator = new EbcdicTranslator("930");
|
||||||
|
ScreenBuffer screen = new ScreenBuffer(TerminalModel.IBM_3279_4, translator);
|
||||||
|
QueryReplyBuilder builder = new QueryReplyBuilder(screen);
|
||||||
|
|
||||||
|
byte[] charsets = builder.buildCharsets();
|
||||||
|
assertNotNull(charsets);
|
||||||
|
assertEquals(42, charsets.length, "DBCS Character Sets QR payload must be 42 bytes (matching HoD xxxqrcs)");
|
||||||
|
|
||||||
|
assertEquals((byte) 0x8E, charsets[0], "Flags 1 must be 0x8E");
|
||||||
|
assertEquals((byte) 0x00, charsets[1], "Flags 2 must be 0x00");
|
||||||
|
assertEquals(12, charsets[2], "SDW must be 12");
|
||||||
|
assertEquals(24, charsets[3], "SDH must be 24");
|
||||||
|
assertEquals(0x0B, charsets[8], "Descriptor length DL must be 11 (0x0B)");
|
||||||
|
|
||||||
|
// Descriptor 2 (DBCS): offset 9 + 11 = 20
|
||||||
|
assertEquals(0x01, charsets[20], "Set ID must be 1");
|
||||||
|
assertEquals((byte) 0x80, charsets[22], "Local ID must be 0x80 (DBCS)");
|
||||||
|
assertEquals(24, charsets[23], "DBCS cell width must be double (24)");
|
||||||
|
assertEquals(24, charsets[24], "DBCS cell height must be 24");
|
||||||
|
assertEquals(0x41, charsets[25], "Section start must be 0x41");
|
||||||
|
assertEquals(0x7F, charsets[26], "Section end for 930 must be 0x7F");
|
||||||
|
|
||||||
|
// DB CGCSGID = 370 (0x0172), DB CPGID = 300 (0x012C)
|
||||||
|
int cgcsgid = ((charsets[27] & 0xFF) << 8) | (charsets[28] & 0xFF);
|
||||||
|
int cpgid = ((charsets[29] & 0xFF) << 8) | (charsets[30] & 0xFF);
|
||||||
|
assertEquals(370, cgcsgid);
|
||||||
|
assertEquals(300, cpgid);
|
||||||
|
|
||||||
|
// Summary Query Reply contains Outlining (0x8C) and DBCS Asia (0x91)
|
||||||
|
byte[] summary = builder.buildSummary();
|
||||||
|
boolean hasOutlining = false;
|
||||||
|
boolean hasDbcsAsia = false;
|
||||||
|
for (byte b : summary) {
|
||||||
|
if ((b & 0xFF) == QR_OUTLINING) hasOutlining = true;
|
||||||
|
if ((b & 0xFF) == QR_DBCS_ASIA) hasDbcsAsia = true;
|
||||||
|
}
|
||||||
|
assertTrue(hasOutlining, "Summary must include QR_OUTLINING");
|
||||||
|
assertTrue(hasDbcsAsia, "Summary must include QR_DBCS_ASIA");
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
-1
@@ -49,9 +49,24 @@ public class QueryReplyBuilderTest {
|
|||||||
|
|
||||||
// In GraphicsMode.NONE, Vector Graphics QR 0xB0 must NOT be advertised in Summary
|
// In GraphicsMode.NONE, Vector Graphics QR 0xB0 must NOT be advertised in Summary
|
||||||
int sumLen = ((replies[1] & 0xFF) << 8) | (replies[2] & 0xFF);
|
int sumLen = ((replies[1] & 0xFF) << 8) | (replies[2] & 0xFF);
|
||||||
|
boolean hasB0 = false;
|
||||||
|
boolean hasA8 = false;
|
||||||
for (int i = 5; i < 1 + sumLen; i++) {
|
for (int i = 5; i < 1 + sumLen; i++) {
|
||||||
assertNotEquals(QR_SEGMENT, replies[i] & 0xFF);
|
if ((replies[i] & 0xFF) == QR_SEGMENT) hasB0 = true;
|
||||||
|
if ((replies[i] & 0xFF) == QR_TRANSPARENCY) hasA8 = true;
|
||||||
}
|
}
|
||||||
|
assertFalse(hasB0);
|
||||||
|
assertTrue(hasA8, "QR_TRANSPARENCY (0xA8) should be advertised even in GraphicsMode.NONE");
|
||||||
|
|
||||||
|
// And QR_TRANSPARENCY structured field payload must be present in replies
|
||||||
|
boolean hasA8Payload = false;
|
||||||
|
for (int i = 0; i < replies.length - 3; i++) {
|
||||||
|
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_TRANSPARENCY) {
|
||||||
|
hasA8Payload = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(hasA8Payload, "QR_TRANSPARENCY structured field should be included in buildAllQueryReplies");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -87,10 +87,10 @@ public class QueryReplyPhase2Test {
|
|||||||
assertNotNull(reply);
|
assertNotNull(reply);
|
||||||
|
|
||||||
List<ParsedSF> sfs = parseQueryReplies(reply);
|
List<ParsedSF> sfs = parseQueryReplies(reply);
|
||||||
// Base query reply must contain exactly 10 structured fields for SBCS:
|
// Base query reply must contain exactly 11 structured fields for SBCS:
|
||||||
// 0x80 (Summary), 0x81 (Usable Area), 0x84 (Alpha Partitions), 0x85 (Character Sets),
|
// 0x80 (Summary), 0x81 (Usable Area), 0x84 (Alpha Partitions), 0x85 (Character Sets),
|
||||||
// 0x86 (Color), 0x87 (Highlighting), 0x88 (Reply Modes), 0x95 (DDM), 0x99 (AuxDA), 0xA6 (Implicit Part)
|
// 0x86 (Color), 0x87 (Highlighting), 0x88 (Reply Modes), 0x95 (DDM), 0x99 (AuxDA), 0xA6 (Implicit Part), 0xA8 (Transparency)
|
||||||
assertEquals(10, sfs.size());
|
assertEquals(11, sfs.size());
|
||||||
|
|
||||||
assertEquals(QR_SUMMARY, sfs.get(0).qcode);
|
assertEquals(QR_SUMMARY, sfs.get(0).qcode);
|
||||||
assertEquals(QR_USABLE_AREA, sfs.get(1).qcode);
|
assertEquals(QR_USABLE_AREA, sfs.get(1).qcode);
|
||||||
@@ -102,10 +102,10 @@ public class QueryReplyPhase2Test {
|
|||||||
assertEquals(QR_DDM, sfs.get(7).qcode);
|
assertEquals(QR_DDM, sfs.get(7).qcode);
|
||||||
assertEquals(QR_AUXDA, sfs.get(8).qcode);
|
assertEquals(QR_AUXDA, sfs.get(8).qcode);
|
||||||
assertEquals(QR_IMP_PART, sfs.get(9).qcode);
|
assertEquals(QR_IMP_PART, sfs.get(9).qcode);
|
||||||
|
assertEquals(QR_TRANSPARENCY, sfs.get(10).qcode);
|
||||||
|
|
||||||
// Verify that Vector Graphics SF payloads (0xA8, 0xB0..0xB6) are NOT sent in base reply
|
// Verify that Vector Graphics SF payloads (0xB0..0xB6) are NOT sent in base reply
|
||||||
for (ParsedSF sf : sfs) {
|
for (ParsedSF sf : sfs) {
|
||||||
assertNotEquals(QR_TRANSPARENCY, sf.qcode);
|
|
||||||
assertNotEquals(QR_SEGMENT, sf.qcode);
|
assertNotEquals(QR_SEGMENT, sf.qcode);
|
||||||
assertNotEquals(QR_PROCEDURE, sf.qcode);
|
assertNotEquals(QR_PROCEDURE, sf.qcode);
|
||||||
assertNotEquals(QR_LINETYPE, sf.qcode);
|
assertNotEquals(QR_LINETYPE, sf.qcode);
|
||||||
@@ -246,7 +246,7 @@ public class QueryReplyPhase2Test {
|
|||||||
assertTrue(sent.length > 0);
|
assertTrue(sent.length > 0);
|
||||||
assertEquals((byte) AID_SF, sent[0]);
|
assertEquals((byte) AID_SF, sent[0]);
|
||||||
List<ParsedSF> sfs = parseQueryReplies(sent);
|
List<ParsedSF> sfs = parseQueryReplies(sent);
|
||||||
assertEquals(10, sfs.size());
|
assertEquals(11, sfs.size());
|
||||||
|
|
||||||
// Test SF_RP_QLIST (0x03) with SF_RPQ_LIST (0x00) and codes 0xB0, 0xB4
|
// Test SF_RP_QLIST (0x03) with SF_RPQ_LIST (0x00) and codes 0xB0, 0xB4
|
||||||
byte[] queryList = new byte[] {
|
byte[] queryList = new byte[] {
|
||||||
|
|||||||
+143
@@ -0,0 +1,143 @@
|
|||||||
|
package haus.nightmare.lib3270j.datastream;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.TerminalModel;
|
||||||
|
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||||
|
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
|
||||||
|
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
|
||||||
|
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class TransparencyDataStreamTest {
|
||||||
|
|
||||||
|
private ScreenBuffer screen;
|
||||||
|
private DataStreamProcessor processor;
|
||||||
|
private ByteArrayOutputStream output;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() {
|
||||||
|
EbcdicTranslator translator = new EbcdicTranslator();
|
||||||
|
screen = new ScreenBuffer(TerminalModel.IBM_3279_2, translator);
|
||||||
|
processor = new DataStreamProcessor(screen, translator);
|
||||||
|
output = new ByteArrayOutputStream();
|
||||||
|
processor.setOutputSender(bytes -> output.write(bytes, 0, bytes.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSetAttributeTransparency() {
|
||||||
|
// Send SFE with XA_TRANSPARENCY = XAT_OPAQUE (0xFF)
|
||||||
|
byte[] sfeRecord = new byte[] {
|
||||||
|
(byte) CMD_EW, 0x00,
|
||||||
|
(byte) ORDER_SFE, 0x02,
|
||||||
|
(byte) XA_3270, (byte) FA_PRINTABLE,
|
||||||
|
(byte) XA_TRANSPARENCY, (byte) ExtendedAttribute.TR_OPAQUE
|
||||||
|
};
|
||||||
|
processor.processRecord(sfeRecord, 0, sfeRecord.length, true);
|
||||||
|
|
||||||
|
ExtendedAttribute cell0 = screen.getCell(0);
|
||||||
|
assertTrue(cell0.isFieldAttribute());
|
||||||
|
assertEquals(ExtendedAttribute.TR_OPAQUE, cell0.tr);
|
||||||
|
|
||||||
|
// Send SA with XA_TRANSPARENCY = XAT_OPAQUE followed by character 'A' (0xC1)
|
||||||
|
byte[] saRecord = new byte[] {
|
||||||
|
(byte) CMD_W, 0x00,
|
||||||
|
(byte) ORDER_SBA, 0x00, 0x01,
|
||||||
|
(byte) ORDER_SA, (byte) XA_TRANSPARENCY, (byte) ExtendedAttribute.TR_OPAQUE,
|
||||||
|
(byte) 0xC1 // 'A'
|
||||||
|
};
|
||||||
|
processor.processRecord(saRecord, 0, saRecord.length, true);
|
||||||
|
|
||||||
|
ExtendedAttribute charCell1 = screen.getCell(1);
|
||||||
|
assertFalse(charCell1.isFieldAttribute());
|
||||||
|
assertEquals((byte) 0xC1, charCell1.ec);
|
||||||
|
assertEquals(ExtendedAttribute.TR_OPAQUE, charCell1.tr);
|
||||||
|
|
||||||
|
// Send SA with XA_ALL (0x00) followed by character 'B' (0xC2) -> should reset tr to 0
|
||||||
|
byte[] resetRecord = new byte[] {
|
||||||
|
(byte) CMD_W, 0x00,
|
||||||
|
(byte) ORDER_SBA, 0x00, 0x02,
|
||||||
|
(byte) ORDER_SA, (byte) XA_ALL, 0x00,
|
||||||
|
(byte) 0xC2 // 'B'
|
||||||
|
};
|
||||||
|
processor.processRecord(resetRecord, 0, resetRecord.length, true);
|
||||||
|
|
||||||
|
ExtendedAttribute charCell2 = screen.getCell(2);
|
||||||
|
assertFalse(charCell2.isFieldAttribute());
|
||||||
|
assertEquals((byte) 0xC2, charCell2.ec);
|
||||||
|
assertEquals(ExtendedAttribute.TR_DEFAULT, charCell2.tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRepeatToAddressWithTransparency() {
|
||||||
|
// Set SA Transparency = Opaque, then RA to address 5 with char 'C' (0xC3)
|
||||||
|
byte[] raRecord = new byte[] {
|
||||||
|
(byte) CMD_EW, 0x00,
|
||||||
|
(byte) ORDER_SBA, 0x00, 0x00,
|
||||||
|
(byte) ORDER_SA, (byte) XA_TRANSPARENCY, (byte) ExtendedAttribute.TR_OPAQUE,
|
||||||
|
(byte) ORDER_RA, 0x00, 0x05, (byte) 0xC3
|
||||||
|
};
|
||||||
|
processor.processRecord(raRecord, 0, raRecord.length, true);
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; i++) {
|
||||||
|
ExtendedAttribute cell = screen.getCell(i);
|
||||||
|
assertEquals((byte) 0xC3, cell.ec);
|
||||||
|
assertEquals(ExtendedAttribute.TR_OPAQUE, cell.tr, "Cell at " + i + " should have TR_OPAQUE");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadBufferExtendedFieldIncludesTransparency() {
|
||||||
|
screen.setReplyMode((byte) SF_SRM_XFIELD);
|
||||||
|
screen.setFieldAttribute(0, (byte) FA_PRINTABLE);
|
||||||
|
screen.getCell(0).tr = ExtendedAttribute.TR_OPAQUE;
|
||||||
|
|
||||||
|
processor.processReadBuffer();
|
||||||
|
|
||||||
|
byte[] result = output.toByteArray();
|
||||||
|
assertTrue(result.length > 3);
|
||||||
|
assertEquals((byte) AID_NO, result[0]);
|
||||||
|
|
||||||
|
// Find ORDER_SFE
|
||||||
|
boolean foundTr = false;
|
||||||
|
for (int i = 3; i < result.length - 2; i++) {
|
||||||
|
if ((result[i] & 0xFF) == ORDER_SFE) {
|
||||||
|
int pairCount = result[i + 1] & 0xFF;
|
||||||
|
int pos = i + 2;
|
||||||
|
for (int p = 0; p < pairCount; p++) {
|
||||||
|
int attrType = result[pos] & 0xFF;
|
||||||
|
int attrVal = result[pos + 1] & 0xFF;
|
||||||
|
if (attrType == XA_TRANSPARENCY && attrVal == (ExtendedAttribute.TR_OPAQUE & 0xFF)) {
|
||||||
|
foundTr = true;
|
||||||
|
}
|
||||||
|
pos += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(foundTr, "Read Buffer in SF_SRM_XFIELD mode should report XA_TRANSPARENCY");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadBufferCharacterModeIncludesTransparency() {
|
||||||
|
screen.setReplyMode((byte) SF_SRM_CHAR);
|
||||||
|
screen.getCell(0).ec = (byte) 0xC1;
|
||||||
|
screen.getCell(0).tr = ExtendedAttribute.TR_OPAQUE;
|
||||||
|
|
||||||
|
processor.processReadBuffer();
|
||||||
|
|
||||||
|
byte[] result = output.toByteArray();
|
||||||
|
assertTrue(result.length > 3);
|
||||||
|
|
||||||
|
boolean foundSaTr = false;
|
||||||
|
for (int i = 3; i < result.length - 2; i++) {
|
||||||
|
if ((result[i] & 0xFF) == ORDER_SA && (result[i + 1] & 0xFF) == XA_TRANSPARENCY && (result[i + 2] & 0xFF) == (ExtendedAttribute.TR_OPAQUE & 0xFF)) {
|
||||||
|
foundSaTr = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertTrue(foundSaTr, "Read Buffer in SF_SRM_CHAR mode should output ORDER_SA for XA_TRANSPARENCY");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user