Codepage, ECL, and some UI updates
This commit is contained in:
+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
|
||||
int sumLen = ((replies[1] & 0xFF) << 8) | (replies[2] & 0xFF);
|
||||
boolean hasB0 = false;
|
||||
boolean hasA8 = false;
|
||||
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
|
||||
|
||||
@@ -87,10 +87,10 @@ public class QueryReplyPhase2Test {
|
||||
assertNotNull(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),
|
||||
// 0x86 (Color), 0x87 (Highlighting), 0x88 (Reply Modes), 0x95 (DDM), 0x99 (AuxDA), 0xA6 (Implicit Part)
|
||||
assertEquals(10, sfs.size());
|
||||
// 0x86 (Color), 0x87 (Highlighting), 0x88 (Reply Modes), 0x95 (DDM), 0x99 (AuxDA), 0xA6 (Implicit Part), 0xA8 (Transparency)
|
||||
assertEquals(11, sfs.size());
|
||||
|
||||
assertEquals(QR_SUMMARY, sfs.get(0).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_AUXDA, sfs.get(8).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) {
|
||||
assertNotEquals(QR_TRANSPARENCY, sf.qcode);
|
||||
assertNotEquals(QR_SEGMENT, sf.qcode);
|
||||
assertNotEquals(QR_PROCEDURE, sf.qcode);
|
||||
assertNotEquals(QR_LINETYPE, sf.qcode);
|
||||
@@ -246,7 +246,7 @@ public class QueryReplyPhase2Test {
|
||||
assertTrue(sent.length > 0);
|
||||
assertEquals((byte) AID_SF, sent[0]);
|
||||
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
|
||||
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