Rearrange settings, update sample config
Build and Test j3270 / Build JAR & Run Tests (Java 11) (push) Successful in 1m8s
Build and Test j3270 / Build JAR & Run Tests (Java 21) (push) Successful in 1m15s
Build and Test j3270 / Build JAR & Run Tests (Java 17) (push) Successful in 1m38s
Release j3270 / Build & Publish Release (push) Successful in 1m13s

This commit is contained in:
2026-09-15 17:59:26 +00:00
parent 7892229a2c
commit 754b1de7cb
22 changed files with 1530 additions and 237 deletions
@@ -546,6 +546,29 @@ public class ECLPS implements ECLConstants {
private boolean enablePasteFromExcel = true;
private boolean pasteStopAtProtectedLine = false;
private boolean blockPaste = true;
public boolean isBlockPaste() {
if (session != null && session.getProperties() != null) {
String p = session.getProperties().getProperty(ECLSession.BLOCK_PASTE);
if (p != null) return Boolean.parseBoolean(p);
}
return blockPaste;
}
public boolean IsBlockPaste() { return isBlockPaste(); }
public void setBlockPaste(boolean val) {
this.blockPaste = val;
if (session != null && session.getProperties() != null) {
session.getProperties().setProperty(ECLSession.BLOCK_PASTE, String.valueOf(val));
}
if (inputProcessor != null) {
inputProcessor.setBlockPaste(val);
}
}
public void SetBlockPaste(boolean val) { setBlockPaste(val); }
public boolean isEnablePasteFromExcel() {
if (session != null && session.getProperties() != null) {
@@ -591,7 +614,7 @@ public class ECLPS implements ECLConstants {
setCursorPos(row, col);
}
if (inputProcessor != null) {
return inputProcessor.pasteText(text, true, isPasteStopAtProtectedLine());
return inputProcessor.pasteText(text, true, isPasteStopAtProtectedLine(), isBlockPaste());
}
return 0;
}
@@ -605,9 +628,9 @@ public class ECLPS implements ECLConstants {
*/
public synchronized int pasteString(String text, int row, int col) {
if (text == null || text.isEmpty() || screen == null) return 0;
if (inputProcessor != null && (text.contains("\t") || isEnablePasteFromExcel() || isPasteStopAtProtectedLine())) {
if (inputProcessor != null && (text.contains("\t") || isEnablePasteFromExcel() || isPasteStopAtProtectedLine() || isBlockPaste())) {
setCursorPos(row, col);
return inputProcessor.pasteText(text, isEnablePasteFromExcel(), isPasteStopAtProtectedLine());
return inputProcessor.pasteText(text, isEnablePasteFromExcel(), isPasteStopAtProtectedLine(), isBlockPaste());
}
int rows = screen.getRows();
int cols = screen.getCols();
@@ -38,6 +38,7 @@ public class ECLSession {
public static final String ENABLE_PASTE_FROM_EXCEL = "enablePasteFromExcel";
public static final String PASTE_TAB_OPTIONS = "pasteTabOptions";
public static final String PASTE_STOP_AT_PROTECTED_LINE = "pasteStopAtProtectedLine";
public static final String BLOCK_PASTE = "blockPaste";
public static final String PASTE_FIELD_WRAP = "pasteFieldWrap";
public static final String PASTE_LINE_WRAP = "pasteLineWrap";
public static final String ENTRYASSIST_DOCMODE = "EntryAssist_DOCmode";
@@ -137,6 +137,10 @@ public class InputProcessor {
private boolean aplKeyboardMode = false;
private boolean numericFieldLock = true;
private boolean autoSkipEnabled = true;
private boolean blockPaste = true;
public boolean isBlockPaste() { return blockPaste; }
public void setBlockPaste(boolean val) { this.blockPaste = val; }
public void setBellListener(BellListener listener) { this.bellListener = listener; }
public BellListener getBellListener() { return bellListener; }
@@ -446,6 +450,19 @@ public class InputProcessor {
* @return number of characters pasted
*/
public int pasteText(String text, boolean enablePasteFromExcel, boolean pasteStopAtProtectedLine) {
return pasteText(text, enablePasteFromExcel, pasteStopAtProtectedLine, this.blockPaste);
}
/**
* Pastes text onto the screen with tabular, boundary, and block alignment controls.
*
* @param text text to paste
* @param enablePasteFromExcel whether to parse tabs as field advances and newlines as row advances
* @param pasteStopAtProtectedLine whether to halt paste when encountering protected boundaries
* @param blockPaste whether newlines align each line with the initial starting cursor column
* @return number of characters pasted
*/
public int pasteText(String text, boolean enablePasteFromExcel, boolean pasteStopAtProtectedLine, boolean blockPaste) {
if (text == null || text.isEmpty() || screen == null || keyboardLocked) {
return 0;
}
@@ -463,6 +480,7 @@ public class InputProcessor {
int count = 0;
int len = text.length();
int i = 0;
int startCol = screen.getCursorCol();
while (i < len && !keyboardLocked) {
char ch = text.charAt(i);
@@ -472,7 +490,34 @@ public class InputProcessor {
if (ch == '\r' && (i + 1) < len && text.charAt(i + 1) == '\n') {
i++; // skip \n of \r\n
}
if (enablePasteFromExcel) {
if (blockPaste) {
int curRow = screen.getCursorRow();
int nextRow = (curRow + 1) % screen.getRows();
if (pasteStopAtProtectedLine && isLineProtected(nextRow)) {
break; // Stop paste when next line is protected
}
int cols = screen.getCols();
int rows = screen.getRows();
int targetAddr = nextRow * cols + startCol;
if (screen.isFormatted()) {
int size = rows * cols;
ExtendedAttribute ea = screen.getCell(targetAddr);
if (ea.isFieldAttribute()) {
targetAddr = (targetAddr + 1) % size;
}
byte faVal = screen.getFieldAttributeAt(targetAddr);
if (faIsProtected(faVal & 0xFF)) {
if (pasteStopAtProtectedLine) {
break;
} else {
targetAddr = screen.findNextUnprotected(targetAddr == 0 ? (size - 1) : (targetAddr - 1));
}
}
}
screen.setCursorAddress(targetAddr);
screen.markAllChanged();
screen.updateDisplaySnapshot();
} else if (enablePasteFromExcel) {
int curRow = screen.getCursorRow();
int nextRow = (curRow + 1) % screen.getRows();
if (pasteStopAtProtectedLine && isLineProtected(nextRow)) {
@@ -0,0 +1,144 @@
package haus.nightmare.lib3270j.input;
import haus.nightmare.lib3270j.TerminalModel;
import haus.nightmare.lib3270j.charset.EbcdicTranslator;
import haus.nightmare.lib3270j.ecl.ECLPS;
import haus.nightmare.lib3270j.screen.ScreenBuffer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static haus.nightmare.lib3270j.protocol.DS3270Constants.*;
import static org.junit.jupiter.api.Assertions.*;
/**
* Unit tests for the "Block paste" feature.
* Ensures that multi-line pastes maintain column alignment with the initial start column
* when blockPaste is active (the default), and revert to standard 3270 Newline field advance
* when blockPaste is disabled.
*/
public class BlockPasteTest {
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 testBlockPasteDefaultsToTrue() {
assertTrue(input.isBlockPaste(), "InputProcessor blockPaste should default to true");
assertTrue(ps.isBlockPaste(), "ECLPS blockPaste should default to true");
}
@Test
public void testBlockPasteAlignsStartColumnInUnformattedScreen() {
screen.erase(false);
// Start cursor at row 0, column 15
int startPos = screen.rowColToAddress(0, 15);
screen.setCursorAddress(startPos);
String multilineText = "FIRST\nSECOND\nTHIRD";
int pasted = input.pasteText(multilineText, true, false, true);
assertEquals(16, pasted);
// Verify row 0, col 15-19 has "FIRST"
assertEquals("FIRST", ps.getString(startPos, 5));
// Verify row 1, col 15-20 has "SECOND"
int row1Col15 = screen.rowColToAddress(1, 15);
assertEquals("SECOND", ps.getString(row1Col15, 6));
// Verify row 2, col 15-19 has "THIRD"
int row2Col15 = screen.rowColToAddress(2, 15);
assertEquals("THIRD", ps.getString(row2Col15, 5));
// Verify start of row 1 (cols 0-5) did NOT receive the paste
int row1Col0 = screen.rowColToAddress(1, 0);
assertNotEquals("SECOND", ps.getString(row1Col0, 6));
}
@Test
public void testBlockPasteAlignsStartColumnInFormattedScreen() {
screen.erase(false);
// Set up 3 rows each with an unprotected field starting at col 0 (FA at 0, 80, 160)
screen.setFieldAttribute(0, (byte) FA_PRINTABLE);
screen.setFieldAttribute(80, (byte) FA_PRINTABLE);
screen.setFieldAttribute(160, (byte) FA_PRINTABLE);
// Start pasting at Row 0, Col 25
int startPos = screen.rowColToAddress(0, 25);
screen.setCursorAddress(startPos);
String multilineText = "ALPHA\r\nBETA\r\nGAMMA";
int pasted = input.pasteText(multilineText, true, false, true);
assertEquals(14, pasted);
assertEquals("ALPHA", ps.getString(screen.rowColToAddress(0, 25), 5));
assertEquals("BETA", ps.getString(screen.rowColToAddress(1, 25), 4));
assertEquals("GAMMA", ps.getString(screen.rowColToAddress(2, 25), 5));
}
@Test
public void testDisabledBlockPasteRevertsToFirstUnprotectedField() {
screen.erase(false);
// Row 0: FA at 0 (unprotected col 1-79)
// Row 1: FA at 80 (unprotected col 1-79)
screen.setFieldAttribute(0, (byte) FA_PRINTABLE);
screen.setFieldAttribute(80, (byte) FA_PRINTABLE);
// Start pasting at Row 0, Col 25
int startPos = screen.rowColToAddress(0, 25);
screen.setCursorAddress(startPos);
// blockPaste = false: newline should advance to first unprotected field on next row (pos 81 / col 1)
String multilineText = "LINE1\nLINE2";
input.pasteText(multilineText, true, false, false);
assertEquals("LINE1", ps.getString(screen.rowColToAddress(0, 25), 5));
// LINE2 must be at row 1, col 1 (pos 81) rather than col 25
assertEquals("LINE2", ps.getString(81, 5));
}
@Test
public void testBlockPasteStopAtProtectedBoundary() {
screen.erase(false);
// Row 0: unprotected field at pos 0
screen.setFieldAttribute(0, (byte) FA_PRINTABLE);
// Row 1: entirely protected row
screen.setFieldAttribute(80, (byte) (FA_PRINTABLE | FA_PROTECT));
int startPos = screen.rowColToAddress(0, 10);
screen.setCursorAddress(startPos);
// pasteStopAtProtectedLine = true
String multilineText = "ROW0\nROW1";
int pasted = input.pasteText(multilineText, true, true, true);
// Should paste "ROW0" (4 chars) and halt when row 1 is protected
assertEquals(4, pasted);
assertEquals("ROW0", ps.getString(startPos, 4));
}
@Test
public void testEclPsPasteStringHonorsBlockPaste() {
screen.erase(false);
screen.setFieldAttribute(0, (byte) FA_PRINTABLE);
screen.setFieldAttribute(80, (byte) FA_PRINTABLE);
ps.setBlockPaste(true);
assertTrue(ps.isBlockPaste());
int pasted = ps.pasteString("DATA1\nDATA2", 0, 12);
assertEquals(10, pasted);
assertEquals("DATA1", ps.getString(screen.rowColToAddress(0, 12), 5));
assertEquals("DATA2", ps.getString(screen.rowColToAddress(1, 12), 5));
}
}