Add missing menus back

This commit is contained in:
2026-08-30 23:46:29 +00:00
parent a3c4b95379
commit 27976dd31f
6 changed files with 62 additions and 56 deletions
@@ -48,7 +48,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
public J3270App() {
super("j3270 — Java TN3270 Terminal Emulator");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBackground(new Color(10, 10, 10));
setBackground(Color.BLACK);
buildUI();
buildMenuBar();
@@ -92,7 +92,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
statusBar = new StatusBar();
getContentPane().setLayout(new BorderLayout());
getContentPane().setBackground(new Color(10, 10, 10));
getContentPane().setBackground(Color.BLACK);
getContentPane().add(terminalPanel, BorderLayout.CENTER);
getContentPane().add(statusBar, BorderLayout.SOUTH);
}
@@ -123,7 +123,7 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
public static final Color DEFAULT_MONO_PROTECTED_HIGH = new Color(255, 255, 255);
// Default Background
public static final Color DEFAULT_BG_COLOR = new Color(10, 10, 10);
public static final Color DEFAULT_BG_COLOR = Color.BLACK;
public TerminalPanel() {
setupColors();
@@ -18,9 +18,9 @@ public class QueryReplyBuilder {
private static final int SW_3279_2 = 0x09;
private static final int SH_3279_2 = 0x0c;
// Usable Area physical dimensions matching IBM Host On-Demand QR_USEAREA_STRING (Inches)
private static final int Xr_HOD = 0x00020089;
private static final int Yr_HOD = 0x00020085;
// Usable Area physical dimensions matching IBM Host On-Demand DS3270.java (Inches, 96 dpi: 0x00010060)
private static final int Xr_HOD = 0x00010060;
private static final int Yr_HOD = 0x00010060;
private final ScreenBuffer screen;
private GraphicsMode graphicsMode = GraphicsMode.BOTH;
@@ -309,7 +309,7 @@ public class QueryReplyBuilder {
private byte[] buildUsableArea(int maxCols, int maxRows, int bufferSize) {
ByteArrayOutputStream out = new ByteArrayOutputStream(19);
out.write(0x01); // 12/14-bit addressing
out.write(graphicsMode.isVectorGraphicsEnabled() ? 0x03 : 0x01); // 12/14-bit addressing + graphics flag (matching HOD DS3270.java)
out.write(0x00); // no special character features
out.write((maxCols >> 8) & 0xFF); // usable width high
out.write(maxCols & 0xFF); // usable width low
@@ -380,8 +380,8 @@ public class QueryReplyBuilder {
cpgid = screen.getTranslator().getCpgid();
}
if (graphicsMode.isProgrammedSymbolsEnabled()) {
// Programmed Symbols mode (3279 PS with LoadPS 0x0A, flags1 = 0xA2 for GE + PS + CGCSGID)
if (graphicsMode == GraphicsMode.PROGRAMMED_SYMBOLS) {
// Programmed Symbols only mode (3279 PS with LoadPS 0x0A, flags1 = 0xA2 for GE + PS + CGCSGID)
ByteArrayOutputStream out = new ByteArrayOutputStream(65);
out.write(0xa2); // flags: GE (0x80), PS/Loadable Charsets (0x20), CGCSGID present (0x02)
out.write(0x00); // more flags
@@ -615,11 +615,10 @@ public class QueryReplyBuilder {
cgcsgid = screen.getTranslator().getCgcsgid();
cpgid = screen.getTranslator().getCpgid();
}
// HOD QueryReply3270Constants.java QR_GRSYMBOLSET_S_STRING ("\u0000!\u0081\u00b6...")
return new byte[]{
0x00, 0x00, (byte) charW, (byte) charH, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x12,
0x01, 0x00, 0x00, (byte) 0xF0, (byte) ((cgcsgid >> 8) & 0xFF), (byte) (cgcsgid & 0xFF), 0x00, 0x00,
(byte) charW, (byte) charH, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
(byte) charW, (byte) charH, 0x00, 0x00, (byte) 0xF0, (byte) ((cgcsgid >> 8) & 0xFF), (byte) (cgcsgid & 0xFF), 0x00, 0x00
};
}
@@ -60,10 +60,9 @@ public final class GocaConstants {
public static final int G_GSCR = 0x35; // Set Character Shear
public static final int G_GSMCEL = 0x37; // Set Marker Cell
public static final int G_GSCS = 0x38; // Set Character Set
public static final int G_GSMP = 0x39; // Set Marker Precision
public static final int G_GSETAG = 0x39; // Set Pick Identifier / Tag
public static final int G_GSCC = 0x39; // Set Character Precision
public static final int G_GSCD = 0x3A; // Set Character Direction
public static final int G_GSCC = 0x3B; // Set Character Precision
public static final int G_GSMP = 0x3B; // Set Marker Precision
public static final int G_GSMS_SET = 0x3C; // Set Marker Set
public static final int G_ENDPROLOGUE = 0x3E; // End Prologue
public static final int G_GPOP = 0x3F; // Pop Attribute
@@ -27,6 +27,7 @@ public class GocaDecoder {
private int markerType = GocaConstants.MK_PLUS;
private int markerSize = 5;
private int markerColor = GocaConstants.GOCA_COLORS[0];
private int markerPrecision = 0;
private int pattern = GocaConstants.PT_SOLID;
private int patternSet = 0;
private int fillColor = GocaConstants.GOCA_COLORS[0];
@@ -225,6 +226,7 @@ public class GocaDecoder {
markerType = GocaConstants.MK_PLUS;
markerSize = 5;
markerColor = curColor;
markerPrecision = 0;
pattern = GocaConstants.PT_SOLID;
patternSet = 0;
fillColor = curColor;
@@ -281,7 +283,7 @@ public class GocaDecoder {
// Flexible 1-byte attribute orders (support both short 2-byte or long 3-byte if len byte == 1)
if (order == GocaConstants.G_GSPT || order == GocaConstants.G_GSMT ||
order == GocaConstants.G_GSCS || order == GocaConstants.G_GSCD ||
order == GocaConstants.G_GSCC ||
order == GocaConstants.G_GSCC || order == GocaConstants.G_GSMP ||
order == GocaConstants.G_GSMS_SET || order == GocaConstants.G_GBAR) {
return (data[idx + 1] == 0x01 && idx + 2 < end) ? 3 : 2;
}
@@ -536,17 +538,7 @@ public class GocaDecoder {
}
break;
}
case GocaConstants.G_GSETAG: { // Set Pick Identifier / Tag (0x39)
if (currentSegId != 0 && payloadLen >= 2 && idx + 3 < end) {
int tag = ((inputData[idx + 2] & 0xFF) << 8) | (inputData[idx + 3] & 0xFF);
SegmentBounds sb = segmentBoundsMap.get(currentSegId);
if (sb != null) {
sb.tag = tag;
}
}
idx += orderLen;
break;
}
case GocaConstants.G_ENDPROLOGUE: { // End Prologue (0x3E)
idx += orderLen;
break;
@@ -706,16 +698,22 @@ public class GocaDecoder {
break;
}
case GocaConstants.G_GSCS: { // Set Character Set (0x38)
charSet = (orderLen == 3) ? (inputData[idx + 2] & 0xFF) : (inputData[idx + 1] & 0xFF);
int cs = (orderLen == 3) ? (inputData[idx + 2] & 0xFF) : (inputData[idx + 1] & 0xFF);
charSet = (cs == 0xF0) ? 0 : cs;
idx += orderLen;
break;
}
case GocaConstants.G_GSCC: { // Set Character Precision (0x3B)
case GocaConstants.G_GSCC: { // Set Character Precision (0x39)
charPrecision = (orderLen == 3) ? (inputData[idx + 2] & 0xFF) : (inputData[idx + 1] & 0xFF);
if (charPrecision == 0) charPrecision = GocaConstants.CP_STRING;
idx += orderLen;
break;
}
case GocaConstants.G_GSMP: { // Set Marker Precision (0x3B)
markerPrecision = (orderLen == 3) ? (inputData[idx + 2] & 0xFF) : (inputData[idx + 1] & 0xFF);
idx += orderLen;
break;
}
case GocaConstants.G_GSMX:
case GocaConstants.G_GSMS_SET:
case GocaConstants.G_GPOP: {
@@ -1393,7 +1391,15 @@ public class GocaDecoder {
break;
}
if (charSet != 0 && programSymbolManager != null) {
if (charSet == 0xF8 || charPrecision == GocaConstants.CP_STROKE) {
char[] chars = new char[textLen];
for (int i = 0; i < textLen; i++) {
chars[i] = EbcdicTranslator.ebcdicToAscii(data[pos + i]);
}
String text = new String(chars);
plane.drawVectorText(plane.mapXDouble(startX), plane.mapYDouble(startY), text,
curColor, cw, ch, charDir, charAngle);
} else if (charSet != 0 && programSymbolManager != null) {
for (int i = 0; i < textLen; i++) {
int code = data[pos + i] & 0xFF;
double px = plane.mapXDouble(startX);
@@ -1417,27 +1423,18 @@ public class GocaDecoder {
}
}
}
} else {
char c = EbcdicTranslator.ebcdicToAscii(data[pos + i]);
plane.drawVectorText(px, py, String.valueOf(c), curColor, cw, ch, charDir, charAngle);
}
startX += (charWidth > 0 ? charWidth : 9);
}
curX = startX;
curY = startY;
return;
}
char[] chars = new char[textLen];
for (int i = 0; i < textLen; i++) {
chars[i] = EbcdicTranslator.ebcdicToAscii(data[pos + i]);
}
String text = new String(chars);
if (charPrecision == GocaConstants.CP_STROKE) {
plane.drawVectorText(plane.mapXDouble(startX), plane.mapYDouble(startY), text,
curColor, cw, ch, charDir, charAngle);
} else {
char[] chars = new char[textLen];
for (int i = 0; i < textLen; i++) {
chars[i] = EbcdicTranslator.ebcdicToAscii(data[pos + i]);
}
String text = new String(chars);
plane.drawText(plane.mapXDouble(startX), plane.mapYDouble(startY), text,
curColor, cw, ch, charDir, charAngle);
}
@@ -81,21 +81,19 @@ public class QueryReplyBuilderTest {
byte[] replies = qrBuilder.buildCompleteQueryReplies(80, 43, 80 * 43);
assertNotNull(replies);
// Vector Graphics (0xB0) must be present and Charsets must have LoadPS (0x0A)
// Vector Graphics (0xB0) must be present and Charsets must be present (0x85)
boolean hasB0 = false;
boolean hasCharsetsWithLoadPs = false;
boolean hasCharsets = false;
for (int i = 0; i < replies.length - 3; i++) {
if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_SEGMENT) {
hasB0 = true;
}
if ((replies[i] & 0xFF) == 0x81 && (completeReplyIsCharsets(replies, i))) {
if (i + 6 < replies.length && (replies[i + 6] & 0xFF) == 0x0A) {
hasCharsetsWithLoadPs = true;
}
hasCharsets = true;
}
}
assertTrue(hasB0);
assertTrue(hasCharsetsWithLoadPs);
assertTrue(hasCharsets);
}
private boolean completeReplyIsCharsets(byte[] replies, int i) {
@@ -283,24 +281,24 @@ public class QueryReplyBuilderTest {
assertEquals(0x81, replies[3] & 0xFF);
assertEquals(QR_USABLE_AREA, replies[4] & 0xFF);
// Flags: 12/14 bit addressing (0x01), 0x00
assertEquals(0x01, replies[5] & 0xFF);
// Flags: 12/14 bit addressing | Graphics (0x03), 0x00
assertEquals(0x03, replies[5] & 0xFF);
assertEquals(0x00, replies[6] & 0xFF);
// Usable width and height: 80, 43
assertEquals(80, ((replies[7] & 0xFF) << 8) | (replies[8] & 0xFF));
assertEquals(43, ((replies[9] & 0xFF) << 8) | (replies[10] & 0xFF));
// Units: 0x00 (Inches, matching IBM Host On-Demand QR_USEAREA_STRING)
// Units: 0x00 (Inches, matching IBM Host On-Demand DS3270.java)
assertEquals(0x00, replies[11] & 0xFF);
// Xr (4 bytes): 0x00020089 (matching HOD)
// Xr (4 bytes): 0x00010060 (96 dpi matching HOD)
int xr = ((replies[12] & 0xFF) << 24) | ((replies[13] & 0xFF) << 16) | ((replies[14] & 0xFF) << 8) | (replies[15] & 0xFF);
assertEquals(0x00020089, xr);
assertEquals(0x00010060, xr);
// Yr (4 bytes): 0x00020085 (matching HOD)
// Yr (4 bytes): 0x00010060 (96 dpi matching HOD)
int yr = ((replies[16] & 0xFF) << 24) | ((replies[17] & 0xFF) << 16) | ((replies[18] & 0xFF) << 8) | (replies[19] & 0xFF);
assertEquals(0x00020085, yr);
assertEquals(0x00010060, yr);
// AW and AH: 9 and 16
assertEquals(9, replies[20] & 0xFF);
@@ -309,4 +307,17 @@ public class QueryReplyBuilderTest {
// Buffer size: 80 * 43 = 3440
assertEquals(80 * 43, ((replies[22] & 0xFF) << 8) | (replies[23] & 0xFF));
}
@Test
public void testGrSymbolSetMatchesHOD() {
qrBuilder.setGraphicsMode(GraphicsMode.VECTOR_GRAPHICS);
byte[] requested = new byte[] { (byte) QR_GRSYMBOLSET };
byte[] replies = qrBuilder.buildQueryReplies(requested, 80, 43, 80 * 43);
assertNotNull(replies);
assertEquals((byte) AID_SF, replies[0]);
assertEquals(33, ((replies[1] & 0xFF) << 8) | (replies[2] & 0xFF));
assertEquals(0x81, replies[3] & 0xFF);
assertEquals(QR_GRSYMBOLSET, replies[4] & 0xFF);
}
}