Overnight churnings
This commit is contained in:
@@ -145,12 +145,12 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
|
||||
// 3. View menu
|
||||
JMenu viewMenu = createMenu("View");
|
||||
viewMenu.add(createMenuItem("Font Size +", KeyEvent.VK_EQUALS, () -> adjustFontSize(2)));
|
||||
viewMenu.add(createMenuItem("Font Size -", KeyEvent.VK_MINUS, () -> adjustFontSize(-2)));
|
||||
viewMenu.add(createMenuItem("Font Size +", KeyEvent.VK_EQUALS, () -> adjustFontSize(2), true));
|
||||
viewMenu.add(createMenuItem("Font Size -", KeyEvent.VK_MINUS, () -> adjustFontSize(-2), true));
|
||||
viewMenu.add(createMenuItem("Reset Font", KeyEvent.VK_0, () -> {
|
||||
terminalPanel.setFontSize(16);
|
||||
terminalPanel.guardedPack();
|
||||
}));
|
||||
}, true));
|
||||
viewMenu.addSeparator();
|
||||
|
||||
// UI Theme Submenu
|
||||
@@ -234,17 +234,17 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
client.sendEnter();
|
||||
terminalPanel.repaint();
|
||||
}
|
||||
}));
|
||||
}, true));
|
||||
actionsMenu.add(createMenuItem("Clear", KeyEvent.VK_K, () -> {
|
||||
if (client != null)
|
||||
client.sendClear();
|
||||
terminalPanel.repaint();
|
||||
}));
|
||||
}, true));
|
||||
actionsMenu.add(createMenuItem("Reset", KeyEvent.VK_R, () -> {
|
||||
if (client != null)
|
||||
client.reset();
|
||||
terminalPanel.repaint();
|
||||
}));
|
||||
}, true));
|
||||
actionsMenu.add(createMenuItem("Erase Input", KeyEvent.VK_E, () -> {
|
||||
if (client != null && client.getConnectionState().isFullSession()) {
|
||||
client.eraseInput();
|
||||
@@ -272,7 +272,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
}, true));
|
||||
actionsMenu.addSeparator();
|
||||
actionsMenu.add(createMenuItem("Run Keystrokes / Script...", -1, this::showScriptDialog));
|
||||
actionsMenu.add(createMenuItem("File Transfer...", KeyEvent.VK_T, this::showFileTransferDialog));
|
||||
actionsMenu.add(createMenuItem("File Transfer...", KeyEvent.VK_T, this::showFileTransferDialog, true));
|
||||
menuBar.add(actionsMenu);
|
||||
|
||||
// 5. Help menu
|
||||
@@ -706,18 +706,18 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
||||
"Insert — Toggle insert mode\n" +
|
||||
"Escape — Reset\n" +
|
||||
"PageUp/Down — PF7/PF8\n" +
|
||||
"Alt+C / Ctrl+K — Clear\n" +
|
||||
"Alt+C / Alt+K — Clear\n" +
|
||||
"Alt+E — Erase Input\n" +
|
||||
"Alt+A — Attention\n" +
|
||||
"Alt+S — System Request\n" +
|
||||
"Alt+Q — Cursor Select\n" +
|
||||
"Alt+L — Toggle Light Pen\n" +
|
||||
"Alt+T — File Transfer\n" +
|
||||
"Alt+=/-/0 — Font size +/-/reset\n" +
|
||||
"Cmd/Ctrl+F — Find on Screen\n" +
|
||||
"Cmd/Ctrl+G / F3— Find Next\n" +
|
||||
"Cmd/Ctrl+T — File Transfer\n" +
|
||||
"Cmd/Ctrl+D — Disconnect\n" +
|
||||
"Cmd/Ctrl+Q — Quit\n" +
|
||||
"Cmd/Ctrl+=/-/0 — Font size +/-/reset";
|
||||
"Cmd/Ctrl+Q — Quit";
|
||||
|
||||
JTextArea area = new JTextArea(text);
|
||||
area.setEditable(false);
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package haus.nightmare.j3270.ui;
|
||||
|
||||
import haus.nightmare.j3270.J3270App;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class MenuBarShortcutsTest {
|
||||
|
||||
private Map<String, JMenuItem> collectMenuItems(JMenu menu) {
|
||||
Map<String, JMenuItem> map = new HashMap<>();
|
||||
for (int i = 0; i < menu.getItemCount(); i++) {
|
||||
JMenuItem item = menu.getItem(i);
|
||||
if (item != null) {
|
||||
map.put(item.getText(), item);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testActionAndViewShortcutsUseAlt() {
|
||||
J3270App app = new J3270App();
|
||||
JMenuBar mb = app.getJMenuBar();
|
||||
assertNotNull(mb, "JMenuBar should be present");
|
||||
|
||||
JMenu viewMenu = null;
|
||||
JMenu actionsMenu = null;
|
||||
JMenu fileMenu = null;
|
||||
JMenu editMenu = null;
|
||||
|
||||
for (int i = 0; i < mb.getMenuCount(); i++) {
|
||||
JMenu m = mb.getMenu(i);
|
||||
if (m != null) {
|
||||
if ("View".equals(m.getText())) viewMenu = m;
|
||||
else if ("Actions".equals(m.getText())) actionsMenu = m;
|
||||
else if ("File".equals(m.getText())) fileMenu = m;
|
||||
else if ("Edit".equals(m.getText())) editMenu = m;
|
||||
}
|
||||
}
|
||||
|
||||
assertNotNull(viewMenu, "View menu should exist");
|
||||
assertNotNull(actionsMenu, "Actions menu should exist");
|
||||
assertNotNull(fileMenu, "File menu should exist");
|
||||
assertNotNull(editMenu, "Edit menu should exist");
|
||||
|
||||
// Verify View Menu items use ALT_DOWN_MASK
|
||||
Map<String, JMenuItem> viewItems = collectMenuItems(viewMenu);
|
||||
assertAcceleratorUsesAlt(viewItems.get("Font Size +"), KeyEvent.VK_EQUALS);
|
||||
assertAcceleratorUsesAlt(viewItems.get("Font Size -"), KeyEvent.VK_MINUS);
|
||||
assertAcceleratorUsesAlt(viewItems.get("Reset Font"), KeyEvent.VK_0);
|
||||
|
||||
// Verify Actions Menu items use ALT_DOWN_MASK
|
||||
Map<String, JMenuItem> actionItems = collectMenuItems(actionsMenu);
|
||||
assertAcceleratorUsesAlt(actionItems.get("Send Enter"), KeyEvent.VK_ENTER);
|
||||
assertAcceleratorUsesAlt(actionItems.get("Clear"), KeyEvent.VK_K);
|
||||
assertAcceleratorUsesAlt(actionItems.get("Reset"), KeyEvent.VK_R);
|
||||
assertAcceleratorUsesAlt(actionItems.get("Erase Input"), KeyEvent.VK_E);
|
||||
assertAcceleratorUsesAlt(actionItems.get("Attention"), KeyEvent.VK_A);
|
||||
assertAcceleratorUsesAlt(actionItems.get("System Request"), KeyEvent.VK_S);
|
||||
assertAcceleratorUsesAlt(actionItems.get("Cursor Select"), KeyEvent.VK_Q);
|
||||
assertAcceleratorUsesAlt(actionItems.get("Toggle Light Pen (Alt+L)"), KeyEvent.VK_L);
|
||||
assertAcceleratorUsesAlt(actionItems.get("File Transfer..."), KeyEvent.VK_T);
|
||||
|
||||
// Dispose frame
|
||||
app.dispose();
|
||||
}
|
||||
|
||||
private void assertAcceleratorUsesAlt(JMenuItem item, int expectedKeyCode) {
|
||||
assertNotNull(item, "Menu item must exist");
|
||||
KeyStroke ks = item.getAccelerator();
|
||||
assertNotNull(ks, "Menu item " + item.getText() + " should have an accelerator");
|
||||
assertEquals(expectedKeyCode, ks.getKeyCode(), "Key code mismatch for " + item.getText());
|
||||
int mods = ks.getModifiers();
|
||||
assertTrue((mods & (KeyEvent.ALT_DOWN_MASK | KeyEvent.ALT_MASK)) != 0,
|
||||
"Menu item " + item.getText() + " accelerator must have ALT modifier");
|
||||
assertEquals(0, mods & (KeyEvent.CTRL_DOWN_MASK | KeyEvent.CTRL_MASK | KeyEvent.META_DOWN_MASK | KeyEvent.META_MASK),
|
||||
"Menu item " + item.getText() + " accelerator must NOT have CTRL or META modifiers");
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,8 @@ public interface ECLConstants {
|
||||
// Search directions
|
||||
int SEARCH_FORWARD = 1;
|
||||
int SEARCH_BACKWARD = 2;
|
||||
int DIR_FORWARD = 1;
|
||||
int DIR_BACKWARD = 2;
|
||||
|
||||
// OIA Input Inhibited Reason Codes
|
||||
int INHIBIT_NOT_INHIBITED = 0;
|
||||
|
||||
@@ -28,35 +28,43 @@ public class ECLField {
|
||||
|
||||
/** Buffer address of the field attribute character. */
|
||||
public int getStart() { return startPos; }
|
||||
public int GetStart() { return getStart(); }
|
||||
|
||||
/** First buffer address of the field data (start + 1). */
|
||||
public int getDataStart() { return dataStart; }
|
||||
public int GetDataStart() { return getDataStart(); }
|
||||
|
||||
/** Last buffer address of the field data inclusive. */
|
||||
public int getEnd() { return endPos; }
|
||||
public int GetEnd() { return getEnd(); }
|
||||
|
||||
/** Number of data characters in the field. */
|
||||
public int getLength() { return length; }
|
||||
public int GetLength() { return getLength(); }
|
||||
|
||||
public int getStartRow() {
|
||||
int cols = ps.getCols();
|
||||
return cols > 0 ? startPos / cols : 0;
|
||||
}
|
||||
public int GetStartRow() { return getStartRow(); }
|
||||
|
||||
public int getStartCol() {
|
||||
int cols = ps.getCols();
|
||||
return cols > 0 ? startPos % cols : 0;
|
||||
}
|
||||
public int GetStartCol() { return getStartCol(); }
|
||||
|
||||
public int getEndRow() {
|
||||
int cols = ps.getCols();
|
||||
return cols > 0 ? endPos / cols : 0;
|
||||
}
|
||||
public int GetEndRow() { return getEndRow(); }
|
||||
|
||||
public int getEndCol() {
|
||||
int cols = ps.getCols();
|
||||
return cols > 0 ? endPos % cols : 0;
|
||||
}
|
||||
public int GetEndCol() { return getEndCol(); }
|
||||
|
||||
private byte getLiveAttribute() {
|
||||
if (ps != null && ps.getScreenBuffer() != null) {
|
||||
@@ -71,34 +79,42 @@ public class ECLField {
|
||||
public boolean isModified() {
|
||||
return faIsModified(getLiveAttribute() & 0xFF);
|
||||
}
|
||||
public boolean IsModified() { return isModified(); }
|
||||
|
||||
public boolean isProtected() {
|
||||
return faIsProtected(getLiveAttribute() & 0xFF);
|
||||
}
|
||||
public boolean IsProtected() { return isProtected(); }
|
||||
|
||||
public boolean isNumeric() {
|
||||
return faIsNumeric(getLiveAttribute() & 0xFF);
|
||||
}
|
||||
public boolean IsNumeric() { return isNumeric(); }
|
||||
|
||||
public boolean isHighIntensity() {
|
||||
return faIsHigh(getLiveAttribute() & 0xFF);
|
||||
}
|
||||
public boolean IsHighIntensity() { return isHighIntensity(); }
|
||||
|
||||
public boolean isHidden() {
|
||||
return faIsZero(getLiveAttribute() & 0xFF);
|
||||
}
|
||||
public boolean IsHidden() { return isHidden(); }
|
||||
|
||||
public boolean isDisplay() {
|
||||
return !isHidden();
|
||||
}
|
||||
public boolean IsDisplay() { return isDisplay(); }
|
||||
|
||||
public boolean isPenSelectable() {
|
||||
return faIsSelectable(getLiveAttribute() & 0xFF);
|
||||
}
|
||||
public boolean IsPenSelectable() { return isPenSelectable(); }
|
||||
|
||||
public short getAttribute() {
|
||||
return (short) (getLiveAttribute() & 0xFF);
|
||||
}
|
||||
public short GetAttribute() { return getAttribute(); }
|
||||
|
||||
/**
|
||||
* Get the text contents of this field as a String.
|
||||
@@ -107,6 +123,7 @@ public class ECLField {
|
||||
if (length <= 0) return "";
|
||||
return ps.getString(dataStart, length);
|
||||
}
|
||||
public String GetText() { return getText(); }
|
||||
|
||||
/**
|
||||
* Set the text contents of this field.
|
||||
@@ -115,6 +132,7 @@ public class ECLField {
|
||||
if (isProtected() || length <= 0) return;
|
||||
ps.setText(text, dataStart);
|
||||
}
|
||||
public void SetText(String text) { setText(text); }
|
||||
|
||||
/**
|
||||
* Get selector light pen type.
|
||||
@@ -129,6 +147,7 @@ public class ECLField {
|
||||
}
|
||||
return ' ';
|
||||
}
|
||||
public char GetSelectorPenType() { return getSelectorPenType(); }
|
||||
|
||||
/**
|
||||
* Actuate lightpen selection on this field ('?' -> '>').
|
||||
@@ -140,6 +159,7 @@ public class ECLField {
|
||||
setText(">" + (t.length() > 1 ? t.substring(1) : ""));
|
||||
}
|
||||
}
|
||||
public void SelectField() { selectField(); }
|
||||
|
||||
/**
|
||||
* Deselect lightpen selection on this field ('>' -> '?').
|
||||
@@ -151,16 +171,19 @@ public class ECLField {
|
||||
setText("?" + (t.length() > 1 ? t.substring(1) : ""));
|
||||
}
|
||||
}
|
||||
public void DeSelectField() { deSelectField(); }
|
||||
|
||||
/** Return true if this field wraps from bottom of screen to top. */
|
||||
public boolean isWrapped() {
|
||||
return startPos > endPos;
|
||||
}
|
||||
public boolean IsWrapped() { return isWrapped(); }
|
||||
|
||||
/** Last data buffer address (same as getEnd). */
|
||||
public int getDataEnd() {
|
||||
return endPos;
|
||||
}
|
||||
public int GetDataEnd() { return getDataEnd(); }
|
||||
|
||||
/**
|
||||
* Check if the specified buffer address is contained within this field (including its FA).
|
||||
@@ -178,6 +201,7 @@ public class ECLField {
|
||||
return pos >= startPos || pos <= endPos;
|
||||
}
|
||||
}
|
||||
public boolean Contains(int pos) { return contains(pos); }
|
||||
|
||||
/**
|
||||
* Check if the specified row and column is contained within this field.
|
||||
@@ -187,6 +211,7 @@ public class ECLField {
|
||||
int cols = ps.getCols();
|
||||
return contains(row * cols + col);
|
||||
}
|
||||
public boolean Contains(int row, int col) { return contains(row, col); }
|
||||
|
||||
/**
|
||||
* Set the Modified Data Tag (MDT) for this field.
|
||||
@@ -205,6 +230,7 @@ public class ECLField {
|
||||
}
|
||||
}
|
||||
}
|
||||
public void SetModified(boolean modified) { setModified(modified); }
|
||||
|
||||
/**
|
||||
* Erase all character data within this field to nulls.
|
||||
@@ -225,6 +251,20 @@ public class ECLField {
|
||||
sb.markAllChanged();
|
||||
sb.updateDisplaySnapshot();
|
||||
}
|
||||
public void Erase() { erase(); }
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof ECLField)) return false;
|
||||
ECLField other = (ECLField) o;
|
||||
return this.startPos == other.startPos && this.endPos == other.endPos && this.length == other.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return java.util.Objects.hash(startPos, endPos, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
@@ -70,27 +70,38 @@ public class ECLFieldList {
|
||||
}
|
||||
}
|
||||
|
||||
public void Refresh() { refresh(); }
|
||||
|
||||
public synchronized int getFieldCount() {
|
||||
return fields.size();
|
||||
}
|
||||
public int GetFieldCount() { return getFieldCount(); }
|
||||
|
||||
public synchronized List<ECLField> getFields() {
|
||||
return Collections.unmodifiableList(new ArrayList<>(fields));
|
||||
}
|
||||
public List<ECLField> GetFields() { return getFields(); }
|
||||
|
||||
public synchronized ECLField getFirstField() {
|
||||
if (fields.isEmpty()) return null;
|
||||
return fields.get(0);
|
||||
}
|
||||
public ECLField GetFirstField() { return getFirstField(); }
|
||||
|
||||
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);
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
ECLField f = fields.get(i);
|
||||
if (f.equals(prev) || f.getStart() == prev.getStart()) {
|
||||
if (i + 1 < fields.size()) {
|
||||
return fields.get(i + 1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public ECLField GetNextField(ECLField prev) { return getNextField(prev); }
|
||||
|
||||
/**
|
||||
* Find the field that contains the specified buffer position.
|
||||
@@ -113,6 +124,7 @@ public class ECLFieldList {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public ECLField FindField(int pos) { return findField(pos); }
|
||||
|
||||
/**
|
||||
* Find the field at the specified 0-indexed row and column.
|
||||
@@ -122,20 +134,26 @@ public class ECLFieldList {
|
||||
int cols = screen.getCols();
|
||||
return findField(row * cols + col);
|
||||
}
|
||||
public ECLField FindField(int row, int col) { return findField(row, col); }
|
||||
|
||||
/**
|
||||
* Get the field preceding the given field in the field list.
|
||||
*/
|
||||
public synchronized ECLField getPreviousField(ECLField next) {
|
||||
if (next == null || fields.isEmpty()) return null;
|
||||
int idx = fields.indexOf(next);
|
||||
if (idx > 0) {
|
||||
return fields.get(idx - 1);
|
||||
} else if (idx == 0) {
|
||||
for (int i = 0; i < fields.size(); i++) {
|
||||
ECLField f = fields.get(i);
|
||||
if (f.equals(next) || f.getStart() == next.getStart()) {
|
||||
if (i > 0) {
|
||||
return fields.get(i - 1);
|
||||
} else {
|
||||
return fields.get(fields.size() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public ECLField GetPreviousField(ECLField next) { return getPreviousField(next); }
|
||||
|
||||
/**
|
||||
* Find the field at the given buffer position (alias for findField).
|
||||
@@ -143,6 +161,7 @@ public class ECLFieldList {
|
||||
public ECLField findFieldAt(int pos) {
|
||||
return findField(pos);
|
||||
}
|
||||
public ECLField FindFieldAt(int pos) { return findFieldAt(pos); }
|
||||
|
||||
/**
|
||||
* Find the field at the given row and column.
|
||||
@@ -150,6 +169,7 @@ public class ECLFieldList {
|
||||
public ECLField findFieldAt(int row, int col) {
|
||||
return findField(row, col);
|
||||
}
|
||||
public ECLField FindFieldAt(int row, int col) { return findFieldAt(row, col); }
|
||||
|
||||
/**
|
||||
* Find the field preceding the one at the given buffer position.
|
||||
@@ -159,6 +179,7 @@ public class ECLFieldList {
|
||||
if (curr == null) return null;
|
||||
return getPreviousField(curr);
|
||||
}
|
||||
public ECLField FindPrevField(int pos) { return findPrevField(pos); }
|
||||
|
||||
/**
|
||||
* Find the field succeeding the one at the given buffer position.
|
||||
@@ -168,6 +189,7 @@ public class ECLFieldList {
|
||||
if (curr == null) return null;
|
||||
return getNextField(curr);
|
||||
}
|
||||
public ECLField FindNextField(int pos) { return findNextField(pos); }
|
||||
|
||||
/**
|
||||
* Find field containing the given text string.
|
||||
@@ -202,4 +224,5 @@ public class ECLFieldList {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public ECLField FindField(String text, int startPos) { return findField(text, startPos); }
|
||||
}
|
||||
|
||||
@@ -85,24 +85,52 @@ public class ECLOIA implements ECLConstants {
|
||||
return inputProcessor != null && inputProcessor.isInsertMode();
|
||||
}
|
||||
|
||||
public boolean IsInsertMode() {
|
||||
return isInsertMode();
|
||||
}
|
||||
|
||||
public boolean isNumeric() {
|
||||
if (screen == null || !screen.isFormatted()) return false;
|
||||
byte fa = screen.getFieldAttributeAt(screen.getCursorAddress());
|
||||
return faIsNumeric(fa & 0xFF);
|
||||
}
|
||||
|
||||
public boolean IsNumeric() {
|
||||
return isNumeric();
|
||||
}
|
||||
|
||||
public boolean isAlphanumeric() {
|
||||
return !isNumeric();
|
||||
}
|
||||
|
||||
public boolean IsAlphanumeric() {
|
||||
return isAlphanumeric();
|
||||
}
|
||||
|
||||
public boolean isDBCS() {
|
||||
return getAlphanumericType() == TYPE_DBCS;
|
||||
}
|
||||
|
||||
public boolean IsDBCS() {
|
||||
return isDBCS();
|
||||
}
|
||||
|
||||
public boolean isMessageWaiting() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean IsMessageWaiting() {
|
||||
return isMessageWaiting();
|
||||
}
|
||||
|
||||
public boolean isCommError() {
|
||||
return fsm != null && fsm.getConnectionState() != null && !fsm.getConnectionState().isConnected();
|
||||
}
|
||||
|
||||
public boolean IsCommError() {
|
||||
return isCommError();
|
||||
}
|
||||
|
||||
private int inhibitOverride = -1;
|
||||
|
||||
public void setInputInhibited(int reason) {
|
||||
@@ -112,6 +140,10 @@ public class ECLOIA implements ECLConstants {
|
||||
}
|
||||
}
|
||||
|
||||
public void SetInputInhibited(int reason) {
|
||||
setInputInhibited(reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the alphanumeric character entry type allowed at current cursor position.
|
||||
* Returns TYPE_ALPHANUMERIC (0), TYPE_NUMERIC (1), or TYPE_DBCS (2).
|
||||
@@ -132,6 +164,10 @@ public class ECLOIA implements ECLConstants {
|
||||
return TYPE_ALPHANUMERIC;
|
||||
}
|
||||
|
||||
public int GetAlphanumericType() {
|
||||
return getAlphanumericType();
|
||||
}
|
||||
|
||||
public String getAlphanumericTypeString() {
|
||||
switch (getAlphanumericType()) {
|
||||
case TYPE_NUMERIC: return "N";
|
||||
@@ -141,38 +177,74 @@ public class ECLOIA implements ECLConstants {
|
||||
}
|
||||
}
|
||||
|
||||
public String GetAlphanumericTypeString() {
|
||||
return getAlphanumericTypeString();
|
||||
}
|
||||
|
||||
public boolean isXSystem() {
|
||||
return getInputInhibited() == INHIBIT_SYSTEM_LOCK;
|
||||
}
|
||||
|
||||
public boolean IsXSystem() {
|
||||
return isXSystem();
|
||||
}
|
||||
|
||||
public boolean isXProt() {
|
||||
return getInputInhibited() == INHIBIT_PROTECTED_FIELD;
|
||||
}
|
||||
|
||||
public boolean IsXProt() {
|
||||
return isXProt();
|
||||
}
|
||||
|
||||
public boolean isXNum() {
|
||||
return getInputInhibited() == INHIBIT_NUMERIC_ONLY;
|
||||
}
|
||||
|
||||
public boolean IsXNum() {
|
||||
return isXNum();
|
||||
}
|
||||
|
||||
public boolean isXWait() {
|
||||
return isXSystem();
|
||||
}
|
||||
|
||||
public boolean IsXWait() {
|
||||
return isXWait();
|
||||
}
|
||||
|
||||
public boolean isXInsert() {
|
||||
return isInsertMode();
|
||||
}
|
||||
|
||||
public boolean IsXInsert() {
|
||||
return isXInsert();
|
||||
}
|
||||
|
||||
public boolean isXComm() {
|
||||
return isCommError() || getInputInhibited() == INHIBIT_COMM_CHECK;
|
||||
}
|
||||
|
||||
public boolean IsXComm() {
|
||||
return isXComm();
|
||||
}
|
||||
|
||||
public boolean isXOverflow() {
|
||||
return getInputInhibited() == INHIBIT_OVERFLOW;
|
||||
}
|
||||
|
||||
public boolean IsXOverflow() {
|
||||
return isXOverflow();
|
||||
}
|
||||
|
||||
public boolean isXOperatorDue() {
|
||||
return getInputInhibited() == INHIBIT_OPERATOR_DUE;
|
||||
}
|
||||
|
||||
public boolean IsXOperatorDue() {
|
||||
return isXOperatorDue();
|
||||
}
|
||||
|
||||
public String getStatusString() {
|
||||
int inhibit = getInputInhibited();
|
||||
switch (inhibit) {
|
||||
@@ -188,6 +260,10 @@ public class ECLOIA implements ECLConstants {
|
||||
}
|
||||
}
|
||||
|
||||
public String GetStatusString() {
|
||||
return getStatusString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current Input Inhibited code.
|
||||
* Returns one of INHIBIT_* constants from ECLConstants.
|
||||
@@ -205,6 +281,18 @@ public class ECLOIA implements ECLConstants {
|
||||
return INHIBIT_NOT_INHIBITED;
|
||||
}
|
||||
|
||||
public int GetInputInhibited() {
|
||||
return getInputInhibited();
|
||||
}
|
||||
|
||||
public int getInputInhibitedType() {
|
||||
return getInputInhibited();
|
||||
}
|
||||
|
||||
public int GetInputInhibitedType() {
|
||||
return getInputInhibited();
|
||||
}
|
||||
|
||||
/**
|
||||
* Block until the keyboard becomes unlocked and ready for user input, or timeout expires.
|
||||
* @return true if keyboard unlocked, false if timeout occurred.
|
||||
|
||||
@@ -205,21 +205,75 @@ public class ECLPS implements ECLConstants {
|
||||
return SearchPSExt(text, 1, getSize(), SEARCH_FORWARD, false, true);
|
||||
}
|
||||
|
||||
public int searchPS(String text) {
|
||||
return SearchPS(text);
|
||||
}
|
||||
|
||||
public int SearchPS(String text, int startPos) {
|
||||
return SearchPSExt(text, startPos, getSize(), SEARCH_FORWARD, false, true);
|
||||
}
|
||||
|
||||
public int searchPS(String text, int startPos) {
|
||||
return SearchPS(text, startPos);
|
||||
}
|
||||
|
||||
public int SearchPS(String text, int startRow, int startCol) {
|
||||
int pos = (startRow - 1) * getCols() + startCol;
|
||||
return SearchPSExt(text, pos, getSize(), SEARCH_FORWARD, false, true);
|
||||
}
|
||||
|
||||
public int searchPS(String text, int startRow, int startCol) {
|
||||
return SearchPS(text, startRow, startCol);
|
||||
}
|
||||
|
||||
public int SearchPS(String text, int startRow, int startCol, int dir) {
|
||||
int pos = (startRow - 1) * getCols() + startCol;
|
||||
return SearchPSExt(text, pos, getSize(), dir, false, true);
|
||||
}
|
||||
|
||||
public int searchPS(String text, int startRow, int startCol, int dir) {
|
||||
return SearchPS(text, startRow, startCol, dir);
|
||||
}
|
||||
|
||||
public int SearchPS(String text, int startRow, int startCol, int dir, boolean ignoreCase) {
|
||||
int pos = (startRow - 1) * getCols() + startCol;
|
||||
return SearchPSExt(text, pos, getSize(), dir, ignoreCase, true);
|
||||
}
|
||||
|
||||
public int searchPS(String text, int startRow, int startCol, int dir, boolean ignoreCase) {
|
||||
return SearchPS(text, startRow, startCol, dir, ignoreCase);
|
||||
}
|
||||
|
||||
public int SearchPS(String text, int startRow, int startCol, int endRow, int endCol) {
|
||||
int sPos = (startRow - 1) * getCols() + startCol;
|
||||
int ePos = (endRow - 1) * getCols() + endCol;
|
||||
return SearchPSExt(text, sPos, ePos, SEARCH_FORWARD, false, false);
|
||||
}
|
||||
|
||||
public int searchPS(String text, int startRow, int startCol, int endRow, int endCol) {
|
||||
return SearchPS(text, startRow, startCol, endRow, endCol);
|
||||
}
|
||||
|
||||
public int SearchPS(String text, int startRow, int startCol, int endRow, int endCol, int dir) {
|
||||
int sPos = (startRow - 1) * getCols() + startCol;
|
||||
int ePos = (endRow - 1) * getCols() + endCol;
|
||||
return SearchPSExt(text, sPos, ePos, dir, false, false);
|
||||
}
|
||||
|
||||
public int searchPS(String text, int startRow, int startCol, int endRow, int endCol, int dir) {
|
||||
return SearchPS(text, startRow, startCol, endRow, endCol, dir);
|
||||
}
|
||||
|
||||
public int SearchPS(String text, int startRow, int startCol, int endRow, int endCol, int dir, boolean ignoreCase) {
|
||||
int sPos = (startRow - 1) * getCols() + startCol;
|
||||
int ePos = (endRow - 1) * getCols() + endCol;
|
||||
return SearchPSExt(text, sPos, ePos, dir, ignoreCase, false);
|
||||
}
|
||||
|
||||
public int searchPS(String text, int startRow, int startCol, int endRow, int endCol, int dir, boolean ignoreCase) {
|
||||
return SearchPS(text, startRow, startCol, endRow, endCol, dir, ignoreCase);
|
||||
}
|
||||
|
||||
/**
|
||||
* SearchPS extended method conforming to IBM ECL specification.
|
||||
* Uses 1-based positions and returns 1-based index (or 0 if not found).
|
||||
@@ -263,6 +317,21 @@ public class ECLPS implements ECLConstants {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int searchPSExt(String text, int startPos, int endPos, int dir, boolean ignoreCase, boolean wrap) {
|
||||
return SearchPSExt(text, startPos, endPos, dir, ignoreCase, wrap);
|
||||
}
|
||||
|
||||
public int SearchPSExt(String text, int startRow, int startCol, int endRow, int endCol, int dir, boolean ignoreCase, boolean wrap) {
|
||||
int cols = getCols();
|
||||
int sPos = (startRow - 1) * cols + startCol;
|
||||
int ePos = (endRow - 1) * cols + endCol;
|
||||
return SearchPSExt(text, sPos, ePos, dir, ignoreCase, wrap);
|
||||
}
|
||||
|
||||
public int searchPSExt(String text, int startRow, int startCol, int endRow, int endCol, int dir, boolean ignoreCase, boolean wrap) {
|
||||
return SearchPSExt(text, startRow, startCol, endRow, endCol, dir, ignoreCase, wrap);
|
||||
}
|
||||
|
||||
private boolean matchesAt(String screenText, String target, int pos, int size) {
|
||||
int len = target.length();
|
||||
for (int j = 0; j < len; j++) {
|
||||
@@ -303,6 +372,10 @@ public class ECLPS implements ECLConstants {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String CopyString(int sRow, int sCol, int eRow, int eCol) {
|
||||
return copyString(sRow, sCol, eRow, eCol);
|
||||
}
|
||||
|
||||
/**
|
||||
* Paste a multi-line rectangular block of text starting at (row, col).
|
||||
*/
|
||||
@@ -320,6 +393,12 @@ public class ECLPS implements ECLConstants {
|
||||
String line = lines[i];
|
||||
for (int c = 0; c < line.length() && (col + c) < cols; c++) {
|
||||
int pos = targetRow * cols + (col + c);
|
||||
if (screen.isFormatted()) {
|
||||
byte fa = screen.getFieldAttributeAt(pos);
|
||||
if (faIsProtected(fa & 0xFF) || screen.getCell(pos).isFieldAttribute()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
setCursorPos(pos);
|
||||
if (inputProcessor != null) {
|
||||
inputProcessor.typeCharacter(line.charAt(c));
|
||||
@@ -330,6 +409,18 @@ public class ECLPS implements ECLConstants {
|
||||
return count;
|
||||
}
|
||||
|
||||
public int PasteString(String text, int row, int col) {
|
||||
return pasteString(text, row, col);
|
||||
}
|
||||
|
||||
public int pasteRectangular(String text, int row, int col) {
|
||||
return pasteString(text, row, col);
|
||||
}
|
||||
|
||||
public int PasteRectangular(String text, int row, int col) {
|
||||
return pasteString(text, row, col);
|
||||
}
|
||||
|
||||
/**
|
||||
* Paste text with line wrapping across unprotected fields.
|
||||
*/
|
||||
@@ -365,6 +456,108 @@ public class ECLPS implements ECLConstants {
|
||||
return charsPasted;
|
||||
}
|
||||
|
||||
public int PasteLineWrap(String text, int startPos, int endCol, boolean wordWrap) {
|
||||
return pasteLineWrap(text, startPos, endCol, wordWrap);
|
||||
}
|
||||
|
||||
// ========== Entry Assist & DOC Mode Operations ==========
|
||||
|
||||
public boolean isEntryAssistDOCmode() { return screen != null && screen.isEntryAssistDOCmode(); }
|
||||
public boolean IsEntryAssistDOCmode() { return isEntryAssistDOCmode(); }
|
||||
public void setEntryAssistDOCmode(boolean bl) { if (screen != null) screen.setEntryAssistDOCmode(bl); }
|
||||
public void SetEntryAssistDOCmode(boolean bl) { setEntryAssistDOCmode(bl); }
|
||||
|
||||
public boolean isEntryAssistWordWrap() { return screen != null && screen.isEntryAssistWordWrap(); }
|
||||
public boolean IsEntryAssistWordWrap() { return isEntryAssistWordWrap(); }
|
||||
public void setEntryAssistWordWrap(boolean bl) { if (screen != null) screen.setEntryAssistWordWrap(bl); }
|
||||
public void SetEntryAssistWordWrap(boolean bl) { setEntryAssistWordWrap(bl); }
|
||||
|
||||
public int getEntryAssistStartColumn() { return screen != null ? screen.getEntryAssistStartColumn() : 0; }
|
||||
public int GetEntryAssistStartColumn() { return getEntryAssistStartColumn(); }
|
||||
public void setEntryAssistStartColumn(int n) { if (screen != null) screen.setEntryAssistStartColumn(n); }
|
||||
public void SetEntryAssistStartColumn(int n) { setEntryAssistStartColumn(n); }
|
||||
|
||||
public int getEntryAssistEndColumn() { return screen != null ? screen.getEntryAssistEndColumn() : 0; }
|
||||
public int GetEntryAssistEndColumn() { return getEntryAssistEndColumn(); }
|
||||
public void setEntryAssistEndColumn(int n) { if (screen != null) screen.setEntryAssistEndColumn(n); }
|
||||
public void SetEntryAssistEndColumn(int n) { setEntryAssistEndColumn(n); }
|
||||
|
||||
public int[] getEntryAssistTabStops() { return screen != null ? screen.getEntryAssistTabStops() : null; }
|
||||
public int[] GetEntryAssistTabStops() { return getEntryAssistTabStops(); }
|
||||
public void setEntryAssistTabStops(int[] stops) { if (screen != null) screen.setEntryAssistTabStops(stops); }
|
||||
public void SetEntryAssistTabStops(int[] stops) { setEntryAssistTabStops(stops); }
|
||||
|
||||
public void processWordTab(boolean forward) { if (inputProcessor != null) inputProcessor.processWordTab(forward); else if (screen != null) screen.processWordTab(forward); }
|
||||
public void ProcessWordTab(boolean forward) { processWordTab(forward); }
|
||||
public void wordTab(boolean forward) { processWordTab(forward); }
|
||||
public void WordTab(boolean forward) { processWordTab(forward); }
|
||||
|
||||
public void processDeleteWord() { if (inputProcessor != null) inputProcessor.processDeleteWord(); else if (screen != null) screen.processDeleteWord(); }
|
||||
public void ProcessDeleteWord() { processDeleteWord(); }
|
||||
public void deleteWord() { processDeleteWord(); }
|
||||
public void DeleteWord() { processDeleteWord(); }
|
||||
|
||||
public void processWordLeft() { if (inputProcessor != null) inputProcessor.processWordLeft(); }
|
||||
public void ProcessWordLeft() { processWordLeft(); }
|
||||
public void wordLeft() { processWordLeft(); }
|
||||
public void WordLeft() { processWordLeft(); }
|
||||
|
||||
public void processWordRight() { if (inputProcessor != null) inputProcessor.processWordRight(); }
|
||||
public void ProcessWordRight() { processWordRight(); }
|
||||
public void wordRight() { processWordRight(); }
|
||||
public void WordRight() { processWordRight(); }
|
||||
|
||||
public void processFieldEnd() { if (inputProcessor != null) inputProcessor.processFieldEnd(); }
|
||||
public void ProcessFieldEnd() { processFieldEnd(); }
|
||||
public void fieldEnd() { processFieldEnd(); }
|
||||
public void FieldEnd() { processFieldEnd(); }
|
||||
|
||||
// ========== Field Management Accessors ==========
|
||||
|
||||
public ECLField getField(int pos) {
|
||||
return getFieldList().findField(pos);
|
||||
}
|
||||
|
||||
public ECLField GetField(int pos) {
|
||||
return getField(pos);
|
||||
}
|
||||
|
||||
public ECLField getField(int row, int col) {
|
||||
return getFieldList().findField(row, col);
|
||||
}
|
||||
|
||||
public ECLField GetField(int row, int col) {
|
||||
return getField(row, col);
|
||||
}
|
||||
|
||||
public ECLField getFirstField() {
|
||||
return getFieldList().getFirstField();
|
||||
}
|
||||
|
||||
public ECLField GetFirstField() {
|
||||
return getFirstField();
|
||||
}
|
||||
|
||||
public ECLField getNextField(ECLField prev) {
|
||||
return getFieldList().getNextField(prev);
|
||||
}
|
||||
|
||||
public ECLField GetNextField(ECLField prev) {
|
||||
return getNextField(prev);
|
||||
}
|
||||
|
||||
public ECLField getPreviousField(ECLField next) {
|
||||
return getFieldList().getPreviousField(next);
|
||||
}
|
||||
|
||||
public ECLField GetPreviousField(ECLField next) {
|
||||
return getPreviousField(next);
|
||||
}
|
||||
|
||||
public ECLFieldList GetFieldList() {
|
||||
return getFieldList();
|
||||
}
|
||||
|
||||
// ========== ECLPS Event Listener Management ==========
|
||||
|
||||
private final java.util.List<ECLPSListener> psListeners = new java.util.concurrent.CopyOnWriteArrayList<>();
|
||||
@@ -455,6 +648,29 @@ public class ECLPS implements ECLConstants {
|
||||
/**
|
||||
* Send keystrokes with configurable inter-character or inter-mnemonic delay.
|
||||
*/
|
||||
public void sendCharacters(String keys) {
|
||||
sendCharacters(keys, 0);
|
||||
}
|
||||
|
||||
public void SendCharacters(String keys) {
|
||||
sendCharacters(keys, 0);
|
||||
}
|
||||
|
||||
public void SendCharacters(String keys, int delayMs) {
|
||||
sendCharacters(keys, delayMs);
|
||||
}
|
||||
|
||||
public void sendCharacters(String keys, int row, int col, int delayMs) {
|
||||
if (row > 0 && col > 0) {
|
||||
setCursorPos(row - 1, col - 1);
|
||||
}
|
||||
sendCharacters(keys, delayMs);
|
||||
}
|
||||
|
||||
public void SendCharacters(String keys, int row, int col, int delayMs) {
|
||||
sendCharacters(keys, row, col, delayMs);
|
||||
}
|
||||
|
||||
public void sendCharacters(String keys, int delayMs) {
|
||||
if (keys == null || keys.isEmpty()) return;
|
||||
if (delayMs <= 0) {
|
||||
@@ -466,6 +682,11 @@ public class ECLPS implements ECLConstants {
|
||||
int len = keys.length();
|
||||
while (i < len) {
|
||||
if (keys.charAt(i) == '[') {
|
||||
if (i + 1 < len && keys.charAt(i + 1) == '[') {
|
||||
// Escaped bracket "[["
|
||||
sendKeys("[");
|
||||
i += 2;
|
||||
} else {
|
||||
int close = keys.indexOf(']', i);
|
||||
if (close > i) {
|
||||
String mnemonic = keys.substring(i, close + 1);
|
||||
@@ -475,6 +696,7 @@ public class ECLPS implements ECLConstants {
|
||||
sendKeys(keys.substring(i, i + 1));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sendKeys(keys.substring(i, i + 1));
|
||||
i++;
|
||||
|
||||
@@ -131,6 +131,22 @@ public class InputProcessor {
|
||||
int baddr = screen.getCursorAddress();
|
||||
baddr = ((baddr % size) + size) % size;
|
||||
|
||||
// Entry Assist DOC mode / Word Wrap handling
|
||||
if ((screen.isEntryAssistDOCmode() || screen.isEntryAssistWordWrap()) && !isNvtMode()) {
|
||||
int curCol = baddr % screen.getCols();
|
||||
int endCol = screen.getEntryAssistEndColumn();
|
||||
int startCol = screen.getEntryAssistStartColumn();
|
||||
if (curCol >= endCol) {
|
||||
screen.handleWordWrap(baddr, ch);
|
||||
baddr = screen.getCursorAddress();
|
||||
if (ch == ' ' && (baddr % screen.getCols()) == startCol) {
|
||||
screen.markAllChanged();
|
||||
screen.updateDisplaySnapshot();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (screen.isFormatted()) {
|
||||
// Check if cursor is at a field attribute
|
||||
ExtendedAttribute ea = screen.getCell(baddr);
|
||||
|
||||
@@ -909,19 +909,108 @@ public class ScreenBuffer {
|
||||
// ========== Entry Assist & DOC Mode Operations ==========
|
||||
|
||||
public boolean isEntryAssistDOCmode() { return docMode; }
|
||||
public boolean IsEntryAssistDOCmode() { return isEntryAssistDOCmode(); }
|
||||
public void setEntryAssistDOCmode(boolean bl) { this.docMode = bl; }
|
||||
public void SetEntryAssistDOCmode(boolean bl) { setEntryAssistDOCmode(bl); }
|
||||
|
||||
public boolean isEntryAssistWordWrap() { return wordWrap; }
|
||||
public boolean IsEntryAssistWordWrap() { return isEntryAssistWordWrap(); }
|
||||
public void setEntryAssistWordWrap(boolean bl) { this.wordWrap = bl; }
|
||||
public void SetEntryAssistWordWrap(boolean bl) { setEntryAssistWordWrap(bl); }
|
||||
|
||||
public int getEntryAssistStartColumn() { return docStartCol; }
|
||||
public int GetEntryAssistStartColumn() { return getEntryAssistStartColumn(); }
|
||||
public void setEntryAssistStartColumn(int n) { this.docStartCol = Math.max(0, n); }
|
||||
public void SetEntryAssistStartColumn(int n) { setEntryAssistStartColumn(n); }
|
||||
|
||||
public int getEntryAssistEndColumn() { return docEndCol >= 0 ? docEndCol : (cols - 1); }
|
||||
public int GetEntryAssistEndColumn() { return getEntryAssistEndColumn(); }
|
||||
public void setEntryAssistEndColumn(int n) { this.docEndCol = n; }
|
||||
public void SetEntryAssistEndColumn(int n) { setEntryAssistEndColumn(n); }
|
||||
|
||||
public int[] getEntryAssistTabStops() { return tabStops; }
|
||||
public int[] GetEntryAssistTabStops() { return getEntryAssistTabStops(); }
|
||||
public void setEntryAssistTabStops(int[] stops) { this.tabStops = stops; }
|
||||
public void SetEntryAssistTabStops(int[] stops) { setEntryAssistTabStops(stops); }
|
||||
|
||||
/**
|
||||
* Perform Entry Assist word wrap if typing near/past end margin.
|
||||
* Moves any partial word typed on the current line to the beginning of the next line (docStartCol).
|
||||
*/
|
||||
public synchronized boolean handleWordWrap(int curAddr, char typedChar) {
|
||||
if (!docMode && !wordWrap) return false;
|
||||
int size = rows * cols;
|
||||
if (size <= 0) return false;
|
||||
curAddr = ((curAddr % size) + size) % size;
|
||||
|
||||
int curRow = curAddr / cols;
|
||||
int curCol = curAddr % cols;
|
||||
int endCol = getEntryAssistEndColumn();
|
||||
int startCol = getEntryAssistStartColumn();
|
||||
|
||||
if (curCol < endCol) return false;
|
||||
|
||||
if (typedChar == ' ') {
|
||||
int nextRow = (curRow + 1) % rows;
|
||||
setCursorPosition(nextRow, startCol);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Scan backwards to find the start of the current word on this row
|
||||
int rowStartAddr = curRow * cols + startCol;
|
||||
int scan = curAddr - 1;
|
||||
while (scan >= rowStartAddr) {
|
||||
ExtendedAttribute ea = getCell(scan);
|
||||
if (ea.isFieldAttribute()) break;
|
||||
int ec = ea.ec & 0xFF;
|
||||
if (ec == 0 || ec == 0x40 || ea.ucs4 == ' ' || ea.ucs4 == 0) {
|
||||
break;
|
||||
}
|
||||
scan--;
|
||||
}
|
||||
|
||||
int wordStartAddr = scan + 1;
|
||||
int wordStartCol = wordStartAddr % cols;
|
||||
|
||||
if (wordStartCol > startCol && wordStartAddr < curAddr) {
|
||||
int wordLen = curAddr - wordStartAddr;
|
||||
ExtendedAttribute[] wordCells = new ExtendedAttribute[wordLen];
|
||||
for (int i = 0; i < wordLen; i++) {
|
||||
wordCells[i] = new ExtendedAttribute();
|
||||
wordCells[i].copyFrom(getCell(wordStartAddr + i));
|
||||
getCell(wordStartAddr + i).clear();
|
||||
}
|
||||
|
||||
int nextRow = (curRow + 1) % rows;
|
||||
int targetAddr = nextRow * cols + startCol;
|
||||
if (formatted) {
|
||||
targetAddr = findNextUnprotected(targetAddr - 1);
|
||||
}
|
||||
|
||||
for (int i = 0; i < wordLen; i++) {
|
||||
int dst = (targetAddr + i) % size;
|
||||
if (!getCell(dst).isFieldAttribute()) {
|
||||
getCell(dst).copyFrom(wordCells[i]);
|
||||
}
|
||||
}
|
||||
|
||||
setCursorAddress((targetAddr + wordLen) % size);
|
||||
screenChanged = true;
|
||||
updateDisplaySnapshot();
|
||||
return true;
|
||||
} else {
|
||||
// Word spans entire line or starts at startCol, wrap to next line
|
||||
int nextRow = (curRow + 1) % rows;
|
||||
int targetAddr = nextRow * cols + startCol;
|
||||
if (formatted) {
|
||||
targetAddr = findNextUnprotected(targetAddr - 1);
|
||||
}
|
||||
setCursorAddress(targetAddr);
|
||||
screenChanged = true;
|
||||
updateDisplaySnapshot();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void processWordTab(boolean forward) {
|
||||
int size = rows * cols;
|
||||
@@ -1060,4 +1149,12 @@ public class ScreenBuffer {
|
||||
screenChanged = true;
|
||||
updateDisplaySnapshot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspect and balance Shift-Out (0x0E) and Shift-In (0x0F) markers, ensuring DBCS integrity.
|
||||
*/
|
||||
public synchronized void balanceSOSI() {
|
||||
cleanAdjacentSISO(0);
|
||||
processSOSI();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,11 +44,41 @@ public class TelnetConnection {
|
||||
*/
|
||||
public void connect() throws IOException {
|
||||
ConnectionConfig.ProxyType proxyType = config.getProxyType();
|
||||
boolean hasProxy = proxyType != null && proxyType != ConnectionConfig.ProxyType.NONE &&
|
||||
config.getProxyHost() != null && !config.getProxyHost().trim().isEmpty();
|
||||
String proxyHost = config.getProxyHost();
|
||||
int proxyPort = config.getProxyPort();
|
||||
String proxyUser = config.getProxyUsername();
|
||||
String proxyPass = config.getProxyPassword();
|
||||
|
||||
String connectHost = hasProxy ? config.getProxyHost().trim() : config.getHost();
|
||||
int connectPort = hasProxy ? (config.getProxyPort() > 0 ? config.getProxyPort() : (proxyType == ConnectionConfig.ProxyType.HTTP ? 8080 : 1080)) : config.getPort();
|
||||
if ((proxyType == null || proxyType == ConnectionConfig.ProxyType.NONE) && (proxyHost == null || proxyHost.trim().isEmpty())) {
|
||||
// Check JVM system properties (matching IBM HoD browser/system default fallback)
|
||||
String sysSocks = System.getProperty("socksProxyHost");
|
||||
String sysHttp = System.getProperty("http.proxyHost");
|
||||
if (sysSocks != null && !sysSocks.trim().isEmpty()) {
|
||||
proxyType = ConnectionConfig.ProxyType.SOCKS5;
|
||||
proxyHost = sysSocks.trim();
|
||||
String portStr = System.getProperty("socksProxyPort");
|
||||
if (portStr != null) {
|
||||
try { proxyPort = Integer.parseInt(portStr.trim()); } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
proxyUser = System.getProperty("java.net.socks.username");
|
||||
proxyPass = System.getProperty("java.net.socks.password");
|
||||
} else if (sysHttp != null && !sysHttp.trim().isEmpty()) {
|
||||
proxyType = ConnectionConfig.ProxyType.HTTP;
|
||||
proxyHost = sysHttp.trim();
|
||||
String portStr = System.getProperty("http.proxyPort");
|
||||
if (portStr != null) {
|
||||
try { proxyPort = Integer.parseInt(portStr.trim()); } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
proxyUser = System.getProperty("http.proxyUser");
|
||||
proxyPass = System.getProperty("http.proxyPassword");
|
||||
}
|
||||
}
|
||||
|
||||
boolean hasProxy = proxyType != null && proxyType != ConnectionConfig.ProxyType.NONE &&
|
||||
proxyHost != null && !proxyHost.trim().isEmpty();
|
||||
|
||||
String connectHost = hasProxy ? proxyHost.trim() : config.getHost();
|
||||
int connectPort = hasProxy ? (proxyPort > 0 ? proxyPort : (proxyType == ConnectionConfig.ProxyType.HTTP ? 8080 : 1080)) : config.getPort();
|
||||
|
||||
log.info("Connecting TCP socket to " + connectHost + ":" + connectPort +
|
||||
(hasProxy ? " (via " + proxyType + " proxy for " + config.getHost() + ":" + config.getPort() + ")" : "") +
|
||||
@@ -67,13 +97,13 @@ public class TelnetConnection {
|
||||
if (hasProxy) {
|
||||
switch (proxyType) {
|
||||
case HTTP:
|
||||
establishHttpProxy(rawSocket, config.getHost(), config.getPort(), config.getProxyUsername(), config.getProxyPassword());
|
||||
establishHttpProxy(rawSocket, config.getHost(), config.getPort(), proxyUser, proxyPass);
|
||||
break;
|
||||
case SOCKS4:
|
||||
establishSocks4Proxy(rawSocket, config.getHost(), config.getPort(), config.getProxyUsername());
|
||||
establishSocks4Proxy(rawSocket, config.getHost(), config.getPort(), proxyUser);
|
||||
break;
|
||||
case SOCKS5:
|
||||
establishSocks5Proxy(rawSocket, config.getHost(), config.getPort(), config.getProxyUsername(), config.getProxyPassword());
|
||||
establishSocks5Proxy(rawSocket, config.getHost(), config.getPort(), proxyUser, proxyPass);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
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.ExtendedAttribute;
|
||||
import haus.nightmare.lib3270j.screen.ScreenBuffer;
|
||||
import static haus.nightmare.lib3270j.ecl.ECLConstants.*;
|
||||
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||
|
||||
public class ECLOIAPhase4Test {
|
||||
|
||||
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 testInhibitionCodesAndStatusStrings() {
|
||||
screen.erase(false);
|
||||
assertEquals(INHIBIT_NOT_INHIBITED, oia.GetInputInhibited());
|
||||
assertEquals(INHIBIT_NOT_INHIBITED, oia.GetInputInhibitedType());
|
||||
assertEquals("READY", oia.GetStatusString());
|
||||
|
||||
// Test X-SYSTEM
|
||||
oia.SetInputInhibited(INHIBIT_SYSTEM_LOCK);
|
||||
assertTrue(oia.IsXSystem());
|
||||
assertTrue(oia.IsXWait());
|
||||
assertEquals("X-SYSTEM", oia.GetStatusString());
|
||||
|
||||
// Test X-NUM
|
||||
oia.SetInputInhibited(INHIBIT_NUMERIC_ONLY);
|
||||
assertTrue(oia.IsXNum());
|
||||
assertEquals("X-NUM", oia.GetStatusString());
|
||||
|
||||
// Test X-PROT
|
||||
oia.SetInputInhibited(INHIBIT_PROTECTED_FIELD);
|
||||
assertTrue(oia.IsXProt());
|
||||
assertEquals("X-PROT", oia.GetStatusString());
|
||||
|
||||
// Test X-OVERFLOW
|
||||
oia.SetInputInhibited(INHIBIT_OVERFLOW);
|
||||
assertTrue(oia.IsXOverflow());
|
||||
assertEquals("X-OVERFLOW", oia.GetStatusString());
|
||||
|
||||
// Test X-COMM
|
||||
oia.SetInputInhibited(INHIBIT_COMM_CHECK);
|
||||
assertTrue(oia.IsXComm());
|
||||
assertEquals("X-COMM", oia.GetStatusString());
|
||||
|
||||
// Test X-OP
|
||||
oia.SetInputInhibited(INHIBIT_OPERATOR_DUE);
|
||||
assertTrue(oia.IsXOperatorDue());
|
||||
assertEquals("X-OP", oia.GetStatusString());
|
||||
|
||||
// Test X-INSERT
|
||||
oia.SetInputInhibited(INHIBIT_NOT_INHIBITED);
|
||||
input.setInsertMode(true);
|
||||
assertTrue(oia.IsInsertMode());
|
||||
assertTrue(oia.isXInsert());
|
||||
assertEquals("X-INSERT", oia.GetStatusString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlphanumericTypeGetters() {
|
||||
screen.erase(false);
|
||||
assertEquals(TYPE_ALPHANUMERIC, oia.GetAlphanumericType());
|
||||
assertEquals("A", oia.GetAlphanumericTypeString());
|
||||
assertTrue(oia.IsAlphanumeric());
|
||||
assertFalse(oia.IsNumeric());
|
||||
assertFalse(oia.IsDBCS());
|
||||
|
||||
// Numeric field
|
||||
screen.setFieldAttribute(0, (byte) (FA_PRINTABLE | FA_NUMERIC));
|
||||
screen.setCursorAddress(1);
|
||||
assertEquals(TYPE_NUMERIC, oia.GetAlphanumericType());
|
||||
assertEquals("N", oia.GetAlphanumericTypeString());
|
||||
assertTrue(oia.IsNumeric());
|
||||
assertFalse(oia.IsAlphanumeric());
|
||||
|
||||
// DBCS cell
|
||||
ExtendedAttribute ea = screen.getCell(1);
|
||||
ea.cs = ExtendedAttribute.CS_DBCS;
|
||||
assertEquals(TYPE_DBCS, oia.GetAlphanumericType());
|
||||
assertEquals("D", oia.GetAlphanumericTypeString());
|
||||
assertTrue(oia.IsDBCS());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSynchronizationPrimitives() {
|
||||
screen.erase(false);
|
||||
assertTrue(oia.WaitForInput(100));
|
||||
assertTrue(oia.WaitForSystemAvailable(100));
|
||||
assertTrue(oia.WaitForAppAvailable(100));
|
||||
|
||||
// Lock keyboard, wait should timeout
|
||||
input.setKeyboardLocked(true);
|
||||
assertFalse(oia.WaitForInput(50));
|
||||
input.setKeyboardLocked(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
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.ecl.ECLConstants.*;
|
||||
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||
|
||||
public class ECLPSPhase4Test {
|
||||
|
||||
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 testSearchPSComprehensiveOverloads() {
|
||||
screen.erase(false);
|
||||
screen.setText("IBM Host On-Demand ECL Automation Presentation Space");
|
||||
|
||||
// 1-based indexing overloads
|
||||
assertEquals(5, ps.SearchPS("Host"));
|
||||
assertEquals(5, ps.searchPS("Host"));
|
||||
assertEquals(5, ps.SearchPS("Host", 1));
|
||||
assertEquals(5, ps.SearchPS("Host", 1, SEARCH_FORWARD));
|
||||
assertEquals(5, ps.SearchPS("Host", 1, 1));
|
||||
assertEquals(5, ps.SearchPS("host", 1, 1, SEARCH_FORWARD, true));
|
||||
assertEquals(0, ps.SearchPS("host", 1, 1, SEARCH_FORWARD, false));
|
||||
|
||||
// Bounded range searches
|
||||
assertEquals(5, ps.SearchPS("Host", 1, 1, 1, 30));
|
||||
assertEquals(0, ps.SearchPS("Host", 1, 10, 1, 30)); // starts after "Host"
|
||||
assertEquals(5, ps.SearchPS("host", 1, 1, 1, 30, DIR_FORWARD, true));
|
||||
|
||||
// SearchPSExt
|
||||
int pos1 = ps.SearchPSExt("ECL", 1, 50, SEARCH_FORWARD, false, false);
|
||||
assertEquals(20, pos1);
|
||||
|
||||
int pos2 = ps.SearchPSExt("ecl", 1, 1, 1, 50, DIR_FORWARD, true, false);
|
||||
assertEquals(20, pos2);
|
||||
|
||||
// Backward search
|
||||
screen.setChar(1, 10, 'Z');
|
||||
screen.setChar(1, 11, 'O');
|
||||
screen.setChar(1, 12, 'O');
|
||||
|
||||
int backFound = ps.SearchPSExt("ZOO", 200, 1, SEARCH_BACKWARD, false, false);
|
||||
assertEquals(91, backFound); // row 1 col 10 is 80 + 10 + 1 = 91
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRectangularCopyAndPaste() {
|
||||
screen.erase(false);
|
||||
// Fill a small grid across rows 0, 1, 2
|
||||
String r0 = "ROW0_DATA_TEST";
|
||||
for (int c = 0; c < r0.length(); c++) screen.setChar(0, c, r0.charAt(c));
|
||||
String r1 = "ROW1_DATA_TEST";
|
||||
for (int c = 0; c < r1.length(); c++) screen.setChar(1, c, r1.charAt(c));
|
||||
String r2 = "ROW2_DATA_TEST";
|
||||
for (int c = 0; c < r2.length(); c++) screen.setChar(2, c, r2.charAt(c));
|
||||
|
||||
// Copy rectangular region rows 0..2, cols 0..3
|
||||
String copied = ps.copyString(0, 0, 2, 3);
|
||||
assertEquals("ROW0\nROW1\nROW2", copied);
|
||||
|
||||
// Test inverted coordinate normalization
|
||||
String inverted = ps.CopyString(2, 3, 0, 0);
|
||||
assertEquals("ROW0\nROW1\nROW2", inverted);
|
||||
|
||||
// Paste rectangular text
|
||||
screen.erase(false);
|
||||
screen.setFieldAttribute(0, (byte) FA_PRINTABLE);
|
||||
screen.setFieldAttribute(80, (byte) FA_PRINTABLE);
|
||||
screen.setFieldAttribute(160, (byte) FA_PRINTABLE);
|
||||
|
||||
int pasted = ps.PasteString("ABC\nDEF\nGHI", 0, 1);
|
||||
assertEquals(9, pasted);
|
||||
|
||||
assertEquals("ABC", ps.getString(1, 3));
|
||||
assertEquals("DEF", ps.getString(81, 3));
|
||||
assertEquals("GHI", ps.getString(161, 3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendCharactersWithMnemonicsAndEscaping() {
|
||||
screen.erase(false);
|
||||
screen.setFieldAttribute(0, (byte) FA_PRINTABLE);
|
||||
screen.setFieldAttribute(20, (byte) FA_PRINTABLE);
|
||||
screen.setCursorAddress(1);
|
||||
|
||||
// Test mnemonic [tab] and escaped bracket [[
|
||||
ps.sendCharacters("A[[B[tab]C", 1);
|
||||
|
||||
assertEquals('A', screen.getChar(0, 1));
|
||||
assertEquals('[', screen.getChar(0, 2));
|
||||
assertEquals('B', screen.getChar(0, 3));
|
||||
// After tab, at pos 21
|
||||
assertEquals('C', screen.getChar(0, 21));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntryAssistPassThroughMethods() {
|
||||
screen.erase(false);
|
||||
ps.SetEntryAssistDOCmode(true);
|
||||
ps.SetEntryAssistWordWrap(true);
|
||||
ps.SetEntryAssistStartColumn(4);
|
||||
ps.SetEntryAssistEndColumn(72);
|
||||
ps.SetEntryAssistTabStops(new int[]{ 8, 16, 24, 32 });
|
||||
|
||||
assertTrue(ps.IsEntryAssistDOCmode());
|
||||
assertTrue(ps.IsEntryAssistWordWrap());
|
||||
assertEquals(4, ps.GetEntryAssistStartColumn());
|
||||
assertEquals(72, ps.GetEntryAssistEndColumn());
|
||||
assertArrayEquals(new int[]{ 8, 16, 24, 32 }, ps.GetEntryAssistTabStops());
|
||||
|
||||
ps.setCursorPos(0, 0);
|
||||
ps.WordTab(true);
|
||||
assertEquals(8, ps.getCursorCol());
|
||||
|
||||
ps.WordTab(true);
|
||||
assertEquals(16, ps.getCursorCol());
|
||||
|
||||
ps.WordTab(false);
|
||||
assertEquals(8, ps.getCursorCol());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFieldManagementAccessors() {
|
||||
screen.erase(false);
|
||||
screen.setFieldAttribute(0, (byte) FA_PRINTABLE);
|
||||
screen.setFieldAttribute(50, (byte) (FA_PRINTABLE | FA_PROTECT));
|
||||
|
||||
assertNotNull(ps.GetFieldList());
|
||||
assertEquals(2, ps.GetFieldList().GetFieldCount());
|
||||
|
||||
ECLField f0 = ps.GetFirstField();
|
||||
assertNotNull(f0);
|
||||
assertEquals(0, f0.GetStart());
|
||||
assertEquals(1, f0.GetDataStart());
|
||||
assertEquals(49, f0.GetEnd());
|
||||
assertEquals(49, f0.GetLength());
|
||||
assertFalse(f0.IsProtected());
|
||||
|
||||
ECLField f1 = ps.GetNextField(f0);
|
||||
assertNotNull(f1);
|
||||
assertEquals(50, f1.GetStart());
|
||||
assertTrue(f1.IsProtected());
|
||||
|
||||
assertEquals(f0.GetStart(), ps.GetPreviousField(f1).GetStart());
|
||||
assertEquals(f0.GetStart(), ps.GetField(25).GetStart());
|
||||
assertEquals(f1.GetStart(), ps.GetField(60).GetStart());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
package haus.nightmare.lib3270j.graphics;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.awt.Point;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
/**
|
||||
* Verification test suite for Phase 3: Host Graphics (GOCA / 3179G / Programmed Symbols) (GDDM).
|
||||
* Covers items 3.1 through 3.6 of the PhasedUpdates specification.
|
||||
*/
|
||||
public class GocaPhase3Test {
|
||||
|
||||
// ====================================================================================================
|
||||
// ITEM 3.1: GOCA Order 0x23 / 0x27 - Viewing Window (Clipping Viewport)
|
||||
// ====================================================================================================
|
||||
@Test
|
||||
@DisplayName("Item 3.1: Viewing Window (0x23 / 0x27) clipping bounds and raster isolation")
|
||||
public void testItem3_1_ViewingWindowClipping() {
|
||||
GraphicsPlane plane = new GraphicsPlane(400, 300);
|
||||
GocaDecoder decoder = new GocaDecoder(plane);
|
||||
|
||||
assertFalse(plane.isViewingWindowActive());
|
||||
|
||||
// Define Viewing Window: x in [-100, 100], y in [-100, 100]
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(GocaConstants.G_GSVW_DEF); // 0x23
|
||||
out.write(0x08); // 8-byte payload
|
||||
out.write((byte) 0xFF); out.write((byte) 0x9C); // xMin = -100
|
||||
out.write((byte) 0xFF); out.write((byte) 0x9C); // yMin = -100
|
||||
out.write(0x00); out.write(100); // xMax = 100
|
||||
out.write(0x00); out.write(100); // yMax = 100
|
||||
|
||||
byte[] vwOrder = out.toByteArray();
|
||||
decoder.decodeGoca(vwOrder, 0, vwOrder.length);
|
||||
|
||||
assertTrue(plane.isViewingWindowActive());
|
||||
assertEquals(-100, plane.getViewingWindowXMin());
|
||||
assertEquals(-100, plane.getViewingWindowYMin());
|
||||
assertEquals(100, plane.getViewingWindowXMax());
|
||||
assertEquals(100, plane.getViewingWindowYMax());
|
||||
|
||||
// 1. Draw a line entirely outside viewing window: (-300, -150) to (-200, -150)
|
||||
ByteArrayOutputStream lineOutside = new ByteArrayOutputStream();
|
||||
lineOutside.write(GocaConstants.G_GSCOL); lineOutside.write(0x02); // Red
|
||||
lineOutside.write(GocaConstants.G_GLINE); lineOutside.write(0x08);
|
||||
lineOutside.write((byte) 0xFE); lineOutside.write((byte) 0xD4); // -300
|
||||
lineOutside.write((byte) 0xFF); lineOutside.write((byte) 0x6A); // -150
|
||||
lineOutside.write((byte) 0xFF); lineOutside.write((byte) 0x38); // -200
|
||||
lineOutside.write((byte) 0xFF); lineOutside.write((byte) 0x6A); // -150
|
||||
|
||||
byte[] lineOutBytes = lineOutside.toByteArray();
|
||||
decoder.decodeGoca(lineOutBytes, 0, lineOutBytes.length);
|
||||
|
||||
int pxClipped = plane.mapX(-300);
|
||||
int pyClipped = plane.mapY(-150);
|
||||
int pixelVal = plane.getRgbBuffer()[pyClipped * plane.getCanvasWidth() + pxClipped];
|
||||
assertEquals(0, pixelVal, "Pixel outside viewing window must be clipped (0)");
|
||||
|
||||
// 2. Draw a line inside viewing window: (0, 0) to (50, 50)
|
||||
ByteArrayOutputStream lineInside = new ByteArrayOutputStream();
|
||||
lineInside.write(GocaConstants.G_GSCOL); lineInside.write(0x04); // Green
|
||||
lineInside.write(GocaConstants.G_GLINE); lineInside.write(0x08);
|
||||
lineInside.write(0x00); lineInside.write(0); lineInside.write(0x00); lineInside.write(0);
|
||||
lineInside.write(0x00); lineInside.write(50); lineInside.write(0x00); lineInside.write(50);
|
||||
|
||||
byte[] lineInBytes = lineInside.toByteArray();
|
||||
decoder.decodeGoca(lineInBytes, 0, lineInBytes.length);
|
||||
|
||||
int pxIn = plane.mapX(0);
|
||||
int pyIn = plane.mapY(0);
|
||||
int pixelInVal = plane.getRgbBuffer()[pyIn * plane.getCanvasWidth() + pxIn];
|
||||
assertNotEquals(0, pixelInVal, "Pixel inside viewing window must be rendered");
|
||||
|
||||
// 3. Clear viewing window via zero-length payload
|
||||
ByteArrayOutputStream clearVw = new ByteArrayOutputStream();
|
||||
clearVw.write(GocaConstants.G_GSVW);
|
||||
clearVw.write(0x00);
|
||||
byte[] clearVwBytes = clearVw.toByteArray();
|
||||
decoder.decodeGoca(clearVwBytes, 0, clearVwBytes.length);
|
||||
|
||||
assertFalse(plane.isViewingWindowActive());
|
||||
}
|
||||
|
||||
// ====================================================================================================
|
||||
// ITEM 3.2: GOCA Order 0x22 - Segment Characteristics
|
||||
// ====================================================================================================
|
||||
@Test
|
||||
@DisplayName("Item 3.2: Segment Characteristics (0x22) flags decoding")
|
||||
public void testItem3_2_SegmentCharacteristics() {
|
||||
GraphicsPlane plane = new GraphicsPlane(400, 300);
|
||||
GocaDecoder decoder = new GocaDecoder(plane);
|
||||
|
||||
// Chained (0x80) | Dynamic (0x40) | Visible (0x00) -> 0xC0
|
||||
decoder.processSegmentCharacteristics(new byte[]{(byte) 0xC0});
|
||||
assertTrue(decoder.isSegChained());
|
||||
assertTrue(decoder.isSegDynamic());
|
||||
assertTrue(decoder.isSegVisible());
|
||||
|
||||
// Non-chained (0x00) | Static (0x00) | Invisible (0x20) -> 0x20
|
||||
decoder.processSegmentCharacteristics(new byte[]{(byte) 0x20});
|
||||
assertFalse(decoder.isSegChained());
|
||||
assertFalse(decoder.isSegDynamic());
|
||||
assertFalse(decoder.isSegVisible());
|
||||
|
||||
// Unchained (0x00) | Dynamic (0x40) | Visible (0x00) -> 0x40
|
||||
decoder.processSegmentCharacteristics(new byte[]{(byte) 0x40});
|
||||
assertFalse(decoder.isSegChained());
|
||||
assertTrue(decoder.isSegDynamic());
|
||||
assertTrue(decoder.isSegVisible());
|
||||
}
|
||||
|
||||
// ====================================================================================================
|
||||
// ITEM 3.3: GOCA Order 0x11 - Fractional Line Width Calculation
|
||||
// ====================================================================================================
|
||||
@Test
|
||||
@DisplayName("Item 3.3: Fractional Line Width (0x11) fixed-point ratio and plane synchronization")
|
||||
public void testItem3_3_FractionalLineWidth() {
|
||||
GraphicsPlane plane = new GraphicsPlane(400, 300);
|
||||
GocaDecoder decoder = new GocaDecoder(plane);
|
||||
|
||||
// 1. Fixed point: integer = 3, fraction = 128/256 (0.5) -> 3.5
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(GocaConstants.G_GSFLW); // 0x11
|
||||
out.write(0x03); // Integer part
|
||||
out.write(0x80); // Fractional part (128 / 256 = 0.5)
|
||||
|
||||
byte[] flwBytes = out.toByteArray();
|
||||
decoder.decodeGoca(flwBytes, 0, flwBytes.length);
|
||||
|
||||
assertEquals(3.5, decoder.getFractionalLineWidth(), 0.001);
|
||||
assertEquals(3.5, plane.getFractionalLineWidth(), 0.001);
|
||||
|
||||
// 2. Single byte operand: integer = 2, frac = 0 -> 2.0
|
||||
ByteArrayOutputStream out2 = new ByteArrayOutputStream();
|
||||
out2.write(GocaConstants.G_GSFLW);
|
||||
out2.write(0x02);
|
||||
|
||||
byte[] flwBytes2 = out2.toByteArray();
|
||||
decoder.decodeGoca(flwBytes2, 0, flwBytes2.length);
|
||||
|
||||
assertEquals(2.0, decoder.getFractionalLineWidth(), 0.001);
|
||||
assertEquals(2.0, plane.getFractionalLineWidth(), 0.001);
|
||||
|
||||
// 3. Reset defaults restores 1.0
|
||||
decoder.resetDefaults();
|
||||
assertEquals(1.0, decoder.getFractionalLineWidth(), 0.001);
|
||||
assertEquals(1.0, plane.getFractionalLineWidth(), 0.001);
|
||||
}
|
||||
|
||||
// ====================================================================================================
|
||||
// ITEM 3.4: GCS Character Baseline Angle and Shear Vector Transforms
|
||||
// ====================================================================================================
|
||||
@Test
|
||||
@DisplayName("Item 3.4: Character Baseline Angle (GSCA 0x34) and Shear (GSCR 0x35) transforms")
|
||||
public void testItem3_4_CharacterAngleAndShear() {
|
||||
GraphicsPlane plane = new GraphicsPlane(400, 300);
|
||||
GocaDecoder decoder = new GocaDecoder(plane);
|
||||
|
||||
// 1. GSCA 0x34: Character Angle vector (ax=0, ay=10) -> 90 degrees vertical
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(GocaConstants.G_GSCA);
|
||||
out.write(0x04);
|
||||
out.write(0x00); out.write(0); // ax = 0
|
||||
out.write(0x00); out.write(10); // ay = 10
|
||||
|
||||
// GSCR 0x35: Character Shear vector (sx=10, sy=10) -> 45 degrees slant
|
||||
out.write(GocaConstants.G_GSCR);
|
||||
out.write(0x04);
|
||||
out.write(0x00); out.write(10); // sx = 10
|
||||
out.write(0x00); out.write(10); // sy = 10
|
||||
|
||||
byte[] stream = out.toByteArray();
|
||||
decoder.decodeGoca(stream, 0, stream.length);
|
||||
|
||||
assertEquals(90.0, decoder.getCharAngle(), 0.01);
|
||||
assertEquals(45.0, decoder.getCharShear(), 0.01);
|
||||
|
||||
// 2. Draw stroked vector text with active angle and shear
|
||||
ByteArrayOutputStream textStream = new ByteArrayOutputStream();
|
||||
textStream.write(GocaConstants.G_GSCS); textStream.write(0xF8); // Vector font
|
||||
textStream.write(GocaConstants.G_GCHST); // Character String Absolute
|
||||
textStream.write(0x05); // 4 bytes coord + 1 byte text 'A' (EBCDIC 0xC1)
|
||||
textStream.write(0x00); textStream.write(50);
|
||||
textStream.write(0x00); textStream.write(50);
|
||||
textStream.write((byte) 0xC1); // EBCDIC 'A'
|
||||
|
||||
byte[] textBytes = textStream.toByteArray();
|
||||
decoder.decodeGoca(textBytes, 0, textBytes.length);
|
||||
|
||||
assertTrue(plane.hasContent(), "Vector text with angle/shear must render to GraphicsPlane");
|
||||
}
|
||||
|
||||
// ====================================================================================================
|
||||
// ITEM 3.5: Multi-Plane Programmed Symbols (RWS 4..7) & Multi-Color PS Cells
|
||||
// ====================================================================================================
|
||||
@Test
|
||||
@DisplayName("Item 3.5: Multi-Plane Programmed Symbols (RWS 4..7) & Triple-Plane Composite Color Cells")
|
||||
public void testItem3_5_ProgrammedSymbolsTriplePlane() {
|
||||
ProgramSymbolManager psm = new ProgramSymbolManager(9, 16);
|
||||
|
||||
// 1. Single-plane set (RWS 2)
|
||||
byte[] loadpsSingle = new byte[4 + 18];
|
||||
loadpsSingle[0] = 0x01; // Format 1
|
||||
loadpsSingle[1] = 0x41; // LCID 0x41
|
||||
loadpsSingle[2] = 0x40; // Start codepoint
|
||||
loadpsSingle[3] = 0x02; // RWS 2
|
||||
for (int i = 0; i < 18; i++) loadpsSingle[4 + i] = (byte) 0x55;
|
||||
psm.loadps(loadpsSingle);
|
||||
|
||||
ProgramSymbolSet singleSet = psm.getSymbolSet(0x41);
|
||||
assertNotNull(singleSet);
|
||||
assertFalse(singleSet.isTriplePlane());
|
||||
|
||||
// 2. Triple-plane set (RWS 4, LCID 0x42) loaded with all 3 planes simultaneously (54 bytes)
|
||||
byte[] loadpsTriple = new byte[4 + 54];
|
||||
loadpsTriple[0] = 0x01; // Format 1
|
||||
loadpsTriple[1] = 0x42; // LCID 0x42
|
||||
loadpsTriple[2] = 0x40; // Start codepoint
|
||||
loadpsTriple[3] = 0x04; // RWS 4 (Triple Plane)
|
||||
// Red slice: plane 1 (0x01)
|
||||
for (int i = 0; i < 18; i++) loadpsTriple[4 + i] = (byte) 0xFF;
|
||||
// Green slice: plane 2 (0x02)
|
||||
for (int i = 0; i < 18; i++) loadpsTriple[4 + 18 + i] = (byte) 0xFF;
|
||||
// Blue slice: plane 4 (0x04)
|
||||
for (int i = 0; i < 18; i++) loadpsTriple[4 + 36 + i] = (byte) 0xFF;
|
||||
|
||||
psm.loadps(loadpsTriple);
|
||||
|
||||
ProgramSymbolSet tripleSet = psm.getSymbolSet(0x42);
|
||||
assertNotNull(tripleSet);
|
||||
assertTrue(tripleSet.isTriplePlane());
|
||||
|
||||
ProgramSymbolSet.SymbolSlot slot = psm.getSymbol(0x42, 0x40);
|
||||
assertNotNull(slot);
|
||||
assertTrue(slot.isTriplePlane());
|
||||
|
||||
// Verify multi-color RGB mapping: composite plane (1|2|4 = 7 -> White)
|
||||
int[] rgb = slot.getRgbPixels(GocaConstants.GOCA_COLORS[0], 0);
|
||||
assertNotNull(rgb);
|
||||
assertEquals(9 * 16, rgb.length);
|
||||
assertEquals(0xFFFFFFFF, rgb[0]); // White pixel (all 3 planes set)
|
||||
}
|
||||
|
||||
// ====================================================================================================
|
||||
// ITEM 3.6: Pick Correlation for Graphical Segment Selection
|
||||
// ====================================================================================================
|
||||
@Test
|
||||
@DisplayName("Item 3.6: Pick Correlation & 56-Byte Graphic Input Structured Field generation")
|
||||
public void testItem3_6_PickCorrelationAndGraphicInput() {
|
||||
GraphicsPlane plane = new GraphicsPlane(400, 300);
|
||||
GocaDecoder decoder = new GocaDecoder(plane);
|
||||
|
||||
// 1. Define Segment 100: drawing a line from (-50, -50) to (50, 50)
|
||||
ByteArrayOutputStream segStream = new ByteArrayOutputStream();
|
||||
segStream.write(GocaConstants.G_BEGSEGM);
|
||||
segStream.write(0x0C);
|
||||
segStream.write(0x00); segStream.write(0x00); segStream.write(0x00); segStream.write(100); // Seg ID 100
|
||||
segStream.write(0x00); segStream.write(0x00);
|
||||
segStream.write(0x00); segStream.write(0x00);
|
||||
segStream.write(0x00); segStream.write(0x00); segStream.write(0x00); segStream.write(0x00);
|
||||
segStream.write(GocaConstants.G_GSCOL); segStream.write(0x06); // Yellow
|
||||
segStream.write(GocaConstants.G_GLINE); segStream.write(0x08);
|
||||
segStream.write((byte) 0xFF); segStream.write((byte) 0xCE); // -50
|
||||
segStream.write((byte) 0xFF); segStream.write((byte) 0xCE); // -50
|
||||
segStream.write(0x00); segStream.write(50);
|
||||
segStream.write(0x00); segStream.write(50);
|
||||
segStream.write(GocaConstants.G_ENDSEGM); segStream.write(0x00);
|
||||
|
||||
byte[] segBytes = segStream.toByteArray();
|
||||
decoder.decodeGoca(segBytes, 0, segBytes.length);
|
||||
|
||||
// Hit test inside segment bounds: (0, 0)
|
||||
int pickedSeg = decoder.findPickedSegment(0, 0);
|
||||
assertEquals(100, pickedSeg, "Segment 100 should be hit at (0, 0)");
|
||||
|
||||
// Hit test outside segment bounds: (200, 200)
|
||||
int pickedMiss = decoder.findPickedSegment(200, 200);
|
||||
assertEquals(0, pickedMiss, "Missed click should default to segId 0");
|
||||
|
||||
// 2. Build 56-byte Graphic Input Structured Field via GraphicInputBuilder
|
||||
byte[] inputSF = GraphicInputBuilder.buildPickCorrelation(25, -25, 5);
|
||||
assertEquals(56, inputSF.length);
|
||||
// Header check: 0x0034 (52 byte payload length per IBM HOD)
|
||||
assertEquals(0x00, inputSF[0]);
|
||||
assertEquals(0x34, inputSF[1]);
|
||||
assertEquals(0x0F, inputSF[2]);
|
||||
assertEquals(0x0F, inputSF[3]);
|
||||
|
||||
// Coordinate checks: X = 25, Y = -25
|
||||
int gX = ((inputSF[24] & 0xFF) << 8) | (inputSF[25] & 0xFF);
|
||||
int gY = (short) (((inputSF[26] & 0xFF) << 8) | (inputSF[27] & 0xFF));
|
||||
assertEquals(25, gX);
|
||||
assertEquals(-25, gY);
|
||||
|
||||
// Mouse action flags: trigger class = 4, correlation class = 4, Button 1 = 0x01
|
||||
assertEquals(0x04, inputSF[31]);
|
||||
assertEquals(0x04, inputSF[33]);
|
||||
assertEquals(0x01, inputSF[35]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
package haus.nightmare.lib3270j.screen;
|
||||
|
||||
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.AbstractDBCSCodePage;
|
||||
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
|
||||
import haus.nightmare.lib3270j.ecl.ECLField;
|
||||
import haus.nightmare.lib3270j.ecl.ECLFieldList;
|
||||
import haus.nightmare.lib3270j.input.InputProcessor;
|
||||
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
|
||||
|
||||
public class ScreenBufferPhase4Test {
|
||||
|
||||
private ScreenBuffer screen;
|
||||
private EbcdicTranslator translator;
|
||||
private InputProcessor input;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
translator = new EbcdicTranslator();
|
||||
screen = new ScreenBuffer(TerminalModel.IBM_3278_2, translator);
|
||||
input = new InputProcessor(screen, translator, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntryAssistDOCModeAndWordWrap() {
|
||||
screen.erase(false);
|
||||
screen.setEntryAssistDOCmode(true);
|
||||
screen.setEntryAssistWordWrap(true);
|
||||
screen.setEntryAssistStartColumn(0);
|
||||
screen.setEntryAssistEndColumn(20);
|
||||
|
||||
assertTrue(screen.isEntryAssistDOCmode());
|
||||
assertTrue(screen.isEntryAssistWordWrap());
|
||||
assertEquals(0, screen.getEntryAssistStartColumn());
|
||||
assertEquals(20, screen.getEntryAssistEndColumn());
|
||||
|
||||
// Type "HELLO WORLD " near column 20
|
||||
screen.setCursorPosition(0, 10);
|
||||
String s1 = "HELLO ";
|
||||
for (int i = 0; i < s1.length(); i++) {
|
||||
input.typeCharacter(s1.charAt(i));
|
||||
}
|
||||
assertEquals(16, screen.getCursorCol());
|
||||
assertEquals(0, screen.getCursorRow());
|
||||
|
||||
// Now type "TESTING" which starts at col 16 and crosses col 20
|
||||
String s2 = "TESTING";
|
||||
for (int i = 0; i < s2.length(); i++) {
|
||||
input.typeCharacter(s2.charAt(i));
|
||||
}
|
||||
|
||||
// "HELLO " should remain on row 0 (cols 10..15)
|
||||
assertEquals("HELLO ", screen.getString(10, 6));
|
||||
// "TESTING" should be soft-wrapped to row 1 (cols 0..6)
|
||||
assertEquals("TESTING", screen.getString(80, 7));
|
||||
assertEquals(1, screen.getCursorRow());
|
||||
assertEquals(7, screen.getCursorCol());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntryAssistWordWrapTypingSpaceAtEndMargin() {
|
||||
screen.erase(false);
|
||||
screen.setEntryAssistWordWrap(true);
|
||||
screen.setEntryAssistStartColumn(5);
|
||||
screen.setEntryAssistEndColumn(20);
|
||||
|
||||
screen.setCursorPosition(0, 20);
|
||||
input.typeCharacter(' ');
|
||||
|
||||
// Cursor should wrap to next line at startCol 5
|
||||
assertEquals(1, screen.getCursorRow());
|
||||
assertEquals(5, screen.getCursorCol());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEntryAssistTabStopsAndNavigation() {
|
||||
screen.erase(false);
|
||||
screen.setEntryAssistTabStops(new int[]{ 10, 25, 50, 70 });
|
||||
assertArrayEquals(new int[]{ 10, 25, 50, 70 }, screen.getEntryAssistTabStops());
|
||||
|
||||
screen.setCursorPosition(0, 0);
|
||||
screen.processWordTab(true);
|
||||
assertEquals(10, screen.getCursorCol());
|
||||
|
||||
screen.processWordTab(true);
|
||||
assertEquals(25, screen.getCursorCol());
|
||||
|
||||
screen.processWordTab(true);
|
||||
assertEquals(50, screen.getCursorCol());
|
||||
|
||||
screen.processWordTab(false);
|
||||
assertEquals(25, screen.getCursorCol());
|
||||
|
||||
screen.processWordTab(false);
|
||||
assertEquals(10, screen.getCursorCol());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessDeleteWord() {
|
||||
screen.erase(false);
|
||||
screen.setFieldAttribute(0, (byte) FA_PRINTABLE);
|
||||
screen.setFieldAttribute(40, (byte) (FA_PRINTABLE | FA_PROTECT));
|
||||
|
||||
// Field contains "ALPHA BRAVO CHARLIE"
|
||||
String text = "ALPHA BRAVO CHARLIE";
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
screen.setChar(0, 1 + i, text.charAt(i));
|
||||
}
|
||||
|
||||
// Place cursor at "BRAVO" (col 7)
|
||||
screen.setCursorPosition(0, 7);
|
||||
screen.processDeleteWord();
|
||||
|
||||
// "BRAVO " should be deleted, leaving "ALPHA CHARLIE"
|
||||
assertEquals("ALPHA CHARLIE", screen.getString(1, 13));
|
||||
assertTrue(screen.isModified(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDBCSSOSIPairBalancing() {
|
||||
EbcdicTranslator dbcsTranslator = new EbcdicTranslator("930");
|
||||
AbstractDBCSCodePage cp = (AbstractDBCSCodePage) dbcsTranslator.getCodePage();
|
||||
cp.registerDbcsPair(0x4341, '\u6771');
|
||||
|
||||
ScreenBuffer dbcsScreen = new ScreenBuffer(TerminalModel.IBM_3279_2, dbcsTranslator);
|
||||
dbcsScreen.erase(false);
|
||||
dbcsScreen.setFieldAttribute(0, (byte) FA_PRINTABLE);
|
||||
dbcsScreen.setFieldAttribute(20, (byte) (FA_PRINTABLE | FA_PROTECT));
|
||||
|
||||
// Insert DBCS character
|
||||
boolean inserted = dbcsScreen.insertChar(1, '\u6771');
|
||||
assertTrue(inserted);
|
||||
assertEquals(ExtendedAttribute.CS_DBCS, dbcsScreen.getCell(1).cs);
|
||||
assertEquals(ExtendedAttribute.DB_LEFT, dbcsScreen.getCell(1).db);
|
||||
assertEquals(ExtendedAttribute.DB_RIGHT, dbcsScreen.getCell(2).db);
|
||||
|
||||
// Delete DBCS character at left half
|
||||
boolean deleted = dbcsScreen.deleteChar(1);
|
||||
assertTrue(deleted);
|
||||
assertEquals(0, dbcsScreen.getCell(1).ec);
|
||||
assertEquals(0, dbcsScreen.getCell(2).ec);
|
||||
|
||||
// Test balanceSOSI removes adjacent empty SO SI
|
||||
dbcsScreen.setCell(3, 0x0E); // SO
|
||||
dbcsScreen.setCell(4, 0x0F); // SI
|
||||
dbcsScreen.balanceSOSI();
|
||||
assertEquals(0, dbcsScreen.getCell(3).ec);
|
||||
assertEquals(0, dbcsScreen.getCell(4).ec);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrappedFieldListHandling() {
|
||||
screen.erase(false);
|
||||
// Field 1 at pos 100..499
|
||||
screen.setFieldAttribute(100, (byte) FA_PRINTABLE);
|
||||
// Field 2 at pos 500..99 (wrapping around end of buffer)
|
||||
screen.setFieldAttribute(500, (byte) (FA_PRINTABLE | FA_PROTECT));
|
||||
|
||||
ECLFieldList list = screen.buildFieldList();
|
||||
assertEquals(2, list.getFieldCount());
|
||||
|
||||
ECLField f1 = list.getFirstField();
|
||||
assertNotNull(f1);
|
||||
assertEquals(100, f1.getStart());
|
||||
assertEquals(101, f1.getDataStart());
|
||||
assertEquals(499, f1.getEnd());
|
||||
assertEquals(399, f1.getLength());
|
||||
assertFalse(f1.isWrapped());
|
||||
assertFalse(f1.isProtected());
|
||||
|
||||
ECLField f2 = list.getNextField(f1);
|
||||
assertNotNull(f2);
|
||||
assertEquals(500, f2.getStart());
|
||||
assertEquals(501, f2.getDataStart());
|
||||
assertEquals(99, f2.getEnd());
|
||||
assertEquals(1920 - 500 - 1 + 100, f2.getLength());
|
||||
assertTrue(f2.isWrapped());
|
||||
assertTrue(f2.isProtected());
|
||||
|
||||
// Test containment across wrap
|
||||
assertTrue(f2.contains(500));
|
||||
assertTrue(f2.contains(501));
|
||||
assertTrue(f2.contains(1919));
|
||||
assertTrue(f2.contains(0));
|
||||
assertTrue(f2.contains(99));
|
||||
assertFalse(f2.contains(100));
|
||||
assertFalse(f2.contains(200));
|
||||
|
||||
// Test field lookup by position
|
||||
assertEquals(f2.getStart(), list.findField(50).getStart());
|
||||
assertEquals(f1.getStart(), list.findField(200).getStart());
|
||||
assertEquals(f2.getStart(), list.findField(600).getStart());
|
||||
|
||||
// Test navigation
|
||||
assertEquals(f2.getStart(), list.getPreviousField(f1).getStart());
|
||||
assertNull(list.getNextField(f2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleFieldFullBufferWrap() {
|
||||
screen.erase(false);
|
||||
screen.setFieldAttribute(250, (byte) FA_PRINTABLE);
|
||||
|
||||
ECLFieldList list = screen.buildFieldList();
|
||||
assertEquals(1, list.getFieldCount());
|
||||
|
||||
ECLField f = list.getFirstField();
|
||||
assertNotNull(f);
|
||||
assertEquals(250, f.getStart());
|
||||
assertEquals(251, f.getDataStart());
|
||||
assertEquals(249, f.getEnd());
|
||||
assertEquals(1919, f.getLength());
|
||||
assertTrue(f.isWrapped());
|
||||
|
||||
assertTrue(f.contains(0));
|
||||
assertTrue(f.contains(250));
|
||||
assertTrue(f.contains(1919));
|
||||
}
|
||||
}
|
||||
@@ -241,4 +241,51 @@ public class ProxyConnectionTest {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSystemPropertiesProxyFallback() throws Exception {
|
||||
try (ServerSocket proxyServer = new ServerSocket(0)) {
|
||||
int proxyPort = proxyServer.getLocalPort();
|
||||
CountDownLatch serverHandshakeDone = new CountDownLatch(1);
|
||||
|
||||
Thread serverThread = new Thread(() -> {
|
||||
try (Socket clientSock = proxyServer.accept()) {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(clientSock.getInputStream(), StandardCharsets.US_ASCII));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.isEmpty()) break;
|
||||
}
|
||||
OutputStream out = clientSock.getOutputStream();
|
||||
out.write("HTTP/1.1 200 OK\r\n\r\n".getBytes(StandardCharsets.US_ASCII));
|
||||
out.flush();
|
||||
serverHandshakeDone.countDown();
|
||||
} catch (Exception ignored) {}
|
||||
});
|
||||
serverThread.setDaemon(true);
|
||||
serverThread.start();
|
||||
|
||||
String origHost = System.getProperty("http.proxyHost");
|
||||
String origPort = System.getProperty("http.proxyPort");
|
||||
try {
|
||||
System.setProperty("http.proxyHost", "127.0.0.1");
|
||||
System.setProperty("http.proxyPort", String.valueOf(proxyPort));
|
||||
|
||||
ConnectionConfig config = new ConnectionConfig("test.fallback.org", 23);
|
||||
config.setConnectTimeoutMs(5000);
|
||||
|
||||
TelnetFSM fsm = new TelnetFSM(config, new haus.nightmare.lib3270j.screen.ScreenBuffer(TerminalModel.IBM_3279_4, new haus.nightmare.lib3270j.charset.EbcdicTranslator()), null);
|
||||
TelnetConnection connection = new TelnetConnection(config, fsm);
|
||||
|
||||
connection.connect();
|
||||
assertTrue(connection.isConnected());
|
||||
assertTrue(serverHandshakeDone.await(3, TimeUnit.SECONDS));
|
||||
connection.disconnect();
|
||||
} finally {
|
||||
if (origHost != null) System.setProperty("http.proxyHost", origHost);
|
||||
else System.clearProperty("http.proxyHost");
|
||||
if (origPort != null) System.setProperty("http.proxyPort", origPort);
|
||||
else System.clearProperty("http.proxyPort");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user