Cleanup datastream and Graphics
Build and Test j3270 / Build JAR & Run Tests (Java 11) (push) Successful in 36s
Build and Test j3270 / Build JAR & Run Tests (Java 17) (push) Successful in 1m13s
Build and Test j3270 / Build JAR & Run Tests (Java 21) (push) Successful in 1m34s

This commit is contained in:
2026-09-18 00:55:54 +00:00
parent ef70ea0f5a
commit badd026055
9 changed files with 932 additions and 31 deletions
@@ -363,8 +363,11 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
boolean isEnterField = (desEbcdic == 0x50 || desAscii == '&' || desAscii == '\uFF06');
boolean isProtected = haus.nightmare.lib3270j.protocol.DS3270Constants.faIsProtected(fa);
// Only auto-select toggle fields (?/>), enter fields (&), or unprotected space fields
if (lightPenMode || isToggleField || isEnterField || !isProtected) {
boolean isDoubleClick = (e.getClickCount() >= 2);
// For detectable fields (FA bits 4-5 = 01 or 11):
// Double-clicking or invoking mnemonic [cursel] toggles the designator character (? -> >).
// If field has immediate select attribute, immediately transmit Cursor Select AID (0x7E).
if (lightPenMode || isDoubleClick || isToggleField || isEnterField || !isProtected) {
if (client.lightPenSelect(clickAddr)) {
refreshScreen();
return;
@@ -1343,7 +1346,41 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
blinkTimer = new Timer(500, e -> {
cursorVisible = !cursorVisible;
textBlinkVisible = !textBlinkVisible;
repaint();
// Check if there are any blinking cells or fields on the screen
boolean hasBlinkingCells = false;
ScreenBuffer sb = (client != null) ? client.getScreenBuffer() : null;
if (sb != null) {
int total = sb.getDisplayRows() * sb.getDisplayCols();
byte curFaGr = 0;
for (int i = 0; i < total; i++) {
ExtendedAttribute c = sb.getDisplayCell(i);
if (c.isFieldAttribute()) {
curFaGr = c.gr;
}
if ((c.gr & ExtendedAttribute.GR_BLINK) != 0 || (curFaGr & ExtendedAttribute.GR_BLINK) != 0) {
hasBlinkingCells = true;
break;
}
}
}
if (hasBlinkingCells) {
repaint();
} else if (sb != null && client != null && client.getConnectionState() != null && client.getConnectionState().isFullSession()) {
// Only repaint the cursor cell bounding rect
int cols = sb.getDisplayCols();
int ox = getRenderOffsetX();
int oy = getRenderOffsetY();
int curAddr = sb.getDisplayCursorAddress();
int curRow = curAddr / cols;
int curCol = curAddr % cols;
int cx = ox + curCol * cellWidth;
int cy = oy + curRow * cellHeight;
repaint(cx, cy, cellWidth, cellHeight);
} else {
repaint();
}
});
blinkTimer.start();
}
@@ -1609,6 +1646,9 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
}
}
// Field Outlining (0xC2 / TY_FOUTLINE)
drawOutlining(g2, ea, currentFieldEa, baddr, row, col, x, y, cellWidth, cellHeight, rows, cols, sb, fgColor);
if (isCellSelected(row, col)) {
g2.setColor(SELECTION_COLOR);
g2.fillRect(x, y, cellWidth, cellHeight);
@@ -1628,7 +1668,7 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
}
// Check text blink (500ms cycle)
boolean blink = (gr & GR_BLINK) != 0;
boolean blink = (gr & GR_BLINK) != 0 || (currentFieldEa != null && (currentFieldEa.gr & GR_BLINK) != 0);
boolean suppressGlyph = blink && !textBlinkVisible;
// Draw character or Programmed Symbol
@@ -1662,12 +1702,15 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
}
}
if (underline) {
if (underline && !suppressGlyph) {
g2.setColor(fgColor);
int ulY = Math.min(y + cellHeight - 1, y + fontAscent + Math.max(0, (cellHeight - (fontAscent + fontDescent)) / 2) + 2);
g2.drawLine(x, ulY, x + cellWidth - 1, ulY);
}
// Field Outlining (0xC2 / TY_FOUTLINE)
drawOutlining(g2, ea, currentFieldEa, baddr, row, col, x, y, cellWidth, cellHeight, rows, cols, sb, fgColor);
// Draw search highlight
if (isCellSearchHighlighted(row, col)) {
g2.setColor(SEARCH_HIGHLIGHT_COLOR);
@@ -1835,6 +1878,57 @@ public class TerminalPanel extends JPanel implements java.awt.print.Printable {
return faIsHigh(fa & 0xFF) ? monoIntensify : monoNormal;
}
/**
* Render field outline lines around cells based on ea.ol or field attribute outline.
* Conforms to IBM 3270 Extended Data Stream / Host On-Demand DS3270 TY_FOUTLINE (0xC2).
*
* Bit 0 (0x01): Underline -> draw line at y + cellHeight - 1
* Bit 1 (0x02): Right vertical line -> draw line at x + cellWidth - 1
* Bit 2 (0x04): Overline -> draw line at y
* Bit 3 (0x08): Left vertical line -> draw line at x
*/
private void drawOutlining(Graphics2D g2, ExtendedAttribute ea, ExtendedAttribute currentFieldEa,
int baddr, int row, int col, int x, int y,
int cellWidth, int cellHeight, int rows, int cols,
ScreenBuffer sb, Color fgColor) {
int outline = ea.ol & 0xFF;
if (outline == 0 && currentFieldEa != null && currentFieldEa.ol != 0) {
int fol = currentFieldEa.ol & 0xFF;
if ((fol & ExtendedAttribute.OL_UNDERLINE) != 0) outline |= ExtendedAttribute.OL_UNDERLINE;
if ((fol & ExtendedAttribute.OL_OVERLINE) != 0) outline |= ExtendedAttribute.OL_OVERLINE;
int size = rows * cols;
int prevAddr = (baddr - 1 + size) % size;
if (sb.getDisplayCell(prevAddr).isFieldAttribute() || col == 0) {
if ((fol & ExtendedAttribute.OL_LEFT) != 0) outline |= ExtendedAttribute.OL_LEFT;
}
int nextAddr = (baddr + 1) % size;
if (sb.getDisplayCell(nextAddr).isFieldAttribute() || col == cols - 1) {
if ((fol & ExtendedAttribute.OL_RIGHT) != 0) outline |= ExtendedAttribute.OL_RIGHT;
}
}
if (outline != 0) {
g2.setColor(fgColor);
// Bit 0 (0x01): Underline -> draw line at y + cellHeight - 1
if ((outline & ExtendedAttribute.OL_UNDERLINE) != 0) {
g2.drawLine(x, y + cellHeight - 1, x + cellWidth - 1, y + cellHeight - 1);
}
// Bit 1 (0x02): Right vertical line -> draw line at x + cellWidth - 1
if ((outline & ExtendedAttribute.OL_RIGHT) != 0) {
g2.drawLine(x + cellWidth - 1, y, x + cellWidth - 1, y + cellHeight - 1);
}
// Bit 2 (0x04): Overline -> draw line at y
if ((outline & ExtendedAttribute.OL_OVERLINE) != 0) {
g2.drawLine(x, y, x + cellWidth - 1, y);
}
// Bit 3 (0x08): Left vertical line -> draw line at x
if ((outline & ExtendedAttribute.OL_LEFT) != 0) {
g2.drawLine(x, y, x, y + cellHeight - 1);
}
}
}
public void dispose() {
if (blinkTimer != null)
blinkTimer.stop();