Cleanup datastream and Graphics
This commit is contained in:
@@ -59,6 +59,7 @@ public class DataStreamProcessor {
|
||||
private byte currentBg = 0;
|
||||
private byte currentGr = 0;
|
||||
private byte currentCs = 0;
|
||||
private byte currentOl = 0;
|
||||
|
||||
/** Functional interface for sending output back through the telnet stack. */
|
||||
@FunctionalInterface
|
||||
@@ -250,6 +251,7 @@ public class DataStreamProcessor {
|
||||
currentBg = 0;
|
||||
currentGr = 0;
|
||||
currentCs = 0;
|
||||
currentOl = 0;
|
||||
break;
|
||||
case CMD_WSF:
|
||||
case SNA_CMD_WSF:
|
||||
@@ -392,6 +394,7 @@ public class DataStreamProcessor {
|
||||
currentBg = 0;
|
||||
currentGr = 0;
|
||||
currentCs = 0;
|
||||
currentOl = 0;
|
||||
}
|
||||
|
||||
if (eraseFirst) {
|
||||
@@ -401,6 +404,7 @@ public class DataStreamProcessor {
|
||||
currentBg = 0;
|
||||
currentGr = 0;
|
||||
currentCs = 0;
|
||||
currentOl = 0;
|
||||
}
|
||||
|
||||
// Process orders and data starting at byte 2
|
||||
@@ -497,6 +501,7 @@ public class DataStreamProcessor {
|
||||
currentBg = 0;
|
||||
currentGr = 0;
|
||||
currentCs = 0;
|
||||
currentOl = 0;
|
||||
break;
|
||||
case XA_FOREGROUND:
|
||||
currentFg = (byte) attrValue;
|
||||
@@ -521,6 +526,10 @@ public class DataStreamProcessor {
|
||||
case XA_CHARSET:
|
||||
currentCs = (byte) attrValue;
|
||||
break;
|
||||
case XA_OUTLINING:
|
||||
case 0x84:
|
||||
currentOl = (byte) attrValue;
|
||||
break;
|
||||
}
|
||||
pos += 3;
|
||||
lastWasOrder = true;
|
||||
@@ -605,6 +614,7 @@ public class DataStreamProcessor {
|
||||
ea.bg = currentBg;
|
||||
ea.gr = (byte) currentGr;
|
||||
ea.cs = fillCs;
|
||||
ea.ol = currentOl;
|
||||
baddr = (baddr + 1) % size;
|
||||
} while (baddr != toAddr);
|
||||
screen.setBufferAddress(baddr);
|
||||
@@ -633,6 +643,7 @@ public class DataStreamProcessor {
|
||||
ea.bg = 0;
|
||||
ea.gr = 0;
|
||||
ea.cs = 0;
|
||||
ea.ol = 0;
|
||||
}
|
||||
}
|
||||
baddr = (baddr + 1) % size;
|
||||
@@ -657,6 +668,7 @@ public class DataStreamProcessor {
|
||||
ea.fg = currentFg;
|
||||
ea.bg = currentBg;
|
||||
ea.gr = (byte) currentGr;
|
||||
ea.ol = currentOl;
|
||||
baddr = (baddr + 1) % size;
|
||||
screen.setBufferAddress(baddr);
|
||||
pos += 2;
|
||||
@@ -683,6 +695,7 @@ public class DataStreamProcessor {
|
||||
ea.bg = currentBg;
|
||||
ea.gr = (byte) currentGr;
|
||||
ea.cs = currentCs;
|
||||
ea.ol = currentOl;
|
||||
|
||||
baddr = (baddr + 1) % size;
|
||||
screen.setBufferAddress(baddr);
|
||||
@@ -995,6 +1008,7 @@ public class DataStreamProcessor {
|
||||
byte[] psData = new byte[fieldLen - 3];
|
||||
System.arraycopy(data, pos + 3, psData, 0, psData.length);
|
||||
programSymbolManager.loadps(psData);
|
||||
notifyScreenUpdated();
|
||||
}
|
||||
break;
|
||||
case haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJDATA: // 0x85: Graphics Object Data / GOCA
|
||||
@@ -1305,6 +1319,7 @@ public class DataStreamProcessor {
|
||||
byte[] psData = new byte[fieldLen - 4];
|
||||
System.arraycopy(data, offset + 4, psData, 0, psData.length);
|
||||
programSymbolManager.loadps(psData);
|
||||
notifyScreenUpdated();
|
||||
}
|
||||
break;
|
||||
case haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJDATA_SUB: { // 0x0F: Object Control / Data Unit
|
||||
|
||||
@@ -482,8 +482,8 @@ public class QueryReplyBuilder {
|
||||
cpgid = screen.getTranslator().getCpgid();
|
||||
}
|
||||
|
||||
if (graphicsMode == GraphicsMode.PROGRAMMED_SYMBOLS) {
|
||||
// Programmed Symbols only mode (3279 PS with LoadPS 0x0A, flags1 = 0xA2 for GE + PS + CGCSGID)
|
||||
if (graphicsMode.isProgrammedSymbolsEnabled()) {
|
||||
// Programmed Symbols mode (3279 PS / 3179G 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
|
||||
@@ -681,16 +681,18 @@ public class QueryReplyBuilder {
|
||||
public byte[] buildGrSymbolSet() {
|
||||
int charW = getCharWidth();
|
||||
int charH = getCharHeight();
|
||||
int cgcsgid = 0x02B9;
|
||||
int cpgid = 0x0025;
|
||||
if (screen != null && screen.getTranslator() != null) {
|
||||
cgcsgid = screen.getTranslator().getCgcsgid();
|
||||
cpgid = screen.getTranslator().getCpgid();
|
||||
if (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS()) {
|
||||
return new byte[]{
|
||||
0x00, 0x00, (byte) charW, (byte) charH, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x12,
|
||||
0x01, 0x00, 0x00, (byte) 0xF0, (byte) 0xC1, (byte) 0xC1, 0x00, 0x00, (byte) charW, (byte) charH,
|
||||
0x00, 0x01, 0x4C, 0x01, 0x22, 0x00, 0x00, 0x12, 0x01, 0x00, 0x20, (byte) 0xF8, (byte) 0xC1, (byte) 0xC1,
|
||||
0x00, 0x00, 0x18, 0x18, 0x00, 0x01, 0x72, 0x01, 0x2C, 0x00, (byte) 0xFF
|
||||
};
|
||||
}
|
||||
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, (byte) 0xF0, (byte) ((cgcsgid >> 8) & 0xFF), (byte) (cgcsgid & 0xFF), 0x00, 0x00
|
||||
0x01, 0x00, 0x00, (byte) 0xF0, (byte) 0xC1, (byte) 0xC1, 0x00, 0x00,
|
||||
(byte) charW, (byte) charH, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1159,7 +1159,9 @@ public class GraphicsPlane implements PixelBuffer {
|
||||
int pIdx = psY * psW + psX;
|
||||
boolean bit = (psPix != null && pIdx < psPix.length && psPix[pIdx] != 0);
|
||||
if (bit) {
|
||||
setPixelInFill(x, y, fill, isMixOr);
|
||||
int pixColor = (psSlot.isTriplePlane() && psSlot.getRgbPixels(fill, bg) != null)
|
||||
? psSlot.getRgbPixels(fill, bg)[pIdx] : fill;
|
||||
setPixelInFill(x, y, pixColor, isMixOr);
|
||||
} else if (isOpaqueBg) {
|
||||
setPixelInFill(x, y, bg, isMixOr);
|
||||
}
|
||||
@@ -1192,7 +1194,9 @@ public class GraphicsPlane implements PixelBuffer {
|
||||
int pIdx = psY * psW + psX;
|
||||
boolean bit = (psPix != null && pIdx < psPix.length && psPix[pIdx] != 0);
|
||||
if (bit) {
|
||||
setPixelInFill(x, y, fill, isMixOr);
|
||||
int pixColor = (psSlot.isTriplePlane() && psSlot.getRgbPixels(fill, bg) != null)
|
||||
? psSlot.getRgbPixels(fill, bg)[pIdx] : fill;
|
||||
setPixelInFill(x, y, pixColor, isMixOr);
|
||||
} else if (isOpaqueBg) {
|
||||
setPixelInFill(x, y, bg, isMixOr);
|
||||
}
|
||||
|
||||
@@ -175,34 +175,46 @@ public class ProgramSymbolManager {
|
||||
int startCodePoint = data[2] & 0xFF;
|
||||
int rws = data[3] & 0xFF;
|
||||
|
||||
int normRws = rws;
|
||||
if (normRws >= 0x40 && normRws <= 0x49) {
|
||||
normRws = normRws - 0x40 + 2; // 0x40 -> 2 (PSA), 0x41 -> 3 (PSB), ..., 0x45 -> 7 (PSF)
|
||||
} else if ((normRws < 2 || normRws > 11) && (lcid >= 0x40 && lcid <= 0x49)) {
|
||||
normRws = lcid - 0x40 + 2;
|
||||
}
|
||||
|
||||
int setIndex;
|
||||
switch (rws) {
|
||||
case 2: setIndex = 0; break;
|
||||
case 3: setIndex = 1; break;
|
||||
case 4: setIndex = 2; break;
|
||||
case 5: setIndex = 3; break;
|
||||
case 6: setIndex = 4; break;
|
||||
case 7: setIndex = 5; break;
|
||||
switch (normRws) {
|
||||
case 2: setIndex = 0; break; // PSA (0x40)
|
||||
case 3: setIndex = 1; break; // PSB (0x41)
|
||||
case 4: setIndex = 2; break; // PSC (0x42)
|
||||
case 5: setIndex = 3; break; // PSD (0x43)
|
||||
case 6: setIndex = 4; break; // PSE (0x44)
|
||||
case 7: setIndex = 5; break; // PSF (0x45)
|
||||
case 8: setIndex = 6; break;
|
||||
case 9: setIndex = 7; break;
|
||||
case 10: setIndex = 8; break;
|
||||
case 11: setIndex = 9; break;
|
||||
default:
|
||||
logger.warning("LOADPS: Invalid RWS slot 0x" + Integer.toHexString(rws));
|
||||
return;
|
||||
if (lcid >= 0x40 && lcid < 256) {
|
||||
setIndex = (lcid - 0x40) % NUMBER_SYMBOL_SETS;
|
||||
} else {
|
||||
logger.warning("LOADPS: Invalid RWS slot 0x" + Integer.toHexString(rws));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
boolean isTriplePlane = (rws >= 4 && rws <= 7);
|
||||
ProgramSymbolSet set = sets[setIndex];
|
||||
boolean isTriplePlane = set.isTriplePlane() || (normRws >= 4 && normRws <= 7);
|
||||
|
||||
if (clearAll) {
|
||||
set.clear();
|
||||
}
|
||||
|
||||
int extHeaderLen = 0;
|
||||
int cellWidth = (loadFormat == 1 || rws < 8) ? 9 : defaultCellWidth;
|
||||
int cellHeight = (loadFormat == 1 || rws < 8) ? 16 : defaultCellHeight;
|
||||
int colorPlane = 0; // 0 = all planes, 1 = Blue, 2 = Red, 4 = Green
|
||||
int cellWidth = (loadFormat == 1) ? 9 : (defaultCellWidth > 0 ? defaultCellWidth : 9);
|
||||
int cellHeight = (loadFormat == 1) ? 16 : (defaultCellHeight > 0 ? defaultCellHeight : 16);
|
||||
int colorPlane = 0; // 0 = all planes, 1 = Red, 2 = Green, 4 = Blue
|
||||
|
||||
if (hasExtHeader && data.length > 4) {
|
||||
extHeaderLen = data[4] & 0xFF;
|
||||
|
||||
@@ -334,11 +334,17 @@ public class InputProcessor {
|
||||
}
|
||||
|
||||
// Set MDT on field attribute
|
||||
boolean isLastFieldChar = false;
|
||||
ExtendedAttribute currentFaEa = null;
|
||||
if (screen.isFormatted()) {
|
||||
int faAddr = screen.findFieldAttribute(baddr);
|
||||
if (faAddr >= 0) {
|
||||
ExtendedAttribute faEa = screen.getCell(faAddr);
|
||||
faEa.fa = (byte) (faEa.fa | FA_MODIFY);
|
||||
currentFaEa = screen.getCell(faAddr);
|
||||
currentFaEa.fa = (byte) (currentFaEa.fa | FA_MODIFY);
|
||||
}
|
||||
int nextAddr = screen.incrementAddress(baddr);
|
||||
if (screen.getCell(nextAddr).isFieldAttribute()) {
|
||||
isLastFieldChar = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,6 +397,11 @@ public class InputProcessor {
|
||||
|
||||
screen.markAllChanged();
|
||||
screen.updateDisplaySnapshot();
|
||||
|
||||
// Field Validation: Trigger field (0x04) — automatically send AID upon filling the last character position
|
||||
if (isLastFieldChar && currentFaEa != null && (currentFaEa.vl & ExtendedAttribute.VL_TRIGGER) != 0) {
|
||||
sendAid(AID_ENTER);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -858,6 +869,13 @@ public class InputProcessor {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aidCode != AID_CLEAR && aidCode != AID_PA1 && aidCode != AID_PA2 && aidCode != AID_PA3 && aidCode != AID_SYSREQ && aidCode != AID_SELECT) {
|
||||
if (!validateInput()) {
|
||||
log.fine("Input validation failed for AID 0x" + Integer.toHexString(aidCode));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (insertOffOnAid && insertMode) {
|
||||
setInsertMode(false);
|
||||
}
|
||||
@@ -1017,6 +1035,141 @@ public class InputProcessor {
|
||||
sendAidResponse(out.toByteArray());
|
||||
}
|
||||
|
||||
// ========== 3270 EDS Field Validation (0xC1 / TY_FVALIDATE) ==========
|
||||
|
||||
/**
|
||||
* Enforce 3270 Extended Data Stream field validation attributes (0xC1 / TY_FVALIDATE)
|
||||
* prior to transmitting AID keys (Mandatory Fill: 0x01, Mandatory Entry: 0x02).
|
||||
*
|
||||
* @return true if all fields pass validation, false if validation failed and input inhibited
|
||||
*/
|
||||
public boolean validateInput() {
|
||||
if (screen == null || !screen.isFormatted()) {
|
||||
return true;
|
||||
}
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
if (size <= 0) return true;
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
ExtendedAttribute faCell = screen.getCell(i);
|
||||
if (!faCell.isFieldAttribute()) continue;
|
||||
|
||||
int vl = faCell.vl & 0xFF;
|
||||
if (vl == 0) continue;
|
||||
|
||||
// Only validate unprotected input fields
|
||||
if (faIsProtected(faCell.fa & 0xFF)) continue;
|
||||
|
||||
int fieldStart = (i + 1) % size;
|
||||
int totalChars = 0;
|
||||
int filledChars = 0;
|
||||
int firstEmptyPos = -1;
|
||||
|
||||
int p = fieldStart;
|
||||
while (p != i && !screen.getCell(p).isFieldAttribute()) {
|
||||
ExtendedAttribute cell = screen.getCell(p);
|
||||
totalChars++;
|
||||
int ec = cell.ec & 0xFF;
|
||||
char ucs = cell.ucs4;
|
||||
boolean isFilled = (ec != 0x00 && ec != 0x40 && ucs != 0 && ucs != ' ' && ucs != '\u3000');
|
||||
if (isFilled) {
|
||||
filledChars++;
|
||||
} else if (firstEmptyPos < 0) {
|
||||
firstEmptyPos = p;
|
||||
}
|
||||
p = (p + 1) % size;
|
||||
}
|
||||
|
||||
if (totalChars == 0) continue;
|
||||
|
||||
boolean isModified = faIsModified(faCell.fa & 0xFF);
|
||||
boolean mandatoryFill = (vl & ExtendedAttribute.VL_MANDATORY_FILL) != 0;
|
||||
boolean mandatoryEntry = (vl & ExtendedAttribute.VL_MANDATORY_ENTRY) != 0;
|
||||
|
||||
// Mandatory Entry (0x02): Field must contain at least one user-entered character
|
||||
if (mandatoryEntry && filledChars == 0) {
|
||||
failValidation(fieldStart);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mandatory Fill (0x01): Field must be completely filled with characters (no nulls/blanks).
|
||||
// If the field is modified, has characters entered, or has Mandatory Entry also set:
|
||||
if (mandatoryFill) {
|
||||
if (mandatoryEntry || isModified || filledChars > 0) {
|
||||
if (filledChars < totalChars) {
|
||||
failValidation(firstEmptyPos >= 0 ? firstEmptyPos : fieldStart);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the field at the current cursor position when the user attempts to tab away.
|
||||
* If the field has Mandatory Fill (0x01) and is partially filled, inhibits keyboard with input error.
|
||||
*/
|
||||
public boolean validateCurrentFieldOnExit() {
|
||||
if (screen == null || !screen.isFormatted()) {
|
||||
return true;
|
||||
}
|
||||
int curAddr = screen.getCursorAddress();
|
||||
int faPos = screen.findFieldAttribute(curAddr);
|
||||
if (faPos < 0) return true;
|
||||
|
||||
ExtendedAttribute faCell = screen.getCell(faPos);
|
||||
int vl = faCell.vl & 0xFF;
|
||||
if ((vl & ExtendedAttribute.VL_MANDATORY_FILL) == 0) {
|
||||
return true;
|
||||
}
|
||||
if (faIsProtected(faCell.fa & 0xFF)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
int fieldStart = (faPos + 1) % size;
|
||||
int totalChars = 0;
|
||||
int filledChars = 0;
|
||||
int firstEmptyPos = -1;
|
||||
|
||||
int p = fieldStart;
|
||||
while (p != faPos && !screen.getCell(p).isFieldAttribute()) {
|
||||
ExtendedAttribute cell = screen.getCell(p);
|
||||
totalChars++;
|
||||
int ec = cell.ec & 0xFF;
|
||||
char ucs = cell.ucs4;
|
||||
boolean isFilled = (ec != 0x00 && ec != 0x40 && ucs != 0 && ucs != ' ' && ucs != '\u3000');
|
||||
if (isFilled) {
|
||||
filledChars++;
|
||||
} else if (firstEmptyPos < 0) {
|
||||
firstEmptyPos = p;
|
||||
}
|
||||
p = (p + 1) % size;
|
||||
}
|
||||
|
||||
// If partially filled (incomplete):
|
||||
if (filledChars > 0 && filledChars < totalChars) {
|
||||
failValidation(firstEmptyPos >= 0 ? firstEmptyPos : curAddr);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void failValidation(int errorCursorPos) {
|
||||
if (errorCursorPos >= 0 && screen != null) {
|
||||
screen.setCursorAddress(errorCursorPos);
|
||||
screen.updateDisplaySnapshot();
|
||||
}
|
||||
if (oia != null) {
|
||||
oia.setInputInhibited(ECLConstants.INHIBIT_OPERATOR_DUE);
|
||||
}
|
||||
setKeyboardLocked(true);
|
||||
if (bellListener != null) {
|
||||
bellListener.onBell();
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Cursor movement ==========
|
||||
|
||||
/**
|
||||
@@ -1144,6 +1297,9 @@ public class InputProcessor {
|
||||
screen.processWordTab(true);
|
||||
return;
|
||||
}
|
||||
if (!validateCurrentFieldOnExit()) {
|
||||
return;
|
||||
}
|
||||
int addr = screen.findNextUnprotected(screen.getCursorAddress());
|
||||
screen.setCursorAddress(addr);
|
||||
screen.updateDisplaySnapshot();
|
||||
@@ -1163,6 +1319,9 @@ public class InputProcessor {
|
||||
return;
|
||||
}
|
||||
if (!screen.isFormatted()) return;
|
||||
if (!validateCurrentFieldOnExit()) {
|
||||
return;
|
||||
}
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
if (size <= 0) return;
|
||||
int addr = screen.getCursorAddress();
|
||||
|
||||
@@ -62,6 +62,20 @@ public class ExtendedAttribute {
|
||||
public static final byte GR_UNDERLINE = 0x04;
|
||||
public static final byte GR_INTENSIFY = 0x08;
|
||||
|
||||
// Outlining attribute bits (0xC2 / TY_FOUTLINE)
|
||||
public static final byte OL_NONE = 0x00;
|
||||
public static final byte OL_UNDERLINE = 0x01; // Bit 0: Underline (bottom border)
|
||||
public static final byte OL_RIGHT = 0x02; // Bit 1: Right vertical border
|
||||
public static final byte OL_OVERLINE = 0x04; // Bit 2: Overline (top border)
|
||||
public static final byte OL_LEFT = 0x08; // Bit 3: Left vertical border
|
||||
public static final byte OL_BOX = 0x0F; // All four borders
|
||||
|
||||
// Field Validation attribute bits (0xC1 / TY_FVALIDATE)
|
||||
public static final byte VL_NONE = 0x00;
|
||||
public static final byte VL_MANDATORY_FILL = 0x01; // Bit 0: Mandatory fill
|
||||
public static final byte VL_MANDATORY_ENTRY = 0x02; // Bit 1: Mandatory entry
|
||||
public static final byte VL_TRIGGER = 0x04; // Bit 2: Trigger field
|
||||
|
||||
/**
|
||||
* Graphics rendition bits.
|
||||
* GR_BLINK=0x01, GR_REVERSE=0x02, GR_UNDERLINE=0x04, GR_INTENSIFY=0x08
|
||||
|
||||
Reference in New Issue
Block a user