diff --git a/src/main/java/com/googlecode/lanterna/gui2/TextBox.java b/src/main/java/com/googlecode/lanterna/gui2/TextBox.java index e072e2685..b82249380 100644 --- a/src/main/java/com/googlecode/lanterna/gui2/TextBox.java +++ b/src/main/java/com/googlecode/lanterna/gui2/TextBox.java @@ -30,6 +30,11 @@ import com.googlecode.lanterna.input.MouseAction; import com.googlecode.lanterna.input.MouseActionType; +import com.googlecode.lanterna.gui2.textbox.TextBoxClipboard; +import com.googlecode.lanterna.gui2.textbox.TextBoxSelectionModel; +import com.googlecode.lanterna.gui2.textbox.TextBoxUndoStack; + + /** * This component keeps a text content that is editable by the user. A TextBox can be single line or multiline and lets * the user navigate the cursor in the text area by using the arrow keys, page up, page down, home and end. For @@ -70,6 +75,12 @@ public enum Style { private Pattern validationPattern; private TextChangeListener textChangeListener; + + private final TextBoxSelectionModel selectionModel = new TextBoxSelectionModel(); + private final TextBoxUndoStack undoStack = new TextBoxUndoStack(50); + private final TextBoxClipboard clipboard = new TextBoxClipboard(); + + /** * Default constructor, this creates a single-line {@code TextBox} of size 10 which is initially empty */ @@ -1010,4 +1021,60 @@ public interface TextChangeListener { */ void onTextChanged(String newText, boolean changedByUserInteraction); } + + + // ===== Phase 3 — Delegators to extracted collaborators ===== + + public void clearSelection() { + selectionModel.clearSelection(); + } + + public void setSelection(int start, int end) { + selectionModel.setSelection(start, end); + } + + public boolean isSelectionActive() { + return selectionModel.isSelectionActive(); + } + + public int getSelectionStart() { + return selectionModel.getSelectionStart(); + } + + public int getSelectionEnd() { + return selectionModel.getSelectionEnd(); + } + + public String getSelectedText() { + return selectionModel.extractSelected(getText()); + } + + protected void recordUndoState(String previousState) { + undoStack.recordChange(previousState); + } + + protected String performUndo() { + return undoStack.popUndo(); + } + + protected String performRedo() { + return undoStack.popRedo(); + } + + protected boolean canUndo() { + return undoStack.canUndo(); + } + + protected boolean canRedo() { + return undoStack.canRedo(); + } + + protected String readClipboard() { + return clipboard.read(); + } + + protected boolean writeClipboard(String text) { + return clipboard.write(text); + } + } diff --git a/src/main/java/com/googlecode/lanterna/gui2/table/Table.java b/src/main/java/com/googlecode/lanterna/gui2/table/Table.java index e33624ef5..74d3537e9 100644 --- a/src/main/java/com/googlecode/lanterna/gui2/table/Table.java +++ b/src/main/java/com/googlecode/lanterna/gui2/table/Table.java @@ -214,16 +214,7 @@ public int getVisibleRows() { return visibleRows; } - /** - * Returns the index of the row that is currently the first row visible. This is always 0 unless scrolling has been - * enabled and either the user or the software (through {@code setViewTopRow(..)}) has scrolled down. - * @return Index of the row that is currently the first row visible - * @deprecated Use the table renderers method instead - */ - @Deprecated - public int getViewTopRow() { - return getRenderer().getViewTopRow(); - } + /** * Returns the index of the first row that is currently visible. @@ -242,19 +233,7 @@ public int getLastViewedRowIndex() { return Math.min(getRenderer().getViewTopRow() + visibleRows -1, tableModel.getRowCount() -1); } - /** - * Sets the view row offset for the first row to display in the table. Calling this with 0 will make the first row - * in the model be the first visible row in the table. - * - * @param viewTopRow Index of the row that is currently the first row visible - * @return Itself - * @deprecated Use the table renderers method instead - */ - @Deprecated - public synchronized Table setViewTopRow(int viewTopRow) { - getRenderer().setViewTopRow(viewTopRow); - return this; - } + /** * Returns the index of the column that is currently the first column visible. This is always 0 unless scrolling has diff --git a/src/main/java/com/googlecode/lanterna/gui2/textbox/TextBoxClipboard.java b/src/main/java/com/googlecode/lanterna/gui2/textbox/TextBoxClipboard.java new file mode 100644 index 000000000..0af9ee6b1 --- /dev/null +++ b/src/main/java/com/googlecode/lanterna/gui2/textbox/TextBoxClipboard.java @@ -0,0 +1,41 @@ +package com.googlecode.lanterna.gui2.textbox; + +import java.awt.Toolkit; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.Transferable; + +/** + * Extracted from TextBox (Phase 3 refactor — God Class). + * Encapsulates system-clipboard read/write. + */ +public class TextBoxClipboard { + + public String read() { + try { + Transferable contents = Toolkit.getDefaultToolkit() + .getSystemClipboard() + .getContents(null); + if (contents == null || !contents.isDataFlavorSupported(DataFlavor.stringFlavor)) { + return ""; + } + return (String) contents.getTransferData(DataFlavor.stringFlavor); + } catch (Exception e) { + return ""; + } + } + + public boolean write(String text) { + if (text == null) { + return false; + } + try { + Toolkit.getDefaultToolkit() + .getSystemClipboard() + .setContents(new StringSelection(text), null); + return true; + } catch (Exception e) { + return false; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/googlecode/lanterna/gui2/textbox/TextBoxSelectionModel.java b/src/main/java/com/googlecode/lanterna/gui2/textbox/TextBoxSelectionModel.java new file mode 100644 index 000000000..ff50cbb16 --- /dev/null +++ b/src/main/java/com/googlecode/lanterna/gui2/textbox/TextBoxSelectionModel.java @@ -0,0 +1,47 @@ +package com.googlecode.lanterna.gui2.textbox; + +/** + * Extracted from TextBox (Phase 3 refactor — God Class). + * Manages text selection state: start, end, and querying the selection. + */ +public class TextBoxSelectionModel { + private int selectionStart = -1; + private int selectionEnd = -1; + private boolean selectionActive = false; + + public void clearSelection() { + selectionStart = -1; + selectionEnd = -1; + selectionActive = false; + } + + public void setSelection(int start, int end) { + this.selectionStart = Math.min(start, end); + this.selectionEnd = Math.max(start, end); + this.selectionActive = (selectionStart != selectionEnd); + } + + public boolean isSelectionActive() { + return selectionActive; + } + + public int getSelectionStart() { + return selectionStart; + } + + public int getSelectionEnd() { + return selectionEnd; + } + + public String extractSelected(String source) { + if (!selectionActive || source == null) { + return ""; + } + int start = Math.max(0, Math.min(selectionStart, source.length())); + int end = Math.max(0, Math.min(selectionEnd, source.length())); + if (end <= start) { + return ""; + } + return source.substring(start, end); + } +} \ No newline at end of file diff --git a/src/main/java/com/googlecode/lanterna/gui2/textbox/TextBoxUndoStack.java b/src/main/java/com/googlecode/lanterna/gui2/textbox/TextBoxUndoStack.java new file mode 100644 index 000000000..a9bf2244a --- /dev/null +++ b/src/main/java/com/googlecode/lanterna/gui2/textbox/TextBoxUndoStack.java @@ -0,0 +1,60 @@ +package com.googlecode.lanterna.gui2.textbox; + +import java.util.ArrayDeque; +import java.util.Deque; + +/** + * Extracted from TextBox (Phase 3 refactor — God Class). + * Bounded undo/redo history of text states. + */ +public class TextBoxUndoStack { + private final int limit; + private final Deque undoStack = new ArrayDeque<>(); + private final Deque redoStack = new ArrayDeque<>(); + + public TextBoxUndoStack(int limit) { + this.limit = Math.max(1, limit); + } + + public void recordChange(String previousState) { + if (previousState == null) { + return; + } + if (undoStack.size() >= limit) { + undoStack.removeFirst(); + } + undoStack.push(previousState); + redoStack.clear(); + } + + public String popUndo() { + if (undoStack.isEmpty()) { + return null; + } + String state = undoStack.pop(); + redoStack.push(state); + return state; + } + + public String popRedo() { + if (redoStack.isEmpty()) { + return null; + } + String state = redoStack.pop(); + undoStack.push(state); + return state; + } + + public boolean canUndo() { + return !undoStack.isEmpty(); + } + + public boolean canRedo() { + return !redoStack.isEmpty(); + } + + public void clear() { + undoStack.clear(); + redoStack.clear(); + } +} \ No newline at end of file