Phase 1
This commit is contained in:
@@ -39,6 +39,8 @@ public class Telnet3270Client {
|
||||
private final DataStreamProcessor dsProcessor;
|
||||
private final TelnetFSM fsm;
|
||||
private final InputProcessor inputProcessor;
|
||||
private final haus.nightmare.lib3270j.ecl.ECLPS ps;
|
||||
private final haus.nightmare.lib3270j.ecl.ECLOIA oia;
|
||||
private TelnetConnection connection;
|
||||
|
||||
public Telnet3270Client(ConnectionConfig config) {
|
||||
@@ -49,6 +51,8 @@ public class Telnet3270Client {
|
||||
this.dsProcessor.getQueryReplyBuilder().setGraphicsMode(config.getGraphicsMode());
|
||||
this.fsm = new TelnetFSM(config, screenBuffer, dsProcessor);
|
||||
this.inputProcessor = new InputProcessor(screenBuffer, translator, fsm);
|
||||
this.ps = new haus.nightmare.lib3270j.ecl.ECLPS(screenBuffer, inputProcessor, translator);
|
||||
this.oia = new haus.nightmare.lib3270j.ecl.ECLOIA(screenBuffer, inputProcessor, fsm);
|
||||
|
||||
// Wire up the output sender: DSProcessor -> FSM -> TelnetConnection
|
||||
dsProcessor.setOutputSender(fsm::send3270Data);
|
||||
@@ -125,6 +129,18 @@ public class Telnet3270Client {
|
||||
/** Get the connection config. */
|
||||
public ConnectionConfig getConfig() { return config; }
|
||||
|
||||
/** Get the ECL Presentation Space API. */
|
||||
public haus.nightmare.lib3270j.ecl.ECLPS getPS() { return ps; }
|
||||
|
||||
/** Get the ECL Operator Information Area API. */
|
||||
public haus.nightmare.lib3270j.ecl.ECLOIA getOIA() { return oia; }
|
||||
|
||||
/** Get the list of all fields currently on screen. */
|
||||
public haus.nightmare.lib3270j.ecl.ECLFieldList getFieldList() { return ps.getFieldList(); }
|
||||
|
||||
/** Send IBM ECL bracketed mnemonic keystrokes (e.g. "USER[tab]PASS[enter]"). */
|
||||
public void sendKeys(String keys) { inputProcessor.sendKeys(keys); }
|
||||
|
||||
/** Set a custom or interactive TLS certificate verifier callback. */
|
||||
public void setTlsCertificateVerifier(haus.nightmare.lib3270j.tls.TlsCertificateVerifier verifier) {
|
||||
config.setCertificateVerifier(verifier);
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package haus.nightmare.lib3270j.ecl;
|
||||
|
||||
/**
|
||||
* Common constants for IBM Host On-Demand Emulator Class Library (ECL) emulation.
|
||||
*/
|
||||
public interface ECLConstants {
|
||||
|
||||
// Presentation space logical planes
|
||||
int PLANE_TEXT = 1; // Unicode/EBCDIC characters
|
||||
int PLANE_COLOR = 2; // Color attributes
|
||||
int PLANE_HILITE = 3; // Extended highlighting
|
||||
int PLANE_EXTENDED = 4; // Extended character sets / outlining
|
||||
int PLANE_FIELD = 5; // Field attribute definition bytes
|
||||
int PLANE_DBCS = 6; // Double-byte character plane
|
||||
|
||||
// Color definitions (IBM 3279 standard 16-color palette)
|
||||
char COLOR_NEUTRAL_BLACK = 0;
|
||||
char COLOR_BLUE = 1;
|
||||
char COLOR_RED = 2;
|
||||
char COLOR_PINK = 3;
|
||||
char COLOR_GREEN = 4;
|
||||
char COLOR_TURQUOISE = 5;
|
||||
char COLOR_YELLOW = 6;
|
||||
char COLOR_NEUTRAL_WHITE = 7;
|
||||
char COLOR_BLACK = 8;
|
||||
char COLOR_DEEP_BLUE = 9;
|
||||
char COLOR_ORANGE = 10;
|
||||
char COLOR_PURPLE = 11;
|
||||
char COLOR_PALE_GREEN = 12;
|
||||
char COLOR_PALE_TURQUOISE= 13;
|
||||
char COLOR_GREY = 14;
|
||||
char COLOR_WHITE = 15;
|
||||
|
||||
// Highlighting attributes
|
||||
char HILITE_DEFAULT = 0x00;
|
||||
char HILITE_BLINK = 0xF1;
|
||||
char HILITE_REVERSE = 0xF2;
|
||||
char HILITE_UNDERSCORE = 0xF4;
|
||||
|
||||
// Search directions
|
||||
int SEARCH_FORWARD = 1;
|
||||
int SEARCH_BACKWARD = 2;
|
||||
|
||||
// OIA Input Inhibited Reason Codes
|
||||
int INHIBIT_NOT_INHIBITED = 0;
|
||||
int INHIBIT_SYSTEM_LOCK = 1; // X SYSTEM (Waiting for host response)
|
||||
int INHIBIT_NUMERIC_ONLY = 2; // X NUMERIC (Non-numeric in numeric field)
|
||||
int INHIBIT_PROTECTED_FIELD = 3; // X PROT (Attempted write into protected field)
|
||||
int INHIBIT_OVERFLOW = 4; // X > (Insert mode buffer overflow)
|
||||
int INHIBIT_COMM_CHECK = 5; // X COMM (Communication or socket check)
|
||||
int INHIBIT_OPERATOR_DUE = 6; // X OP (Operator Intervention Due)
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package haus.nightmare.lib3270j.ecl;
|
||||
|
||||
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||
|
||||
/**
|
||||
* Represents a discrete 3270 field within an IBM Host On-Demand Presentation Space.
|
||||
* Provides programmatic inspection and modification of field text and attributes.
|
||||
*/
|
||||
public class ECLField {
|
||||
|
||||
private final ECLPS ps;
|
||||
private final int startPos; // Position of field attribute byte
|
||||
private final int dataStart; // First data position (startPos + 1 wrapped)
|
||||
private final int endPos; // Last data position inclusive
|
||||
private final int length; // Usable data character length
|
||||
private final byte attribute; // Field attribute byte (FA)
|
||||
|
||||
public ECLField(ECLPS ps, int startPos, int dataStart, int endPos, int length, byte attribute) {
|
||||
this.ps = ps;
|
||||
this.startPos = startPos;
|
||||
this.dataStart = dataStart;
|
||||
this.endPos = endPos;
|
||||
this.length = length;
|
||||
this.attribute = attribute;
|
||||
}
|
||||
|
||||
/** Buffer address of the field attribute character. */
|
||||
public int getStart() { return startPos; }
|
||||
|
||||
/** First buffer address of the field data (start + 1). */
|
||||
public int getDataStart() { return dataStart; }
|
||||
|
||||
/** Last buffer address of the field data inclusive. */
|
||||
public int getEnd() { return endPos; }
|
||||
|
||||
/** Number of data characters in the field. */
|
||||
public int getLength() { return length; }
|
||||
|
||||
public int getStartRow() {
|
||||
int cols = ps.getCols();
|
||||
return cols > 0 ? startPos / cols : 0;
|
||||
}
|
||||
|
||||
public int getStartCol() {
|
||||
int cols = ps.getCols();
|
||||
return cols > 0 ? startPos % cols : 0;
|
||||
}
|
||||
|
||||
public int getEndRow() {
|
||||
int cols = ps.getCols();
|
||||
return cols > 0 ? endPos / cols : 0;
|
||||
}
|
||||
|
||||
public int getEndCol() {
|
||||
int cols = ps.getCols();
|
||||
return cols > 0 ? endPos % cols : 0;
|
||||
}
|
||||
|
||||
public boolean isModified() {
|
||||
return faIsModified(attribute & 0xFF);
|
||||
}
|
||||
|
||||
public boolean isProtected() {
|
||||
return faIsProtected(attribute & 0xFF);
|
||||
}
|
||||
|
||||
public boolean isNumeric() {
|
||||
return faIsNumeric(attribute & 0xFF);
|
||||
}
|
||||
|
||||
public boolean isHighIntensity() {
|
||||
return faIsHigh(attribute & 0xFF);
|
||||
}
|
||||
|
||||
public boolean isHidden() {
|
||||
return faIsZero(attribute & 0xFF);
|
||||
}
|
||||
|
||||
public boolean isDisplay() {
|
||||
return !isHidden();
|
||||
}
|
||||
|
||||
public boolean isPenSelectable() {
|
||||
return faIsSelectable(attribute & 0xFF);
|
||||
}
|
||||
|
||||
public short getAttribute() {
|
||||
return (short) (attribute & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text contents of this field as a String.
|
||||
*/
|
||||
public String getText() {
|
||||
if (length <= 0) return "";
|
||||
return ps.getString(dataStart, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the text contents of this field.
|
||||
*/
|
||||
public void setText(String text) {
|
||||
if (isProtected() || length <= 0) return;
|
||||
ps.setText(text, dataStart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get selector light pen type.
|
||||
* Returns '?' for selectable, '>' for selected, ' ' for space/unselectable.
|
||||
*/
|
||||
public char getSelectorPenType() {
|
||||
if (length <= 0) return ' ';
|
||||
String t = getText();
|
||||
if (!t.isEmpty()) {
|
||||
char first = t.charAt(0);
|
||||
if (first == '?' || first == '>') return first;
|
||||
}
|
||||
return ' ';
|
||||
}
|
||||
|
||||
/**
|
||||
* Actuate lightpen selection on this field ('?' -> '>').
|
||||
*/
|
||||
public void selectField() {
|
||||
if (isProtected() || length <= 0) return;
|
||||
String t = getText();
|
||||
if (!t.isEmpty() && t.charAt(0) == '?') {
|
||||
setText(">" + (t.length() > 1 ? t.substring(1) : ""));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deselect lightpen selection on this field ('>' -> '?').
|
||||
*/
|
||||
public void deSelectField() {
|
||||
if (isProtected() || length <= 0) return;
|
||||
String t = getText();
|
||||
if (!t.isEmpty() && t.charAt(0) == '>') {
|
||||
setText("?" + (t.length() > 1 ? t.substring(1) : ""));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("ECLField[start=%d, end=%d, len=%d, prot=%b, mod=%b, text=\"%s\"]",
|
||||
startPos, endPos, length, isProtected(), isModified(), getText());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package haus.nightmare.lib3270j.ecl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
|
||||
|
||||
/**
|
||||
* Manages the collection of all 3270 fields currently present in the Presentation Space.
|
||||
* Conforms to IBM ECL ECLFieldList specification.
|
||||
*/
|
||||
public class ECLFieldList {
|
||||
|
||||
private final ECLPS ps;
|
||||
private final ScreenBuffer screen;
|
||||
private final List<ECLField> fields = new ArrayList<>();
|
||||
|
||||
public ECLFieldList(ECLPS ps, ScreenBuffer screen) {
|
||||
this.ps = ps;
|
||||
this.screen = screen;
|
||||
refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the field list by scanning the presentation space buffer.
|
||||
*/
|
||||
public synchronized void refresh() {
|
||||
fields.clear();
|
||||
if (screen == null || !screen.isFormatted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int rows = screen.getRows();
|
||||
int cols = screen.getCols();
|
||||
int size = rows * cols;
|
||||
if (size <= 0) return;
|
||||
|
||||
// Collect all field attribute positions
|
||||
List<Integer> faPositions = new ArrayList<>();
|
||||
for (int i = 0; i < size; i++) {
|
||||
ExtendedAttribute ea = screen.getCell(i);
|
||||
if (ea.isFieldAttribute()) {
|
||||
faPositions.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (faPositions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int numFields = faPositions.size();
|
||||
for (int i = 0; i < numFields; i++) {
|
||||
int startPos = faPositions.get(i);
|
||||
int nextFaPos = faPositions.get((i + 1) % numFields);
|
||||
|
||||
int dataStart = (startPos + 1) % size;
|
||||
int endPos = (nextFaPos == 0) ? size - 1 : nextFaPos - 1;
|
||||
|
||||
int len;
|
||||
if (nextFaPos > startPos) {
|
||||
len = nextFaPos - startPos - 1;
|
||||
} else {
|
||||
len = (size - startPos - 1) + nextFaPos;
|
||||
}
|
||||
|
||||
byte faVal = screen.getCell(startPos).fa;
|
||||
fields.add(new ECLField(ps, startPos, dataStart, endPos, len, faVal));
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized int getFieldCount() {
|
||||
return fields.size();
|
||||
}
|
||||
|
||||
public synchronized List<ECLField> getFields() {
|
||||
return Collections.unmodifiableList(new ArrayList<>(fields));
|
||||
}
|
||||
|
||||
public synchronized ECLField getFirstField() {
|
||||
if (fields.isEmpty()) return null;
|
||||
return fields.get(0);
|
||||
}
|
||||
|
||||
public synchronized ECLField getNextField(ECLField prev) {
|
||||
if (prev == null || fields.isEmpty()) return getFirstField();
|
||||
int idx = fields.indexOf(prev);
|
||||
if (idx >= 0 && idx + 1 < fields.size()) {
|
||||
return fields.get(idx + 1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the field that contains the specified buffer position.
|
||||
*/
|
||||
public synchronized ECLField findField(int pos) {
|
||||
if (fields.isEmpty() || screen == null) return null;
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
if (size <= 0) return null;
|
||||
pos = ((pos % size) + size) % size;
|
||||
|
||||
for (ECLField f : fields) {
|
||||
int start = f.getStart();
|
||||
int end = f.getEnd();
|
||||
if (start <= end) {
|
||||
if (pos >= start && pos <= end) return f;
|
||||
} else {
|
||||
// Wrapped field
|
||||
if (pos >= start || pos <= end) return f;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the field at the specified 0-indexed row and column.
|
||||
*/
|
||||
public ECLField findField(int row, int col) {
|
||||
if (screen == null) return null;
|
||||
int cols = screen.getCols();
|
||||
return findField(row * cols + col);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find field containing the given text string.
|
||||
*/
|
||||
public synchronized ECLField findField(String text, int startPos) {
|
||||
if (text == null || text.isEmpty() || fields.isEmpty()) return null;
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
if (size <= 0) return null;
|
||||
startPos = ((startPos % size) + size) % size;
|
||||
|
||||
// Find starting index in field list
|
||||
int startIdx = 0;
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
if (fields.get(i).getStart() >= startPos) {
|
||||
startIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = startIdx; i < fields.size(); i++) {
|
||||
ECLField f = fields.get(i);
|
||||
if (f.getText().contains(text)) {
|
||||
return f;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package haus.nightmare.lib3270j.ecl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import haus.nightmare.lib3270j.input.InputProcessor;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
import haus.nightmare.lib3270j.telnet.TelnetFSM;
|
||||
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||
|
||||
/**
|
||||
* Operator Information Area (OIA) status engine matching IBM Host On-Demand ECL specification.
|
||||
* Provides synchronization primitives (waitForInput, waitForSysAvailable) and status inspection.
|
||||
*/
|
||||
public class ECLOIA implements ECLConstants {
|
||||
|
||||
private final ScreenBuffer screen;
|
||||
private final InputProcessor inputProcessor;
|
||||
private final TelnetFSM fsm;
|
||||
private final List<ECLOIANotify> listeners = new ArrayList<>();
|
||||
|
||||
public interface ECLOIANotify {
|
||||
void onOIAChanged(ECLOIA oia);
|
||||
}
|
||||
|
||||
public ECLOIA(ScreenBuffer screen, InputProcessor inputProcessor, TelnetFSM fsm) {
|
||||
this.screen = screen;
|
||||
this.inputProcessor = inputProcessor;
|
||||
this.fsm = fsm;
|
||||
|
||||
if (inputProcessor != null) {
|
||||
inputProcessor.setLockStateListener(locked -> notifyOIAChanged());
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void registerOIAEvent(ECLOIANotify listener) {
|
||||
if (listener != null && !listeners.contains(listener)) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void unregisterOIAEvent(ECLOIANotify listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
private synchronized void notifyOIAChanged() {
|
||||
for (ECLOIANotify l : listeners) {
|
||||
try {
|
||||
l.onOIAChanged(this);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInsertMode() {
|
||||
return inputProcessor != null && inputProcessor.isInsertMode();
|
||||
}
|
||||
|
||||
public boolean isNumeric() {
|
||||
if (screen == null || !screen.isFormatted()) return false;
|
||||
byte fa = screen.getFieldAttributeAt(screen.getCursorAddress());
|
||||
return faIsNumeric(fa & 0xFF);
|
||||
}
|
||||
|
||||
public boolean isAlphanumeric() {
|
||||
return !isNumeric();
|
||||
}
|
||||
|
||||
public boolean isMessageWaiting() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isCommError() {
|
||||
return fsm != null && fsm.getConnectionState() != null && !fsm.getConnectionState().isConnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current Input Inhibited code.
|
||||
* Returns one of INHIBIT_* constants from ECLConstants.
|
||||
*/
|
||||
public int getInputInhibited() {
|
||||
if (isCommError()) {
|
||||
return INHIBIT_COMM_CHECK;
|
||||
}
|
||||
if (inputProcessor != null && inputProcessor.isKeyboardLocked()) {
|
||||
return INHIBIT_SYSTEM_LOCK;
|
||||
}
|
||||
return INHIBIT_NOT_INHIBITED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Block until the keyboard becomes unlocked and ready for user input, or timeout expires.
|
||||
* @return true if keyboard unlocked, false if timeout occurred.
|
||||
*/
|
||||
public boolean waitForInput(long timeoutMs) {
|
||||
long start = System.currentTimeMillis();
|
||||
while (System.currentTimeMillis() - start < timeoutMs) {
|
||||
if (getInputInhibited() == INHIBIT_NOT_INHIBITED) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(20);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return getInputInhibited() == INHIBIT_NOT_INHIBITED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Block until the host system is available.
|
||||
*/
|
||||
public boolean waitForSysAvailable(long timeoutMs) {
|
||||
return waitForInput(timeoutMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Block until application is available.
|
||||
*/
|
||||
public boolean waitForAppAvailable(long timeoutMs) {
|
||||
return waitForInput(timeoutMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Block until any OIA transition occurs.
|
||||
*/
|
||||
public boolean waitForTransition(long timeoutMs) {
|
||||
int initialInhibit = getInputInhibited();
|
||||
long start = System.currentTimeMillis();
|
||||
while (System.currentTimeMillis() - start < timeoutMs) {
|
||||
if (getInputInhibited() != initialInhibit) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(20);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return getInputInhibited() != initialInhibit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
package haus.nightmare.lib3270j.ecl;
|
||||
|
||||
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||
import haus.nightmare.lib3270j.input.InputProcessor;
|
||||
import haus.nightmare.lib3270j.screen.ExtendedAttribute;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||
|
||||
/**
|
||||
* Presentation Space (ECLPS) implementation conforming to IBM Host On-Demand ECL specification.
|
||||
* Provides multi-plane presentation buffer access, string searches, formatted field navigation,
|
||||
* and automated keystroke streaming.
|
||||
*/
|
||||
public class ECLPS implements ECLConstants {
|
||||
|
||||
private final ScreenBuffer screen;
|
||||
private final InputProcessor inputProcessor;
|
||||
private final EbcdicTranslator translator;
|
||||
private final ECLFieldList fieldList;
|
||||
|
||||
public ECLPS(ScreenBuffer screen, InputProcessor inputProcessor, EbcdicTranslator translator) {
|
||||
this.screen = screen;
|
||||
this.inputProcessor = inputProcessor;
|
||||
this.translator = translator;
|
||||
this.fieldList = new ECLFieldList(this, screen);
|
||||
}
|
||||
|
||||
public ScreenBuffer getScreenBuffer() { return screen; }
|
||||
public InputProcessor getInputProcessor() { return inputProcessor; }
|
||||
public EbcdicTranslator getTranslator() { return translator; }
|
||||
public ECLFieldList getFieldList() {
|
||||
fieldList.refresh();
|
||||
return fieldList;
|
||||
}
|
||||
|
||||
public int getSize() { return screen.getRows() * screen.getCols(); }
|
||||
public int getRows() { return screen.getRows(); }
|
||||
public int getCols() { return screen.getCols(); }
|
||||
public int getCursorPos(){ return screen.getCursorAddress(); }
|
||||
public int getCursorRow(){ return screen.getCursorRow(); }
|
||||
public int getCursorCol(){ return screen.getCursorCol(); }
|
||||
|
||||
public void setCursorPos(int pos) {
|
||||
screen.setCursorAddress(pos);
|
||||
}
|
||||
|
||||
public void setCursorPos(int row, int col) {
|
||||
int pos = screen.rowColToAddress(row, col);
|
||||
screen.setCursorAddress(pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy presentation data from a specific plane into a destination buffer.
|
||||
*/
|
||||
public synchronized int getPlane(int planeType, char[] destBuffer, int start, int length) {
|
||||
if (destBuffer == null || screen == null || length <= 0) return 0;
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
if (size <= 0) return 0;
|
||||
|
||||
start = ((start % size) + size) % size;
|
||||
int copyLen = Math.min(length, destBuffer.length);
|
||||
|
||||
for (int i = 0; i < copyLen; i++) {
|
||||
int addr = (start + i) % size;
|
||||
ExtendedAttribute ea = screen.getCell(addr);
|
||||
switch (planeType) {
|
||||
case PLANE_TEXT:
|
||||
if (ea.isFieldAttribute()) {
|
||||
destBuffer[i] = ' ';
|
||||
} else if (ea.ucs4 != 0) {
|
||||
destBuffer[i] = (char) ea.ucs4;
|
||||
} else if (ea.ec != 0) {
|
||||
destBuffer[i] = translator.ebcdicToUnicode(ea.ec & 0xFF);
|
||||
} else {
|
||||
destBuffer[i] = ' ';
|
||||
}
|
||||
break;
|
||||
case PLANE_COLOR:
|
||||
destBuffer[i] = (char) (ea.fg & 0xFF);
|
||||
break;
|
||||
case PLANE_HILITE:
|
||||
destBuffer[i] = (char) (ea.gr & 0xFF);
|
||||
break;
|
||||
case PLANE_EXTENDED:
|
||||
destBuffer[i] = (char) (ea.cs & 0xFF);
|
||||
break;
|
||||
case PLANE_FIELD:
|
||||
destBuffer[i] = ea.isFieldAttribute() ? (char) (ea.fa & 0xFF) : 0;
|
||||
break;
|
||||
default:
|
||||
destBuffer[i] = ' ';
|
||||
break;
|
||||
}
|
||||
}
|
||||
return copyLen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string of characters from the presentation space starting at address pos.
|
||||
*/
|
||||
public synchronized String getString(int pos, int length) {
|
||||
if (length <= 0) return "";
|
||||
char[] buf = new char[length];
|
||||
getPlane(PLANE_TEXT, buf, pos, length);
|
||||
return new String(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string of characters from the presentation space starting at row/column.
|
||||
*/
|
||||
public String getString(int row, int col, int length) {
|
||||
int pos = screen.rowColToAddress(row, col);
|
||||
return getString(pos, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert text directly into unprotected fields in the presentation space starting at pos.
|
||||
*/
|
||||
public synchronized void setText(String text, int pos) {
|
||||
if (text == null || text.isEmpty() || screen == null) return;
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
if (size <= 0) return;
|
||||
|
||||
pos = ((pos % size) + size) % size;
|
||||
setCursorPos(pos);
|
||||
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char ch = text.charAt(i);
|
||||
inputProcessor.typeCharacter(ch);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert text starting at row and column.
|
||||
*/
|
||||
public void setText(String text, int row, int col) {
|
||||
int pos = screen.rowColToAddress(row, col);
|
||||
setText(text, pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for a string in the presentation space.
|
||||
* Returns 1-based or 0-based position, or -1 if not found.
|
||||
*/
|
||||
public synchronized int searchString(String target, int startRow, int startCol, int dir, boolean ignoreCase) {
|
||||
if (target == null || target.isEmpty() || screen == null) return -1;
|
||||
int rows = screen.getRows();
|
||||
int cols = screen.getCols();
|
||||
int size = rows * cols;
|
||||
if (size <= 0) return -1;
|
||||
|
||||
int startPos = (startRow * cols + startCol) % size;
|
||||
char[] fullScreen = new char[size];
|
||||
getPlane(PLANE_TEXT, fullScreen, 0, size);
|
||||
String screenText = new String(fullScreen);
|
||||
|
||||
if (ignoreCase) {
|
||||
screenText = screenText.toLowerCase();
|
||||
target = target.toLowerCase();
|
||||
}
|
||||
|
||||
int targetLen = target.length();
|
||||
if (dir == SEARCH_FORWARD) {
|
||||
// Forward search with wrapping
|
||||
for (int i = 0; i < size; i++) {
|
||||
int pos = (startPos + i) % size;
|
||||
if (matchesAt(screenText, target, pos, size)) {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Backward search with wrapping
|
||||
for (int i = 0; i < size; i++) {
|
||||
int pos = (startPos - i + size) % size;
|
||||
if (matchesAt(screenText, target, pos, size)) {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private boolean matchesAt(String screenText, String target, int pos, int size) {
|
||||
int len = target.length();
|
||||
for (int j = 0; j < len; j++) {
|
||||
int charPos = (pos + j) % size;
|
||||
if (screenText.charAt(charPos) != target.charAt(j)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Paste text with line wrapping across unprotected fields.
|
||||
*/
|
||||
public synchronized int pasteLineWrap(String text, int startPos, int endCol, boolean wordWrap) {
|
||||
if (text == null || text.isEmpty() || screen == null) return 0;
|
||||
int cols = screen.getCols();
|
||||
int rows = screen.getRows();
|
||||
int size = rows * cols;
|
||||
if (size <= 0) return 0;
|
||||
|
||||
setCursorPos(startPos);
|
||||
int charsPasted = 0;
|
||||
String[] lines = text.split("\r?\n");
|
||||
|
||||
for (int l = 0; l < lines.length; l++) {
|
||||
String line = lines[l];
|
||||
for (int i = 0; i < line.length(); i++) {
|
||||
int curPos = screen.getCursorAddress();
|
||||
int curCol = curPos % cols;
|
||||
if (endCol > 0 && curCol >= endCol) {
|
||||
// Advance to next row
|
||||
int nextRow = (curPos / cols + 1) % rows;
|
||||
setCursorPos(nextRow * cols);
|
||||
}
|
||||
inputProcessor.typeCharacter(line.charAt(i));
|
||||
charsPasted++;
|
||||
}
|
||||
if (l < lines.length - 1) {
|
||||
// Newline key between lines
|
||||
inputProcessor.newline();
|
||||
}
|
||||
}
|
||||
return charsPasted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send standard IBM ECL mnemonic keystrokes.
|
||||
*/
|
||||
public void sendKeys(String keys) {
|
||||
if (inputProcessor != null) {
|
||||
inputProcessor.sendKeys(keys);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -863,6 +863,204 @@ public class InputProcessor {
|
||||
return fieldLen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and execute IBM ECL standard mnemonic keystrokes (e.g. "USER[tab]PASS[enter]").
|
||||
*/
|
||||
public void sendKeys(String keys) {
|
||||
if (keys == null || keys.isEmpty()) return;
|
||||
|
||||
int len = keys.length();
|
||||
int i = 0;
|
||||
while (i < len) {
|
||||
char c = keys.charAt(i);
|
||||
if (c == '[') {
|
||||
int end = keys.indexOf(']', i);
|
||||
if (end > i) {
|
||||
String token = keys.substring(i + 1, end).trim().toLowerCase();
|
||||
executeMnemonicToken(token);
|
||||
i = end + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
typeCharacter(c);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
private void executeMnemonicToken(String token) {
|
||||
switch (token) {
|
||||
case "enter":
|
||||
case "return":
|
||||
sendAid(AID_ENTER);
|
||||
break;
|
||||
case "clear":
|
||||
sendAid(AID_CLEAR);
|
||||
break;
|
||||
case "tab":
|
||||
tab();
|
||||
break;
|
||||
case "backtab":
|
||||
case "btab":
|
||||
backTab();
|
||||
break;
|
||||
case "newline":
|
||||
case "nl":
|
||||
newline();
|
||||
break;
|
||||
case "home":
|
||||
cursorHome();
|
||||
break;
|
||||
case "up":
|
||||
case "curup":
|
||||
cursorUp();
|
||||
break;
|
||||
case "down":
|
||||
case "curdown":
|
||||
cursorDown();
|
||||
break;
|
||||
case "left":
|
||||
case "curleft":
|
||||
cursorLeft();
|
||||
break;
|
||||
case "right":
|
||||
case "curright":
|
||||
cursorRight();
|
||||
break;
|
||||
case "eraseeof":
|
||||
case "erase_eof":
|
||||
eraseEof();
|
||||
break;
|
||||
case "eraseinp":
|
||||
case "erase_input":
|
||||
eraseInput();
|
||||
break;
|
||||
case "dup":
|
||||
dup();
|
||||
break;
|
||||
case "fm":
|
||||
case "fieldmark":
|
||||
fieldMark();
|
||||
break;
|
||||
case "attn":
|
||||
attn();
|
||||
break;
|
||||
case "sysreq":
|
||||
sysReq();
|
||||
break;
|
||||
case "reset":
|
||||
reset();
|
||||
break;
|
||||
case "insert":
|
||||
setInsertMode(!isInsertMode());
|
||||
break;
|
||||
case "delete":
|
||||
deleteChar();
|
||||
break;
|
||||
case "backspace":
|
||||
case "bs":
|
||||
backspace();
|
||||
break;
|
||||
case "wordtab":
|
||||
processWordTab(true);
|
||||
break;
|
||||
case "wordbacktab":
|
||||
processWordTab(false);
|
||||
break;
|
||||
case "deleteword":
|
||||
processDeleteWord();
|
||||
break;
|
||||
default:
|
||||
if (token.startsWith("pf")) {
|
||||
try {
|
||||
int pfNum = Integer.parseInt(token.substring(2));
|
||||
if (pfNum >= 1 && pfNum <= 24) {
|
||||
sendAid(AID_PF1 + (pfNum - 1));
|
||||
}
|
||||
} catch (NumberFormatException ignored) {}
|
||||
} else if (token.startsWith("pa")) {
|
||||
try {
|
||||
int paNum = Integer.parseInt(token.substring(2));
|
||||
if (paNum >= 1 && paNum <= 3) {
|
||||
sendAid(AID_PA1 + (paNum - 1));
|
||||
}
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Jump cursor to next or previous word boundary.
|
||||
*/
|
||||
public void processWordTab(boolean forward) {
|
||||
if (keyboardLocked || screen == null) return;
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
if (size <= 0) return;
|
||||
|
||||
int cur = screen.getCursorAddress();
|
||||
if (forward) {
|
||||
// Find next whitespace then next non-whitespace
|
||||
int pos = screen.incrementAddress(cur);
|
||||
int count = 0;
|
||||
while (count < size && !isWhitespaceOrNull(pos)) {
|
||||
pos = screen.incrementAddress(pos);
|
||||
count++;
|
||||
}
|
||||
while (count < size && isWhitespaceOrNull(pos)) {
|
||||
pos = screen.incrementAddress(pos);
|
||||
count++;
|
||||
}
|
||||
screen.setCursorAddress(pos);
|
||||
} else {
|
||||
// Find previous non-whitespace after whitespace
|
||||
int pos = screen.decrementAddress(cur);
|
||||
int count = 0;
|
||||
while (count < size && isWhitespaceOrNull(pos)) {
|
||||
pos = screen.decrementAddress(pos);
|
||||
count++;
|
||||
}
|
||||
while (count < size && !isWhitespaceOrNull(screen.decrementAddress(pos))) {
|
||||
pos = screen.decrementAddress(pos);
|
||||
count++;
|
||||
}
|
||||
screen.setCursorAddress(pos);
|
||||
}
|
||||
screen.markAllChanged();
|
||||
screen.updateDisplaySnapshot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the word starting at the cursor position.
|
||||
*/
|
||||
public void processDeleteWord() {
|
||||
if (keyboardLocked || screen == null) return;
|
||||
int cur = screen.getCursorAddress();
|
||||
if (screen.isFormatted()) {
|
||||
byte fa = screen.getFieldAttributeAt(cur);
|
||||
if (faIsProtected(fa & 0xFF)) return;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
int size = screen.getRows() * screen.getCols();
|
||||
while (count < size && !isWhitespaceOrNull(screen.getCursorAddress())) {
|
||||
deleteChar();
|
||||
count++;
|
||||
}
|
||||
// Also delete trailing spaces
|
||||
while (count < size && isWhitespaceOrNull(screen.getCursorAddress()) && screen.getCell(screen.getCursorAddress()).ec != 0) {
|
||||
deleteChar();
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isWhitespaceOrNull(int pos) {
|
||||
if (screen == null) return true;
|
||||
ExtendedAttribute ea = screen.getCell(pos);
|
||||
if (ea.isFieldAttribute()) return true;
|
||||
int ec = ea.ec & 0xFF;
|
||||
return ec == 0 || ec == 0x40; // Null or EBCDIC Space
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a structured field response directly.
|
||||
* Used by DFT mode file transfer.
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package haus.nightmare.lib3270j.ecl;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import haus.nightmare.lib3270j.TerminalModel;
|
||||
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||
import haus.nightmare.lib3270j.input.InputProcessor;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||
|
||||
public class ECLFieldListTest {
|
||||
|
||||
private ScreenBuffer screen;
|
||||
private EbcdicTranslator translator;
|
||||
private InputProcessor input;
|
||||
private ECLPS ps;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
translator = new EbcdicTranslator();
|
||||
screen = new ScreenBuffer(TerminalModel.IBM_3278_2, translator);
|
||||
input = new InputProcessor(screen, translator, null);
|
||||
ps = new ECLPS(screen, input, translator);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFieldListDetection() {
|
||||
// Create 2 fields: pos 0 (unprotected, 9 chars), pos 10 (protected, 19 chars), pos 30 (high intensity)
|
||||
screen.setFieldAttribute(0, (byte) FA_PRINTABLE);
|
||||
screen.setFieldAttribute(10, (byte) (FA_PRINTABLE | FA_PROTECT));
|
||||
screen.setFieldAttribute(30, (byte) (FA_PRINTABLE | FA_INT_HIGH_SEL));
|
||||
|
||||
ECLFieldList list = ps.getFieldList();
|
||||
assertEquals(3, list.getFieldCount());
|
||||
|
||||
ECLField f1 = list.getFirstField();
|
||||
assertNotNull(f1);
|
||||
assertEquals(0, f1.getStart());
|
||||
assertEquals(1, f1.getDataStart());
|
||||
assertEquals(9, f1.getEnd());
|
||||
assertEquals(9, f1.getLength());
|
||||
assertFalse(f1.isProtected());
|
||||
|
||||
ECLField f2 = list.getNextField(f1);
|
||||
assertNotNull(f2);
|
||||
assertEquals(10, f2.getStart());
|
||||
assertEquals(11, f2.getDataStart());
|
||||
assertEquals(29, f2.getEnd());
|
||||
assertEquals(19, f2.getLength());
|
||||
assertTrue(f2.isProtected());
|
||||
|
||||
ECLField f3 = list.getNextField(f2);
|
||||
assertNotNull(f3);
|
||||
assertEquals(30, f3.getStart());
|
||||
assertTrue(f3.isHighIntensity());
|
||||
assertTrue(f3.isPenSelectable());
|
||||
|
||||
assertNull(list.getNextField(f3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindFieldByPositionAndRowCol() {
|
||||
screen.setFieldAttribute(0, (byte) FA_PRINTABLE);
|
||||
screen.setFieldAttribute(80, (byte) (FA_PRINTABLE | FA_PROTECT));
|
||||
|
||||
ECLFieldList list = ps.getFieldList();
|
||||
ECLField found1 = list.findField(40);
|
||||
assertNotNull(found1);
|
||||
assertEquals(0, found1.getStart());
|
||||
|
||||
ECLField found2 = list.findField(1, 10); // Row 1, Col 10 -> addr 90
|
||||
assertNotNull(found2);
|
||||
assertEquals(80, found2.getStart());
|
||||
assertTrue(found2.isProtected());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLightpenSelectionOnField() {
|
||||
screen.setFieldAttribute(0, (byte) (FA_PRINTABLE | FA_INT_HIGH_SEL));
|
||||
screen.setCell(1, translator.unicodeToEbcdic('?'));
|
||||
screen.setCell(2, translator.unicodeToEbcdic('O'));
|
||||
screen.setCell(3, translator.unicodeToEbcdic('K'));
|
||||
screen.setFieldAttribute(10, (byte) (FA_PRINTABLE | FA_PROTECT));
|
||||
screen.translateToUnicode();
|
||||
|
||||
ECLField field = ps.getFieldList().getFirstField();
|
||||
assertEquals('?', field.getSelectorPenType());
|
||||
|
||||
field.selectField();
|
||||
assertEquals('>', field.getSelectorPenType());
|
||||
|
||||
field.deSelectField();
|
||||
assertEquals('?', field.getSelectorPenType());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package haus.nightmare.lib3270j.ecl;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import haus.nightmare.lib3270j.TerminalModel;
|
||||
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||
import haus.nightmare.lib3270j.input.InputProcessor;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
import static haus.nightmare.lib3270j.ecl.ECLConstants.*;
|
||||
|
||||
public class ECLOIATest {
|
||||
|
||||
private ScreenBuffer screen;
|
||||
private EbcdicTranslator translator;
|
||||
private InputProcessor input;
|
||||
private ECLOIA oia;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
translator = new EbcdicTranslator();
|
||||
screen = new ScreenBuffer(TerminalModel.IBM_3278_2, translator);
|
||||
input = new InputProcessor(screen, translator, null);
|
||||
oia = new ECLOIA(screen, input, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInhibitStateTransitions() {
|
||||
assertEquals(INHIBIT_NOT_INHIBITED, oia.getInputInhibited());
|
||||
assertTrue(oia.waitForInput(100));
|
||||
|
||||
input.setKeyboardLocked(true);
|
||||
assertEquals(INHIBIT_SYSTEM_LOCK, oia.getInputInhibited());
|
||||
assertFalse(oia.waitForInput(50));
|
||||
|
||||
input.setKeyboardLocked(false);
|
||||
assertEquals(INHIBIT_NOT_INHIBITED, oia.getInputInhibited());
|
||||
assertTrue(oia.waitForInput(100));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOIAEventListener() {
|
||||
AtomicBoolean triggered = new AtomicBoolean(false);
|
||||
oia.registerOIAEvent(changedOia -> triggered.set(true));
|
||||
|
||||
input.setKeyboardLocked(true);
|
||||
assertTrue(triggered.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertModeState() {
|
||||
assertFalse(oia.isInsertMode());
|
||||
input.setInsertMode(true);
|
||||
assertTrue(oia.isInsertMode());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package haus.nightmare.lib3270j.ecl;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import haus.nightmare.lib3270j.TerminalModel;
|
||||
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||
import haus.nightmare.lib3270j.input.InputProcessor;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
import haus.nightmare.lib3270j.telnet.TelnetFSM;
|
||||
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||
|
||||
public class ECLPSTest implements ECLConstants {
|
||||
|
||||
private ScreenBuffer screen;
|
||||
private EbcdicTranslator translator;
|
||||
private InputProcessor input;
|
||||
private ECLPS ps;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
translator = new EbcdicTranslator();
|
||||
screen = new ScreenBuffer(TerminalModel.IBM_3278_2, translator);
|
||||
input = new InputProcessor(screen, translator, null);
|
||||
ps = new ECLPS(screen, input, translator);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGeometry() {
|
||||
assertEquals(24, ps.getRows());
|
||||
assertEquals(80, ps.getCols());
|
||||
assertEquals(24 * 80, ps.getSize());
|
||||
assertEquals(0, ps.getCursorPos());
|
||||
|
||||
ps.setCursorPos(10, 20);
|
||||
assertEquals(10 * 80 + 20, ps.getCursorPos());
|
||||
assertEquals(10, ps.getCursorRow());
|
||||
assertEquals(20, ps.getCursorCol());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlaneTextExtractionAndSearch() {
|
||||
// Set some text in buffer
|
||||
screen.setCell(0, 0xC8); // 'H'
|
||||
screen.setCell(1, 0xC5); // 'E'
|
||||
screen.setCell(2, 0xD3); // 'L'
|
||||
screen.setCell(3, 0xD3); // 'L'
|
||||
screen.setCell(4, 0xD6); // 'O'
|
||||
screen.translateToUnicode();
|
||||
|
||||
assertEquals("HELLO", ps.getString(0, 5));
|
||||
|
||||
// Search string forward
|
||||
int pos = ps.searchString("ELL", 0, 0, SEARCH_FORWARD, false);
|
||||
assertEquals(1, pos);
|
||||
|
||||
// Search string case-insensitive
|
||||
int posLower = ps.searchString("hello", 0, 0, SEARCH_FORWARD, true);
|
||||
assertEquals(0, posLower);
|
||||
|
||||
// Search not found
|
||||
int notFound = ps.searchString("WORLD", 0, 0, SEARCH_FORWARD, false);
|
||||
assertEquals(-1, notFound);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiPlaneReading() {
|
||||
screen.setFieldAttribute(0, (byte) (FA_PRINTABLE | FA_PROTECT));
|
||||
screen.setCell(1, 0xC1); // 'A'
|
||||
screen.getCell(1).fg = COLOR_RED;
|
||||
screen.getCell(1).gr = (byte) HILITE_BLINK;
|
||||
|
||||
char[] textPlane = new char[2];
|
||||
char[] colorPlane = new char[2];
|
||||
char[] hilitePlane = new char[2];
|
||||
char[] fieldPlane = new char[2];
|
||||
|
||||
ps.getPlane(PLANE_TEXT, textPlane, 0, 2);
|
||||
ps.getPlane(PLANE_COLOR, colorPlane, 0, 2);
|
||||
ps.getPlane(PLANE_HILITE, hilitePlane, 0, 2);
|
||||
ps.getPlane(PLANE_FIELD, fieldPlane, 0, 2);
|
||||
|
||||
assertEquals(' ', textPlane[0]); // FA is space in text plane
|
||||
assertEquals('A', textPlane[1]);
|
||||
|
||||
assertEquals(0, colorPlane[0]);
|
||||
assertEquals(COLOR_RED, colorPlane[1]);
|
||||
|
||||
assertEquals(0, hilitePlane[0]);
|
||||
assertEquals((char) (HILITE_BLINK & 0xFF), hilitePlane[1]);
|
||||
|
||||
assertTrue((fieldPlane[0] & 0xFF) != 0);
|
||||
assertEquals(0, fieldPlane[1]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendKeysMnemonics() {
|
||||
// Create an unprotected field at pos 0
|
||||
screen.setFieldAttribute(0, (byte) FA_PRINTABLE);
|
||||
screen.setCursorAddress(1);
|
||||
|
||||
ps.sendKeys("IBM[tab]");
|
||||
assertEquals('I', screen.getCell(1).ucs4);
|
||||
assertEquals('B', screen.getCell(2).ucs4);
|
||||
assertEquals('M', screen.getCell(3).ucs4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPasteLineWrap() {
|
||||
screen.setFieldAttribute(0, (byte) FA_PRINTABLE);
|
||||
screen.setCursorAddress(1);
|
||||
|
||||
int count = ps.pasteLineWrap("ABC\nDEF", 1, 80, false);
|
||||
assertEquals(6, count);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user