diff --git a/j3270/src/main/java/org/pubvm/j3270/ui/StatusBar.java b/j3270/src/main/java/org/pubvm/j3270/ui/StatusBar.java index b4bc6e7..8fb3dcd 100644 --- a/j3270/src/main/java/org/pubvm/j3270/ui/StatusBar.java +++ b/j3270/src/main/java/org/pubvm/j3270/ui/StatusBar.java @@ -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); + } } } diff --git a/j3270/src/main/java/org/pubvm/j3270/ui/TerminalPanel.java b/j3270/src/main/java/org/pubvm/j3270/ui/TerminalPanel.java index 5063d30..5361eb0 100644 --- a/j3270/src/main/java/org/pubvm/j3270/ui/TerminalPanel.java +++ b/j3270/src/main/java/org/pubvm/j3270/ui/TerminalPanel.java @@ -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; } diff --git a/lib3270j/src/main/java/org/lib3270j/graphics/GocaDecoder.java b/lib3270j/src/main/java/org/lib3270j/graphics/GocaDecoder.java index 858e3b8..e169d12 100644 --- a/lib3270j/src/main/java/org/lib3270j/graphics/GocaDecoder.java +++ b/lib3270j/src/main/java/org/lib3270j/graphics/GocaDecoder.java @@ -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) { diff --git a/lib3270j/src/main/java/org/lib3270j/input/InputProcessor.java b/lib3270j/src/main/java/org/lib3270j/input/InputProcessor.java index 0d4579f..1f629fa 100644 --- a/lib3270j/src/main/java/org/lib3270j/input/InputProcessor.java +++ b/lib3270j/src/main/java/org/lib3270j/input/InputProcessor.java @@ -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; }