X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fjexer%2FTEditorWidget.java;h=a329dfa347b770dcb279140a1c5ef170f72aab64;hb=17b598e94cf9afb2022d6d0a842c6b5c3e9fc6cb;hp=8b105f8ba62bb2d3222590eceb28d82ab6c57229;hpb=39e863978f5c6de901b05b3ecfaee18aa934663d;p=fanfix.git diff --git a/src/jexer/TEditorWidget.java b/src/jexer/TEditorWidget.java index 8b105f8..a329dfa 100644 --- a/src/jexer/TEditorWidget.java +++ b/src/jexer/TEditorWidget.java @@ -31,19 +31,22 @@ package jexer; import java.io.IOException; import jexer.bits.CellAttributes; +import jexer.bits.StringUtils; +import jexer.event.TCommandEvent; import jexer.event.TKeypressEvent; import jexer.event.TMouseEvent; import jexer.event.TResizeEvent; import jexer.teditor.Document; import jexer.teditor.Line; import jexer.teditor.Word; +import static jexer.TCommand.*; import static jexer.TKeypress.*; /** * TEditorWidget displays an editable text document. It is unaware of * scrolling behavior, but can respond to mouse and keyboard events. */ -public class TEditorWidget extends TWidget { +public class TEditorWidget extends TWidget implements EditMenuUser { // ------------------------------------------------------------------------ // Constants -------------------------------------------------------------- @@ -78,6 +81,31 @@ public class TEditorWidget extends TWidget { */ private int leftColumn = 0; + /** + * If true, the mouse is dragging a selection. + */ + private boolean inSelection = false; + + /** + * Selection starting column. + */ + private int selectionColumn0; + + /** + * Selection starting line. + */ + private int selectionLine0; + + /** + * Selection ending column. + */ + private int selectionColumn1; + + /** + * Selection ending line. + */ + private int selectionLine1; + // ------------------------------------------------------------------------ // Constructors ----------------------------------------------------------- // ------------------------------------------------------------------------ @@ -105,36 +133,9 @@ public class TEditorWidget extends TWidget { } // ------------------------------------------------------------------------ - // TWidget ---------------------------------------------------------------- + // Event handlers --------------------------------------------------------- // ------------------------------------------------------------------------ - /** - * Draw the text box. - */ - @Override - public void draw() { - for (int i = 0; i < getHeight(); i++) { - // Background line - getScreen().hLineXY(0, i, getWidth(), ' ', defaultColor); - - // Now draw document's line - if (topLine + i < document.getLineCount()) { - Line line = document.getLine(topLine + i); - int x = 0; - for (Word word: line.getWords()) { - // For now, we are cheating: draw outside the left region - // if needed and let screen do the clipping. - getScreen().putStringXY(x - leftColumn, i, word.getText(), - word.getColor()); - x += word.getDisplayLength(); - if (x - leftColumn > getWidth()) { - break; - } - } - } - } - } - /** * Handle mouse press events. * @@ -162,6 +163,76 @@ public class TEditorWidget extends TWidget { } if (mouse.isMouse1()) { + // Selection. + inSelection = true; + selectionColumn0 = leftColumn + mouse.getX(); + selectionLine0 = topLine + mouse.getY(); + selectionColumn1 = selectionColumn0; + selectionLine1 = selectionLine0; + + // Set the row and column + int newLine = topLine + mouse.getY(); + int newX = leftColumn + mouse.getX(); + if (newLine > document.getLineCount() - 1) { + // Go to the end + document.setLineNumber(document.getLineCount() - 1); + document.end(); + if (newLine > document.getLineCount() - 1) { + setCursorY(document.getLineCount() - 1 - topLine); + } else { + setCursorY(mouse.getY()); + } + alignCursor(); + if (inSelection) { + selectionColumn1 = document.getCursor(); + selectionLine1 = document.getLineNumber(); + } + return; + } + + document.setLineNumber(newLine); + setCursorY(mouse.getY()); + if (newX >= document.getCurrentLine().getDisplayLength()) { + document.end(); + alignCursor(); + } else { + document.setCursor(newX); + setCursorX(mouse.getX()); + } + if (inSelection) { + selectionColumn1 = document.getCursor(); + selectionLine1 = document.getLineNumber(); + } + return; + } else { + inSelection = false; + } + + // Pass to children + super.onMouseDown(mouse); + } + + /** + * Handle mouse motion events. + * + * @param mouse mouse motion event + */ + @Override + public void onMouseMotion(final TMouseEvent mouse) { + + if (mouse.isMouse1()) { + // Selection. + if (inSelection) { + selectionColumn1 = leftColumn + mouse.getX(); + selectionLine1 = topLine + mouse.getY(); + } else { + inSelection = true; + selectionColumn0 = leftColumn + mouse.getX(); + selectionLine0 = topLine + mouse.getY(); + selectionColumn1 = selectionColumn0; + selectionLine1 = selectionLine0; + } + // Set the row and column int newLine = topLine + mouse.getY(); int newX = leftColumn + mouse.getX(); @@ -175,6 +246,10 @@ public class TEditorWidget extends TWidget { setCursorY(mouse.getY()); } alignCursor(); + if (inSelection) { + selectionColumn1 = document.getCursor(); + selectionLine1 = document.getLineNumber(); + } return; } @@ -187,6 +262,10 @@ public class TEditorWidget extends TWidget { document.setCursor(newX); setCursorX(mouse.getX()); } + if (inSelection) { + selectionColumn1 = document.getCursor(); + selectionLine1 = document.getLineNumber(); + } return; } @@ -201,25 +280,77 @@ public class TEditorWidget extends TWidget { */ @Override public void onKeypress(final TKeypressEvent keypress) { - if (keypress.equals(kbLeft)) { + if (keypress.getKey().isShift()) { + // Selection. + if (!inSelection) { + inSelection = true; + selectionColumn0 = document.getCursor(); + selectionLine0 = document.getLineNumber(); + selectionColumn1 = selectionColumn0; + selectionLine1 = selectionLine0; + } + } else { + if (keypress.equals(kbLeft) + || keypress.equals(kbRight) + || keypress.equals(kbUp) + || keypress.equals(kbDown) + || keypress.equals(kbPgDn) + || keypress.equals(kbPgUp) + || keypress.equals(kbHome) + || keypress.equals(kbEnd) + ) { + // Non-shifted navigation keys disable selection. + inSelection = false; + } + } + + if (keypress.equals(kbLeft) + || keypress.equals(kbShiftLeft) + ) { document.left(); alignTopLine(false); - } else if (keypress.equals(kbRight)) { + } else if (keypress.equals(kbRight) + || keypress.equals(kbShiftRight) + ) { document.right(); alignTopLine(true); - } else if (keypress.equals(kbUp)) { + } else if (keypress.equals(kbAltLeft) + || keypress.equals(kbCtrlLeft) + || keypress.equals(kbAltShiftLeft) + || keypress.equals(kbCtrlShiftLeft) + ) { + document.backwardsWord(); + alignTopLine(false); + } else if (keypress.equals(kbAltRight) + || keypress.equals(kbCtrlRight) + || keypress.equals(kbAltShiftRight) + || keypress.equals(kbCtrlShiftRight) + ) { + document.forwardsWord(); + alignTopLine(true); + } else if (keypress.equals(kbUp) + || keypress.equals(kbShiftUp) + ) { document.up(); alignTopLine(false); - } else if (keypress.equals(kbDown)) { + } else if (keypress.equals(kbDown) + || keypress.equals(kbShiftDown) + ) { document.down(); alignTopLine(true); - } else if (keypress.equals(kbPgUp)) { + } else if (keypress.equals(kbPgUp) + || keypress.equals(kbShiftPgUp) + ) { document.up(getHeight() - 1); alignTopLine(false); - } else if (keypress.equals(kbPgDn)) { + } else if (keypress.equals(kbPgDn) + || keypress.equals(kbShiftPgDn) + ) { document.down(getHeight() - 1); alignTopLine(true); - } else if (keypress.equals(kbHome)) { + } else if (keypress.equals(kbHome) + || keypress.equals(kbShiftHome) + ) { if (document.home()) { leftColumn = 0; if (leftColumn < 0) { @@ -227,39 +358,54 @@ public class TEditorWidget extends TWidget { } setCursorX(0); } - } else if (keypress.equals(kbEnd)) { + } else if (keypress.equals(kbEnd) + || keypress.equals(kbShiftEnd) + ) { if (document.end()) { alignCursor(); } - } else if (keypress.equals(kbCtrlHome)) { + } else if (keypress.equals(kbCtrlHome) + || keypress.equals(kbCtrlShiftHome) + ) { document.setLineNumber(0); document.home(); topLine = 0; leftColumn = 0; setCursorX(0); setCursorY(0); - } else if (keypress.equals(kbCtrlEnd)) { + } else if (keypress.equals(kbCtrlEnd) + || keypress.equals(kbCtrlShiftEnd) + ) { document.setLineNumber(document.getLineCount() - 1); document.end(); alignTopLine(false); } else if (keypress.equals(kbIns)) { document.setOverwrite(!document.getOverwrite()); } else if (keypress.equals(kbDel)) { - document.del(); + if (inSelection) { + deleteSelection(); + } else { + document.del(); + } alignCursor(); } else if (keypress.equals(kbBackspace) || keypress.equals(kbBackspaceDel) ) { - document.backspace(); + if (inSelection) { + deleteSelection(); + } else { + document.backspace(); + } alignTopLine(false); } else if (keypress.equals(kbTab)) { - // TODO: tab character. For now just add spaces until we hit - // modulo 8. + deleteSelection(); + // Add spaces until we hit modulo 8. for (int i = document.getCursor(); (i + 1) % 8 != 0; i++) { document.addChar(' '); } alignCursor(); } else if (keypress.equals(kbEnter)) { + deleteSelection(); document.enter(); alignTopLine(true); } else if (!keypress.getKey().isFnKey() @@ -267,13 +413,18 @@ public class TEditorWidget extends TWidget { && !keypress.getKey().isCtrl() ) { // Plain old keystroke, process it - // TODO: fix document to use ints, not chars + deleteSelection(); document.addChar(keypress.getKey().getChar()); alignCursor(); } else { // Pass other keys (tab etc.) on to TWidget super.onKeypress(keypress); } + + if (inSelection) { + selectionColumn1 = document.getCursor(); + selectionLine1 = document.getLineNumber(); + } } /** @@ -304,6 +455,129 @@ public class TEditorWidget extends TWidget { } } + /** + * Handle posted command events. + * + * @param command command event + */ + @Override + public void onCommand(final TCommandEvent command) { + if (command.equals(cmCut)) { + // Copy text to clipboard, and then remove it. + copySelection(); + deleteSelection(); + return; + } + + if (command.equals(cmCopy)) { + // Copy text to clipboard. + copySelection(); + return; + } + + if (command.equals(cmPaste)) { + // Delete selected text, then paste text from clipboard. + deleteSelection(); + + String text = getClipboard().pasteText(); + if (text != null) { + for (int i = 0; i < text.length(); ) { + int ch = text.codePointAt(i); + onKeypress(new TKeypressEvent(false, 0, ch, false, false, + false)); + i += Character.charCount(ch); + } + } + return; + } + + if (command.equals(cmClear)) { + // Remove text. + deleteSelection(); + return; + } + + } + + // ------------------------------------------------------------------------ + // TWidget ---------------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Draw the text box. + */ + @Override + public void draw() { + CellAttributes selectedColor = getTheme().getColor("teditor.selected"); + + int startCol = selectionColumn0; + int startRow = selectionLine0; + int endCol = selectionColumn1; + int endRow = selectionLine1; + + if (((selectionColumn1 < selectionColumn0) + && (selectionLine1 <= selectionLine0)) + || ((selectionColumn1 <= selectionColumn0) + && (selectionLine1 < selectionLine0)) + ) { + // The user selected from bottom-right to top-left. Reverse the + // coordinates for the inverted section. + startCol = selectionColumn1; + startRow = selectionLine1; + endCol = selectionColumn0; + endRow = selectionLine0; + } + + for (int i = 0; i < getHeight(); i++) { + // Background line + getScreen().hLineXY(0, i, getWidth(), ' ', defaultColor); + + // Now draw document's line + if (topLine + i < document.getLineCount()) { + Line line = document.getLine(topLine + i); + int x = 0; + for (Word word: line.getWords()) { + // For now, we are cheating: draw outside the left region + // if needed and let screen do the clipping. + getScreen().putStringXY(x - leftColumn, i, word.getText(), + word.getColor()); + x += word.getDisplayLength(); + if (x - leftColumn > getWidth()) { + break; + } + } + + // Highlight selected region + if (inSelection) { + if (startRow == endRow) { + if (topLine + i == startRow) { + for (x = startCol; x <= endCol; x++) { + putAttrXY(x - leftColumn, i, selectedColor); + } + } + } else { + if (topLine + i == startRow) { + for (x = startCol; x < line.getDisplayLength(); x++) { + putAttrXY(x - leftColumn, i, selectedColor); + } + } else if (topLine + i == endRow) { + for (x = 0; x <= endCol; x++) { + putAttrXY(x - leftColumn, i, selectedColor); + } + } else if ((topLine + i >= startRow) + && (topLine + i <= endRow) + ) { + for (x = 0; x < getWidth(); x++) { + putAttrXY(x, i, selectedColor); + } + } + } + } + + } + } + } + // ------------------------------------------------------------------------ // TEditorWidget ---------------------------------------------------------- // ------------------------------------------------------------------------ @@ -452,6 +726,29 @@ public class TEditorWidget extends TWidget { } } + /** + * Set the current visible column number. 1-based. + * + * @return the visible column number. Column 1 is the first column. + */ + public int getVisibleColumnNumber() { + return leftColumn + 1; + } + + /** + * Set the current visible column number. 1-based. + * + * @param column the new visible column number. Column 1 is the first + * column. + */ + public void setVisibleColumnNumber(final int column) { + assert (column > 0); + if ((column > 0) && (column < document.getLineLengthMax())) { + leftColumn = column - 1; + alignDocument(true); + } + } + /** * Get the current editing column number. 1-based. * @@ -511,4 +808,167 @@ public class TEditorWidget extends TWidget { document.saveToFilename(filename); } + /** + * Delete text within the selection bounds. + */ + private void deleteSelection() { + if (inSelection == false) { + return; + } + inSelection = false; + + int startCol = selectionColumn0; + int startRow = selectionLine0; + int endCol = selectionColumn1; + int endRow = selectionLine1; + + if (((selectionColumn1 < selectionColumn0) + && (selectionLine1 <= selectionLine0)) + || ((selectionColumn1 <= selectionColumn0) + && (selectionLine1 < selectionLine0)) + ) { + // The user selected from bottom-right to top-left. Reverse the + // coordinates for the inverted section. + startCol = selectionColumn1; + startRow = selectionLine1; + endCol = selectionColumn0; + endRow = selectionLine0; + } + + // Place the cursor on the selection end, and "press backspace" until + // the cursor matches the selection start. + document.setLineNumber(endRow); + document.setCursor(endCol + 1); + while (!((document.getLineNumber() == startRow) + && (document.getCursor() == startCol)) + ) { + document.backspace(); + } + alignTopLine(true); + } + + /** + * Copy text within the selection bounds to clipboard. + */ + private void copySelection() { + if (inSelection == false) { + return; + } + + int startCol = selectionColumn0; + int startRow = selectionLine0; + int endCol = selectionColumn1; + int endRow = selectionLine1; + + if (((selectionColumn1 < selectionColumn0) + && (selectionLine1 <= selectionLine0)) + || ((selectionColumn1 <= selectionColumn0) + && (selectionLine1 < selectionLine0)) + ) { + // The user selected from bottom-right to top-left. Reverse the + // coordinates for the inverted section. + startCol = selectionColumn1; + startRow = selectionLine1; + endCol = selectionColumn0; + endRow = selectionLine0; + } + + StringBuilder sb = new StringBuilder(); + + if (endRow > startRow) { + // First line + String line = document.getLine(startRow).getRawString(); + int x = 0; + for (int i = 0; i < line.length(); ) { + int ch = line.codePointAt(i); + + if (x >= startCol) { + sb.append(Character.toChars(ch)); + } + x += StringUtils.width(ch); + i += Character.charCount(ch); + } + sb.append("\n"); + + // Middle lines + for (int y = startRow + 1; y < endRow; y++) { + sb.append(document.getLine(y).getRawString()); + sb.append("\n"); + } + + // Final line + line = document.getLine(endRow).getRawString(); + x = 0; + for (int i = 0; i < line.length(); ) { + int ch = line.codePointAt(i); + + if (x > endCol) { + break; + } + + sb.append(Character.toChars(ch)); + x += StringUtils.width(ch); + i += Character.charCount(ch); + } + } else { + assert (startRow == endRow); + + // Only one line + String line = document.getLine(startRow).getRawString(); + int x = 0; + for (int i = 0; i < line.length(); ) { + int ch = line.codePointAt(i); + + if ((x >= startCol) && (x <= endCol)) { + sb.append(Character.toChars(ch)); + } + + x += StringUtils.width(ch); + i += Character.charCount(ch); + } + } + + getClipboard().copyText(sb.toString()); + } + + // ------------------------------------------------------------------------ + // EditMenuUser ----------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Check if the cut menu item should be enabled. + * + * @return true if the cut menu item should be enabled + */ + public boolean isEditMenuCut() { + return true; + } + + /** + * Check if the copy menu item should be enabled. + * + * @return true if the copy menu item should be enabled + */ + public boolean isEditMenuCopy() { + return true; + } + + /** + * Check if the paste menu item should be enabled. + * + * @return true if the paste menu item should be enabled + */ + public boolean isEditMenuPaste() { + return true; + } + + /** + * Check if the clear menu item should be enabled. + * + * @return true if the clear menu item should be enabled + */ + public boolean isEditMenuClear() { + return true; + } + }