Lightpen on
Build and Test j3270 / Build JAR & Run Tests (push) Successful in 1m8s

This commit is contained in:
2026-08-24 16:55:38 +00:00
parent e28b7dc30a
commit d2bdb9bb0a
4 changed files with 70 additions and 32 deletions
@@ -20,6 +20,7 @@ public class StatusBar extends JPanel {
private final JLabel luName;
private final JLabel lockStatus;
private final JLabel modelInfo;
private final JButton lpButton;
private Telnet3270Client client;
private TerminalPanel terminalPanel;
@@ -45,7 +46,7 @@ public class StatusBar extends JPanel {
modelInfo = createLabel("", oiaFont, OIA_DIM);
cursorPosition = createLabel("001/001", oiaFont, OIA_FG);
JButton lpButton = new JButton("LightPen: OFF");
lpButton = new JButton("LightPen: OFF");
lpButton.setFont(oiaFont);
lpButton.setForeground(OIA_FG);
lpButton.setBackground(OIA_BG);
@@ -177,5 +178,11 @@ public class StatusBar extends JPanel {
int row = sb.getCursorRow() + 1;
int col = sb.getCursorCol() + 1;
cursorPosition.setText(String.format("%03d/%03d", row, col));
// Light Pen indicator — sync with actual state
if (terminalPanel != null) {
lpButton.setText("LightPen: " + (terminalPanel.isLightPenMode() ? "ON" : "OFF"));
lpButton.setForeground(terminalPanel.isLightPenMode() ? new Color(255, 255, 80) : OIA_FG);
}
}
}
@@ -215,7 +215,12 @@ public class TerminalPanel extends JPanel {
sb.setCursorAddress(newAddr);
clearSelection();
if (lightPenMode) {
if (client.getInputProcessor().lightPenSelect(newAddr)) {
System.out.println("LP click: row=" + selectionStartRow + " col=" + selectionStartCol
+ " addr=" + newAddr + " mouseY=" + e.getY() + " oy=" + getRenderOffsetY()
+ " cellH=" + cellHeight);
boolean result = client.getInputProcessor().lightPenSelect(newAddr);
System.out.println("LP lightPenSelect result: " + result);
if (result) {
refreshScreen();
return;
}
@@ -35,9 +35,9 @@ public class GocaDecoder {
private int charHeight = 16;
private int charSet = 0;
private int arcParamP = 1;
private int arcParamQ = 1;
private int arcParamQ = 0;
private int arcParamR = 0;
private int arcParamS = 0;
private int arcParamS = 1;
private ProgramSymbolManager programSymbolManager;
@@ -832,48 +832,70 @@ public class GocaDecoder {
int centerX = curX;
int centerY = curY;
// For absolute variants, read the center point from data
if (!fromCurPos && pos + 4 <= off + len) {
centerX = readCoord(data, pos);
centerY = readCoord(data, pos + 2);
pos += 4;
}
double sweepFraction = 1.0;
if (!isFull && pos + 2 <= off + len) {
byte intPart = data[pos];
double fracPart = (data[pos + 1] & 0xFF) / 256.0;
sweepFraction = intPart + (intPart >= 0 ? fracPart : -fracPart);
// Read the multiplier (integer.fraction format)
double multiplier = 1.0;
if (pos + 2 <= off + len) {
int intPart = data[pos]; // signed byte
int fracPart = data[pos + 1] & 0xFF;
multiplier = intPart + fracPart / 256.0;
if (multiplier == 0.0) multiplier = 1.0;
pos += 2;
}
int pxCenter = plane.mapX(centerX);
int pyCenter = plane.mapY(centerY);
int pxCur = plane.mapX(curX);
int pyCur = plane.mapY(curY);
// Compute semi-axes from arc parameters scaled by multiplier
// P,Q and R,S are conjugate semi-diameter vectors
double pScaled = arcParamP * multiplier;
double qScaled = arcParamQ * multiplier;
double rScaled = arcParamR * multiplier;
double sScaled = arcParamS * multiplier;
double radius = Math.sqrt(Math.pow(pxCur - pxCenter, 2) + Math.pow(pyCur - pyCenter, 2));
int rx = (int) Math.round(radius);
int ry = rx;
if (rx <= 0) {
rx = 10;
ry = 10;
// Semi-axis lengths in GOCA coordinate space
double semiAxis1 = Math.sqrt(pScaled * pScaled + qScaled * qScaled);
double semiAxis2 = Math.sqrt(rScaled * rScaled + sScaled * sScaled);
if (semiAxis1 < 1.0) semiAxis1 = 1.0;
if (semiAxis2 < 1.0) semiAxis2 = 1.0;
// Map to pixel space
int rx = Math.abs(plane.mapX((int) Math.round(semiAxis1)) - plane.mapX(0));
int ry = Math.abs(plane.mapY(0) - plane.mapY((int) Math.round(semiAxis2)));
if (rx <= 0) rx = 1;
if (ry <= 0) ry = 1;
// For partial arcs, read sweep start and sweep angle
double startAngleDeg = 0.0;
double sweepAngleDeg = 360.0;
if (!isFull && pos + 4 <= off + len) {
// Sweep start: integer.fraction of full revolution
int startInt = data[pos];
int startFrac = data[pos + 1] & 0xFF;
startAngleDeg = (startInt + startFrac / 256.0) * 360.0;
pos += 2;
// Sweep angle: integer.fraction of full revolution
int sweepInt = data[pos];
int sweepFrac = data[pos + 1] & 0xFF;
sweepAngleDeg = (sweepInt + sweepFrac / 256.0) * 360.0;
} else if (!isFull) {
// No sweep data — default to full circle
sweepAngleDeg = 360.0;
}
double startAngleRad = Math.atan2(pyCenter - pyCur, pxCur - pxCenter);
double startAngleDeg = Math.toDegrees(startAngleRad);
if (startAngleDeg < 0) startAngleDeg += 360.0;
double sweepAngleDeg = isFull ? 360.0 : (sweepFraction * 360.0);
System.out.println("processArc: center=(" + centerX + "," + centerY + ") mult=" + multiplier
+ " P=" + arcParamP + " Q=" + arcParamQ + " R=" + arcParamR + " S=" + arcParamS
+ " rx=" + rx + " ry=" + ry + " start=" + startAngleDeg + " sweep=" + sweepAngleDeg
+ " isFull=" + isFull);
plane.drawArc(pxCenter, pyCenter, rx, ry, startAngleDeg, sweepAngleDeg,
plane.drawArc(plane.mapX(centerX), plane.mapY(centerY), rx, ry, startAngleDeg, sweepAngleDeg,
curColor, lineType, lineWidth, isFull);
// Arc ends at the end of the sweep
double endAngleRad = Math.toRadians(startAngleDeg + sweepAngleDeg);
int pxEnd = (int) Math.round(pxCenter + rx * Math.cos(endAngleRad));
int pyEnd = (int) Math.round(pyCenter - ry * Math.sin(endAngleRad));
curX = plane.unmapX(pxEnd);
curY = plane.unmapY(pyEnd);
curX = centerX;
curY = centerY;
}
private void processFillet(byte[] data, int off, int len, boolean fromCurPos) {
@@ -357,16 +357,20 @@ public class InputProcessor {
*/
public boolean lightPenSelect(int address) {
if (screen == null || !screen.isFormatted()) {
System.out.println("LP: screen null or unformatted");
return false;
}
int size = screen.getRows() * screen.getCols();
int faPos = screen.findFieldAttribute(address);
if (faPos < 0) {
System.out.println("LP: no FA found for addr=" + address);
return false;
}
ExtendedAttribute faCell = screen.getCell(faPos);
int fa = faCell.fa & 0xFF;
System.out.println("LP: addr=" + address + " faPos=" + faPos + " fa=0x" + String.format("%02X", fa)
+ " selectable=" + org.lib3270j.protocol.DS3270Constants.faIsSelectable(fa));
if (!org.lib3270j.protocol.DS3270Constants.faIsSelectable(fa)) {
return false;
}