help system
[fanfix.git] / src / jexer / TEditorWidget.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2019 Kevin Lamonte
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
29 package jexer;
30
31 import java.io.IOException;
32
33 import jexer.bits.CellAttributes;
34 import jexer.bits.StringUtils;
35 import jexer.event.TCommandEvent;
36 import jexer.event.TKeypressEvent;
37 import jexer.event.TMouseEvent;
38 import jexer.event.TResizeEvent;
39 import jexer.teditor.Document;
40 import jexer.teditor.Line;
41 import jexer.teditor.Word;
42 import static jexer.TCommand.*;
43 import static jexer.TKeypress.*;
44
45 /**
46 * TEditorWidget displays an editable text document. It is unaware of
47 * scrolling behavior, but can respond to mouse and keyboard events.
48 */
49 public class TEditorWidget extends TWidget implements EditMenuUser {
50
51 // ------------------------------------------------------------------------
52 // Constants --------------------------------------------------------------
53 // ------------------------------------------------------------------------
54
55 /**
56 * The number of lines to scroll on mouse wheel up/down.
57 */
58 private static final int wheelScrollSize = 3;
59
60 // ------------------------------------------------------------------------
61 // Variables --------------------------------------------------------------
62 // ------------------------------------------------------------------------
63
64 /**
65 * The document being edited.
66 */
67 private Document document;
68
69 /**
70 * The default color for the TEditor class.
71 */
72 private CellAttributes defaultColor = null;
73
74 /**
75 * The topmost line number in the visible area. 0-based.
76 */
77 private int topLine = 0;
78
79 /**
80 * The leftmost column number in the visible area. 0-based.
81 */
82 private int leftColumn = 0;
83
84 /**
85 * If true, the mouse is dragging a selection.
86 */
87 private boolean inSelection = false;
88
89 /**
90 * Selection starting column.
91 */
92 private int selectionColumn0;
93
94 /**
95 * Selection starting line.
96 */
97 private int selectionLine0;
98
99 /**
100 * Selection ending column.
101 */
102 private int selectionColumn1;
103
104 /**
105 * Selection ending line.
106 */
107 private int selectionLine1;
108
109 // ------------------------------------------------------------------------
110 // Constructors -----------------------------------------------------------
111 // ------------------------------------------------------------------------
112
113 /**
114 * Public constructor.
115 *
116 * @param parent parent widget
117 * @param text text on the screen
118 * @param x column relative to parent
119 * @param y row relative to parent
120 * @param width width of text area
121 * @param height height of text area
122 */
123 public TEditorWidget(final TWidget parent, final String text, final int x,
124 final int y, final int width, final int height) {
125
126 // Set parent and window
127 super(parent, x, y, width, height);
128
129 setCursorVisible(true);
130
131 defaultColor = getTheme().getColor("teditor");
132 document = new Document(text, defaultColor);
133 }
134
135 // ------------------------------------------------------------------------
136 // Event handlers ---------------------------------------------------------
137 // ------------------------------------------------------------------------
138
139 /**
140 * Handle mouse press events.
141 *
142 * @param mouse mouse button press event
143 */
144 @Override
145 public void onMouseDown(final TMouseEvent mouse) {
146 if (mouse.isMouseWheelUp()) {
147 for (int i = 0; i < wheelScrollSize; i++) {
148 if (topLine > 0) {
149 topLine--;
150 alignDocument(false);
151 }
152 }
153 return;
154 }
155 if (mouse.isMouseWheelDown()) {
156 for (int i = 0; i < wheelScrollSize; i++) {
157 if (topLine < document.getLineCount() - 1) {
158 topLine++;
159 alignDocument(true);
160 }
161 }
162 return;
163 }
164
165 if (mouse.isMouse1()) {
166 // Selection.
167 int newLine = topLine + mouse.getY();
168 int newX = leftColumn + mouse.getX();
169
170 inSelection = true;
171 if (newLine > document.getLineCount() - 1) {
172 selectionLine0 = document.getLineCount() - 1;
173 } else {
174 selectionLine0 = topLine + mouse.getY();
175 }
176 selectionColumn0 = leftColumn + mouse.getX();
177 selectionColumn0 = Math.max(0, Math.min(selectionColumn0,
178 document.getLine(selectionLine0).getDisplayLength() - 1));
179 selectionColumn1 = selectionColumn0;
180 selectionLine1 = selectionLine0;
181
182 // Set the row and column
183 if (newLine > document.getLineCount() - 1) {
184 // Go to the end
185 document.setLineNumber(document.getLineCount() - 1);
186 document.end();
187 if (newLine > document.getLineCount() - 1) {
188 setCursorY(document.getLineCount() - 1 - topLine);
189 } else {
190 setCursorY(mouse.getY());
191 }
192 alignCursor();
193 if (inSelection) {
194 selectionColumn1 = document.getCursor();
195 selectionLine1 = document.getLineNumber();
196 }
197 return;
198 }
199
200 document.setLineNumber(newLine);
201 setCursorY(mouse.getY());
202 if (newX >= document.getCurrentLine().getDisplayLength()) {
203 document.end();
204 alignCursor();
205 } else {
206 document.setCursor(newX);
207 setCursorX(mouse.getX());
208 }
209 if (inSelection) {
210 selectionColumn1 = document.getCursor();
211 selectionLine1 = document.getLineNumber();
212 }
213 return;
214 } else {
215 inSelection = false;
216 }
217
218 // Pass to children
219 super.onMouseDown(mouse);
220 }
221
222 /**
223 * Handle mouse motion events.
224 *
225 * @param mouse mouse motion event
226 */
227 @Override
228 public void onMouseMotion(final TMouseEvent mouse) {
229
230 if (mouse.isMouse1()) {
231 // Set the row and column
232 int newLine = topLine + mouse.getY();
233 int newX = leftColumn + mouse.getX();
234 if ((newLine < 0) || (newX < 0)) {
235 return;
236 }
237
238 // Selection.
239 if (inSelection) {
240 selectionColumn1 = newX;
241 selectionLine1 = newLine;
242 } else {
243 inSelection = true;
244 selectionColumn0 = newX;
245 selectionLine0 = newLine;
246 selectionColumn1 = selectionColumn0;
247 selectionLine1 = selectionLine0;
248 }
249
250 if (newLine > document.getLineCount() - 1) {
251 // Go to the end
252 document.setLineNumber(document.getLineCount() - 1);
253 document.end();
254 if (newLine > document.getLineCount() - 1) {
255 setCursorY(document.getLineCount() - 1 - topLine);
256 } else {
257 setCursorY(mouse.getY());
258 }
259 alignCursor();
260 if (inSelection) {
261 selectionColumn1 = document.getCursor();
262 selectionLine1 = document.getLineNumber();
263 }
264 return;
265 }
266 document.setLineNumber(newLine);
267 setCursorY(mouse.getY());
268 if (newX >= document.getCurrentLine().getDisplayLength()) {
269 document.end();
270 alignCursor();
271 } else {
272 document.setCursor(newX);
273 setCursorX(mouse.getX());
274 }
275 if (inSelection) {
276 selectionColumn1 = document.getCursor();
277 selectionLine1 = document.getLineNumber();
278 }
279 return;
280 }
281
282 // Pass to children
283 super.onMouseDown(mouse);
284 }
285
286 /**
287 * Handle keystrokes.
288 *
289 * @param keypress keystroke event
290 */
291 @Override
292 public void onKeypress(final TKeypressEvent keypress) {
293 if (keypress.getKey().isShift()) {
294 // Selection.
295 if (!inSelection) {
296 inSelection = true;
297 selectionColumn0 = document.getCursor();
298 selectionLine0 = document.getLineNumber();
299 selectionColumn1 = selectionColumn0;
300 selectionLine1 = selectionLine0;
301 }
302 } else {
303 if (keypress.equals(kbLeft)
304 || keypress.equals(kbRight)
305 || keypress.equals(kbUp)
306 || keypress.equals(kbDown)
307 || keypress.equals(kbPgDn)
308 || keypress.equals(kbPgUp)
309 || keypress.equals(kbHome)
310 || keypress.equals(kbEnd)
311 ) {
312 // Non-shifted navigation keys disable selection.
313 inSelection = false;
314 }
315 if ((selectionColumn0 == selectionColumn1)
316 && (selectionLine0 == selectionLine1)
317 ) {
318 // The user clicked a spot and started typing.
319 inSelection = false;
320 }
321 }
322
323 if (keypress.equals(kbLeft)
324 || keypress.equals(kbShiftLeft)
325 ) {
326 document.left();
327 alignTopLine(false);
328 } else if (keypress.equals(kbRight)
329 || keypress.equals(kbShiftRight)
330 ) {
331 document.right();
332 alignTopLine(true);
333 } else if (keypress.equals(kbAltLeft)
334 || keypress.equals(kbCtrlLeft)
335 || keypress.equals(kbAltShiftLeft)
336 || keypress.equals(kbCtrlShiftLeft)
337 ) {
338 document.backwardsWord();
339 alignTopLine(false);
340 } else if (keypress.equals(kbAltRight)
341 || keypress.equals(kbCtrlRight)
342 || keypress.equals(kbAltShiftRight)
343 || keypress.equals(kbCtrlShiftRight)
344 ) {
345 document.forwardsWord();
346 alignTopLine(true);
347 } else if (keypress.equals(kbUp)
348 || keypress.equals(kbShiftUp)
349 ) {
350 document.up();
351 alignTopLine(false);
352 } else if (keypress.equals(kbDown)
353 || keypress.equals(kbShiftDown)
354 ) {
355 document.down();
356 alignTopLine(true);
357 } else if (keypress.equals(kbPgUp)
358 || keypress.equals(kbShiftPgUp)
359 ) {
360 document.up(getHeight() - 1);
361 alignTopLine(false);
362 } else if (keypress.equals(kbPgDn)
363 || keypress.equals(kbShiftPgDn)
364 ) {
365 document.down(getHeight() - 1);
366 alignTopLine(true);
367 } else if (keypress.equals(kbHome)
368 || keypress.equals(kbShiftHome)
369 ) {
370 if (document.home()) {
371 leftColumn = 0;
372 if (leftColumn < 0) {
373 leftColumn = 0;
374 }
375 setCursorX(0);
376 }
377 } else if (keypress.equals(kbEnd)
378 || keypress.equals(kbShiftEnd)
379 ) {
380 if (document.end()) {
381 alignCursor();
382 }
383 } else if (keypress.equals(kbCtrlHome)
384 || keypress.equals(kbCtrlShiftHome)
385 ) {
386 document.setLineNumber(0);
387 document.home();
388 topLine = 0;
389 leftColumn = 0;
390 setCursorX(0);
391 setCursorY(0);
392 } else if (keypress.equals(kbCtrlEnd)
393 || keypress.equals(kbCtrlShiftEnd)
394 ) {
395 document.setLineNumber(document.getLineCount() - 1);
396 document.end();
397 alignTopLine(false);
398 } else if (keypress.equals(kbIns)) {
399 document.setOverwrite(!document.getOverwrite());
400 } else if (keypress.equals(kbDel)) {
401 if (inSelection) {
402 deleteSelection();
403 } else {
404 document.del();
405 }
406 alignCursor();
407 } else if (keypress.equals(kbBackspace)
408 || keypress.equals(kbBackspaceDel)
409 ) {
410 if (inSelection) {
411 deleteSelection();
412 } else {
413 document.backspace();
414 }
415 alignTopLine(false);
416 } else if (keypress.equals(kbTab)) {
417 deleteSelection();
418 // Add spaces until we hit modulo 8.
419 for (int i = document.getCursor(); (i + 1) % 8 != 0; i++) {
420 document.addChar(' ');
421 }
422 alignCursor();
423 } else if (keypress.equals(kbEnter)) {
424 deleteSelection();
425 document.enter();
426 alignTopLine(true);
427 } else if (!keypress.getKey().isFnKey()
428 && !keypress.getKey().isAlt()
429 && !keypress.getKey().isCtrl()
430 ) {
431 // Plain old keystroke, process it
432 deleteSelection();
433 document.addChar(keypress.getKey().getChar());
434 alignCursor();
435 } else {
436 // Pass other keys (tab etc.) on to TWidget
437 super.onKeypress(keypress);
438 }
439
440 if (inSelection) {
441 selectionColumn1 = document.getCursor();
442 selectionLine1 = document.getLineNumber();
443 }
444 }
445
446 /**
447 * Method that subclasses can override to handle window/screen resize
448 * events.
449 *
450 * @param resize resize event
451 */
452 @Override
453 public void onResize(final TResizeEvent resize) {
454 // Change my width/height, and pull the cursor in as needed.
455 if (resize.getType() == TResizeEvent.Type.WIDGET) {
456 setWidth(resize.getWidth());
457 setHeight(resize.getHeight());
458 // See if the cursor is now outside the window, and if so move
459 // things.
460 if (getCursorX() >= getWidth()) {
461 leftColumn += getCursorX() - (getWidth() - 1);
462 setCursorX(getWidth() - 1);
463 }
464 if (getCursorY() >= getHeight()) {
465 topLine += getCursorY() - (getHeight() - 1);
466 setCursorY(getHeight() - 1);
467 }
468 } else {
469 // Let superclass handle it
470 super.onResize(resize);
471 }
472 }
473
474 /**
475 * Handle posted command events.
476 *
477 * @param command command event
478 */
479 @Override
480 public void onCommand(final TCommandEvent command) {
481 if (command.equals(cmCut)) {
482 // Copy text to clipboard, and then remove it.
483 copySelection();
484 deleteSelection();
485 return;
486 }
487
488 if (command.equals(cmCopy)) {
489 // Copy text to clipboard.
490 copySelection();
491 return;
492 }
493
494 if (command.equals(cmPaste)) {
495 // Delete selected text, then paste text from clipboard.
496 deleteSelection();
497
498 String text = getClipboard().pasteText();
499 if (text != null) {
500 for (int i = 0; i < text.length(); ) {
501 int ch = text.codePointAt(i);
502 onKeypress(new TKeypressEvent(false, 0, ch, false, false,
503 false));
504 i += Character.charCount(ch);
505 }
506 }
507 return;
508 }
509
510 if (command.equals(cmClear)) {
511 // Remove text.
512 deleteSelection();
513 return;
514 }
515
516 }
517
518 // ------------------------------------------------------------------------
519 // TWidget ----------------------------------------------------------------
520 // ------------------------------------------------------------------------
521
522 /**
523 * Draw the text box.
524 */
525 @Override
526 public void draw() {
527 CellAttributes selectedColor = getTheme().getColor("teditor.selected");
528
529 boolean drawSelection = true;
530
531 int startCol = selectionColumn0;
532 int startRow = selectionLine0;
533 int endCol = selectionColumn1;
534 int endRow = selectionLine1;
535
536 if (((selectionColumn1 < selectionColumn0)
537 && (selectionLine1 == selectionLine0))
538 || (selectionLine1 < selectionLine0)
539 ) {
540 // The user selected from bottom-to-top and/or right-to-left.
541 // Reverse the coordinates for the inverted section.
542 startCol = selectionColumn1;
543 startRow = selectionLine1;
544 endCol = selectionColumn0;
545 endRow = selectionLine0;
546 }
547 if ((startCol == endCol) && (startRow == endRow)) {
548 drawSelection = false;
549 }
550
551 for (int i = 0; i < getHeight(); i++) {
552 // Background line
553 getScreen().hLineXY(0, i, getWidth(), ' ', defaultColor);
554
555 // Now draw document's line
556 if (topLine + i < document.getLineCount()) {
557 Line line = document.getLine(topLine + i);
558 int x = 0;
559 for (Word word: line.getWords()) {
560 // For now, we are cheating: draw outside the left region
561 // if needed and let screen do the clipping.
562 getScreen().putStringXY(x - leftColumn, i, word.getText(),
563 word.getColor());
564 x += word.getDisplayLength();
565 if (x - leftColumn > getWidth()) {
566 break;
567 }
568 }
569
570 // Highlight selected region
571 if (inSelection && drawSelection) {
572 if (startRow == endRow) {
573 if (topLine + i == startRow) {
574 for (x = startCol; x <= endCol; x++) {
575 putAttrXY(x - leftColumn, i, selectedColor);
576 }
577 }
578 } else {
579 if (topLine + i == startRow) {
580 for (x = startCol; x < line.getDisplayLength(); x++) {
581 putAttrXY(x - leftColumn, i, selectedColor);
582 }
583 } else if (topLine + i == endRow) {
584 for (x = 0; x <= endCol; x++) {
585 putAttrXY(x - leftColumn, i, selectedColor);
586 }
587 } else if ((topLine + i >= startRow)
588 && (topLine + i <= endRow)
589 ) {
590 for (x = 0; x < getWidth(); x++) {
591 putAttrXY(x, i, selectedColor);
592 }
593 }
594 }
595 }
596
597 }
598 }
599 }
600
601 // ------------------------------------------------------------------------
602 // TEditorWidget ----------------------------------------------------------
603 // ------------------------------------------------------------------------
604
605 /**
606 * Align visible area with document current line.
607 *
608 * @param topLineIsTop if true, make the top visible line the document
609 * current line if it was off-screen. If false, make the bottom visible
610 * line the document current line.
611 */
612 private void alignTopLine(final boolean topLineIsTop) {
613 int line = document.getLineNumber();
614
615 if ((line < topLine) || (line > topLine + getHeight() - 1)) {
616 // Need to move topLine to bring document back into view.
617 if (topLineIsTop) {
618 topLine = line - (getHeight() - 1);
619 if (topLine < 0) {
620 topLine = 0;
621 }
622 assert (topLine >= 0);
623 } else {
624 topLine = line;
625 assert (topLine >= 0);
626 }
627 }
628
629 /*
630 System.err.println("line " + line + " topLine " + topLine);
631 */
632
633 // Document is in view, let's set cursorY
634 assert (line >= topLine);
635 setCursorY(line - topLine);
636 alignCursor();
637 }
638
639 /**
640 * Align document current line with visible area.
641 *
642 * @param topLineIsTop if true, make the top visible line the document
643 * current line if it was off-screen. If false, make the bottom visible
644 * line the document current line.
645 */
646 private void alignDocument(final boolean topLineIsTop) {
647 int line = document.getLineNumber();
648 int cursor = document.getCursor();
649
650 if ((line < topLine) || (line > topLine + getHeight() - 1)) {
651 // Need to move document to ensure it fits view.
652 if (topLineIsTop) {
653 document.setLineNumber(topLine);
654 } else {
655 document.setLineNumber(topLine + (getHeight() - 1));
656 }
657 if (cursor < document.getCurrentLine().getDisplayLength()) {
658 document.setCursor(cursor);
659 }
660 }
661
662 /*
663 System.err.println("getLineNumber() " + document.getLineNumber() +
664 " topLine " + topLine);
665 */
666
667 // Document is in view, let's set cursorY
668 setCursorY(document.getLineNumber() - topLine);
669 alignCursor();
670 }
671
672 /**
673 * Align visible cursor with document cursor.
674 */
675 private void alignCursor() {
676 int width = getWidth();
677
678 int desiredX = document.getCursor() - leftColumn;
679 if (desiredX < 0) {
680 // We need to push the screen to the left.
681 leftColumn = document.getCursor();
682 } else if (desiredX > width - 1) {
683 // We need to push the screen to the right.
684 leftColumn = document.getCursor() - (width - 1);
685 }
686
687 /*
688 System.err.println("document cursor " + document.getCursor() +
689 " leftColumn " + leftColumn);
690 */
691
692
693 setCursorX(document.getCursor() - leftColumn);
694 }
695
696 /**
697 * Get the number of lines in the underlying Document.
698 *
699 * @return the number of lines
700 */
701 public int getLineCount() {
702 return document.getLineCount();
703 }
704
705 /**
706 * Get the current visible top row number. 1-based.
707 *
708 * @return the visible top row number. Row 1 is the first row.
709 */
710 public int getVisibleRowNumber() {
711 return topLine + 1;
712 }
713
714 /**
715 * Set the current visible row number. 1-based.
716 *
717 * @param row the new visible row number. Row 1 is the first row.
718 */
719 public void setVisibleRowNumber(final int row) {
720 assert (row > 0);
721 if ((row > 0) && (row < document.getLineCount())) {
722 topLine = row - 1;
723 alignDocument(true);
724 }
725 }
726
727 /**
728 * Get the current editing row number. 1-based.
729 *
730 * @return the editing row number. Row 1 is the first row.
731 */
732 public int getEditingRowNumber() {
733 return document.getLineNumber() + 1;
734 }
735
736 /**
737 * Set the current editing row number. 1-based.
738 *
739 * @param row the new editing row number. Row 1 is the first row.
740 */
741 public void setEditingRowNumber(final int row) {
742 assert (row > 0);
743 if ((row > 0) && (row < document.getLineCount())) {
744 document.setLineNumber(row - 1);
745 alignTopLine(true);
746 }
747 }
748
749 /**
750 * Set the current visible column number. 1-based.
751 *
752 * @return the visible column number. Column 1 is the first column.
753 */
754 public int getVisibleColumnNumber() {
755 return leftColumn + 1;
756 }
757
758 /**
759 * Set the current visible column number. 1-based.
760 *
761 * @param column the new visible column number. Column 1 is the first
762 * column.
763 */
764 public void setVisibleColumnNumber(final int column) {
765 assert (column > 0);
766 if ((column > 0) && (column < document.getLineLengthMax())) {
767 leftColumn = column - 1;
768 alignDocument(true);
769 }
770 }
771
772 /**
773 * Get the current editing column number. 1-based.
774 *
775 * @return the editing column number. Column 1 is the first column.
776 */
777 public int getEditingColumnNumber() {
778 return document.getCursor() + 1;
779 }
780
781 /**
782 * Set the current editing column number. 1-based.
783 *
784 * @param column the new editing column number. Column 1 is the first
785 * column.
786 */
787 public void setEditingColumnNumber(final int column) {
788 if ((column > 0) && (column < document.getLineLength())) {
789 document.setCursor(column - 1);
790 alignCursor();
791 }
792 }
793
794 /**
795 * Get the maximum possible row number. 1-based.
796 *
797 * @return the maximum row number. Row 1 is the first row.
798 */
799 public int getMaximumRowNumber() {
800 return document.getLineCount() + 1;
801 }
802
803 /**
804 * Get the maximum possible column number. 1-based.
805 *
806 * @return the maximum column number. Column 1 is the first column.
807 */
808 public int getMaximumColumnNumber() {
809 return document.getLineLengthMax() + 1;
810 }
811
812 /**
813 * Get the current editing row plain text. 1-based.
814 *
815 * @param row the editing row number. Row 1 is the first row.
816 * @return the plain text of the row
817 */
818 public String getEditingRawLine(final int row) {
819 Line line = document.getLine(row - 1);
820 return line.getRawString();
821 }
822
823 /**
824 * Get the dirty value.
825 *
826 * @return true if the buffer is dirty
827 */
828 public boolean isDirty() {
829 return document.isDirty();
830 }
831
832 /**
833 * Unset the dirty flag.
834 */
835 public void setNotDirty() {
836 document.setNotDirty();
837 }
838
839 /**
840 * Save contents to file.
841 *
842 * @param filename file to save to
843 * @throws IOException if a java.io operation throws
844 */
845 public void saveToFilename(final String filename) throws IOException {
846 document.saveToFilename(filename);
847 }
848
849 /**
850 * Delete text within the selection bounds.
851 */
852 private void deleteSelection() {
853 if (!inSelection) {
854 return;
855 }
856 inSelection = false;
857
858 int startCol = selectionColumn0;
859 int startRow = selectionLine0;
860 int endCol = selectionColumn1;
861 int endRow = selectionLine1;
862
863 /*
864 System.err.println("INITIAL: " + startRow + " " + startCol + " " +
865 endRow + " " + endCol + " " +
866 document.getLineNumber() + " " + document.getCursor());
867 */
868
869 if (((selectionColumn1 < selectionColumn0)
870 && (selectionLine1 == selectionLine0))
871 || (selectionLine1 < selectionLine0)
872 ) {
873 // The user selected from bottom-to-top and/or right-to-left.
874 // Reverse the coordinates for the inverted section.
875 startCol = selectionColumn1;
876 startRow = selectionLine1;
877 endCol = selectionColumn0;
878 endRow = selectionLine0;
879
880 if (endRow >= document.getLineCount()) {
881 // The selection started beyond EOF, trim it to EOF.
882 endRow = document.getLineCount() - 1;
883 endCol = document.getLine(endRow).getDisplayLength();
884 } else if (endRow == document.getLineCount() - 1) {
885 // The selection started beyond EOF, trim it to EOF.
886 if (endCol >= document.getLine(endRow).getDisplayLength()) {
887 endCol = document.getLine(endRow).getDisplayLength() - 1;
888 }
889 }
890 }
891 /*
892 System.err.println("FLIP: " + startRow + " " + startCol + " " +
893 endRow + " " + endCol + " " +
894 document.getLineNumber() + " " + document.getCursor());
895 System.err.println(" --END: " + endRow + " " + document.getLineCount() +
896 " " + document.getLine(endRow).getDisplayLength());
897 */
898
899 assert (endRow < document.getLineCount());
900 if (endCol >= document.getLine(endRow).getDisplayLength()) {
901 endCol = document.getLine(endRow).getDisplayLength() - 1;
902 }
903 if (endCol < 0) {
904 endCol = 0;
905 }
906 if (startCol >= document.getLine(startRow).getDisplayLength()) {
907 startCol = document.getLine(startRow).getDisplayLength() - 1;
908 }
909 if (startCol < 0) {
910 startCol = 0;
911 }
912
913 // Place the cursor on the selection end, and "press backspace" until
914 // the cursor matches the selection start.
915 /*
916 System.err.println("BEFORE: " + startRow + " " + startCol + " " +
917 endRow + " " + endCol + " " +
918 document.getLineNumber() + " " + document.getCursor());
919 */
920 document.setLineNumber(endRow);
921 document.setCursor(endCol + 1);
922 while (!((document.getLineNumber() == startRow)
923 && (document.getCursor() == startCol))
924 ) {
925 /*
926 System.err.println("DURING: " + startRow + " " + startCol + " " +
927 endRow + " " + endCol + " " +
928 document.getLineNumber() + " " + document.getCursor());
929 */
930
931 document.backspace();
932 }
933 alignTopLine(true);
934 }
935
936 /**
937 * Copy text within the selection bounds to clipboard.
938 */
939 private void copySelection() {
940 if (!inSelection) {
941 return;
942 }
943 getClipboard().copyText(getSelection());
944 }
945
946 /**
947 * Set the selection.
948 *
949 * @param startRow the starting row number. 0-based: row 0 is the first
950 * row.
951 * @param startColumn the starting column number. 0-based: column 0 is
952 * the first column.
953 * @param endRow the ending row number. 0-based: row 0 is the first row.
954 * @param endColumn the ending column number. 0-based: column 0 is the
955 * first column.
956 */
957 public void setSelection(final int startRow, final int startColumn,
958 final int endRow, final int endColumn) {
959
960 inSelection = true;
961 selectionLine0 = startRow;
962 selectionColumn0 = startColumn;
963 selectionLine1 = endRow;
964 selectionColumn1 = endColumn;
965 }
966
967 /**
968 * Copy text within the selection bounds to a string.
969 *
970 * @return the selection as a string, or null if there is no selection
971 */
972 public String getSelection() {
973 if (!inSelection) {
974 return null;
975 }
976
977 int startCol = selectionColumn0;
978 int startRow = selectionLine0;
979 int endCol = selectionColumn1;
980 int endRow = selectionLine1;
981
982 if (((selectionColumn1 < selectionColumn0)
983 && (selectionLine1 == selectionLine0))
984 || (selectionLine1 < selectionLine0)
985 ) {
986 // The user selected from bottom-to-top and/or right-to-left.
987 // Reverse the coordinates for the inverted section.
988 startCol = selectionColumn1;
989 startRow = selectionLine1;
990 endCol = selectionColumn0;
991 endRow = selectionLine0;
992 }
993
994 StringBuilder sb = new StringBuilder();
995
996 if (endRow > startRow) {
997 // First line
998 String line = document.getLine(startRow).getRawString();
999 int x = 0;
1000 for (int i = 0; i < line.length(); ) {
1001 int ch = line.codePointAt(i);
1002
1003 if (x >= startCol) {
1004 sb.append(Character.toChars(ch));
1005 }
1006 x += StringUtils.width(ch);
1007 i += Character.charCount(ch);
1008 }
1009 sb.append("\n");
1010
1011 // Middle lines
1012 for (int y = startRow + 1; y < endRow; y++) {
1013 sb.append(document.getLine(y).getRawString());
1014 sb.append("\n");
1015 }
1016
1017 // Final line
1018 line = document.getLine(endRow).getRawString();
1019 x = 0;
1020 for (int i = 0; i < line.length(); ) {
1021 int ch = line.codePointAt(i);
1022
1023 if (x > endCol) {
1024 break;
1025 }
1026
1027 sb.append(Character.toChars(ch));
1028 x += StringUtils.width(ch);
1029 i += Character.charCount(ch);
1030 }
1031 } else {
1032 assert (startRow == endRow);
1033
1034 // Only one line
1035 String line = document.getLine(startRow).getRawString();
1036 int x = 0;
1037 for (int i = 0; i < line.length(); ) {
1038 int ch = line.codePointAt(i);
1039
1040 if ((x >= startCol) && (x <= endCol)) {
1041 sb.append(Character.toChars(ch));
1042 }
1043
1044 x += StringUtils.width(ch);
1045 i += Character.charCount(ch);
1046 }
1047 }
1048 return sb.toString();
1049 }
1050
1051 /**
1052 * Get the selection starting row number.
1053 *
1054 * @return the starting row number, or -1 if there is no selection.
1055 * 0-based: row 0 is the first row.
1056 */
1057 public int getSelectionStartRow() {
1058 if (!inSelection) {
1059 return -1;
1060 }
1061
1062 int startCol = selectionColumn0;
1063 int startRow = selectionLine0;
1064 int endCol = selectionColumn1;
1065 int endRow = selectionLine1;
1066
1067 if (((selectionColumn1 < selectionColumn0)
1068 && (selectionLine1 == selectionLine0))
1069 || (selectionLine1 < selectionLine0)
1070 ) {
1071 // The user selected from bottom-to-top and/or right-to-left.
1072 // Reverse the coordinates for the inverted section.
1073 startCol = selectionColumn1;
1074 startRow = selectionLine1;
1075 endCol = selectionColumn0;
1076 endRow = selectionLine0;
1077 }
1078 return startRow;
1079 }
1080
1081 /**
1082 * Get the selection starting column number.
1083 *
1084 * @return the starting column number, or -1 if there is no selection.
1085 * 0-based: column 0 is the first column.
1086 */
1087 public int getSelectionStartColumn() {
1088 if (!inSelection) {
1089 return -1;
1090 }
1091
1092 int startCol = selectionColumn0;
1093 int startRow = selectionLine0;
1094 int endCol = selectionColumn1;
1095 int endRow = selectionLine1;
1096
1097 if (((selectionColumn1 < selectionColumn0)
1098 && (selectionLine1 == selectionLine0))
1099 || (selectionLine1 < selectionLine0)
1100 ) {
1101 // The user selected from bottom-to-top and/or right-to-left.
1102 // Reverse the coordinates for the inverted section.
1103 startCol = selectionColumn1;
1104 startRow = selectionLine1;
1105 endCol = selectionColumn0;
1106 endRow = selectionLine0;
1107 }
1108 return startCol;
1109 }
1110
1111 /**
1112 * Get the selection ending row number.
1113 *
1114 * @return the ending row number, or -1 if there is no selection.
1115 * 0-based: row 0 is the first row.
1116 */
1117 public int getSelectionEndRow() {
1118 if (!inSelection) {
1119 return -1;
1120 }
1121
1122 int startCol = selectionColumn0;
1123 int startRow = selectionLine0;
1124 int endCol = selectionColumn1;
1125 int endRow = selectionLine1;
1126
1127 if (((selectionColumn1 < selectionColumn0)
1128 && (selectionLine1 == selectionLine0))
1129 || (selectionLine1 < selectionLine0)
1130 ) {
1131 // The user selected from bottom-to-top and/or right-to-left.
1132 // Reverse the coordinates for the inverted section.
1133 startCol = selectionColumn1;
1134 startRow = selectionLine1;
1135 endCol = selectionColumn0;
1136 endRow = selectionLine0;
1137 }
1138 return endRow;
1139 }
1140
1141 /**
1142 * Get the selection ending column number.
1143 *
1144 * @return the ending column number, or -1 if there is no selection.
1145 * 0-based: column 0 is the first column.
1146 */
1147 public int getSelectionEndColumn() {
1148 if (!inSelection) {
1149 return -1;
1150 }
1151
1152 int startCol = selectionColumn0;
1153 int startRow = selectionLine0;
1154 int endCol = selectionColumn1;
1155 int endRow = selectionLine1;
1156
1157 if (((selectionColumn1 < selectionColumn0)
1158 && (selectionLine1 == selectionLine0))
1159 || (selectionLine1 < selectionLine0)
1160 ) {
1161 // The user selected from bottom-to-top and/or right-to-left.
1162 // Reverse the coordinates for the inverted section.
1163 startCol = selectionColumn1;
1164 startRow = selectionLine1;
1165 endCol = selectionColumn0;
1166 endRow = selectionLine0;
1167 }
1168 return endCol;
1169 }
1170
1171 /**
1172 * Unset the selection.
1173 */
1174 public void unsetSelection() {
1175 inSelection = false;
1176 }
1177
1178 /**
1179 * Replace whatever is being selected with new text. If not in
1180 * selection, nothing is replaced.
1181 *
1182 * @param text the new replacement text
1183 */
1184 public void replaceSelection(final String text) {
1185 if (!inSelection) {
1186 return;
1187 }
1188
1189 // Delete selected text, then paste text from clipboard.
1190 deleteSelection();
1191
1192 for (int i = 0; i < text.length(); ) {
1193 int ch = text.codePointAt(i);
1194 onKeypress(new TKeypressEvent(false, 0, ch, false, false,
1195 false));
1196 i += Character.charCount(ch);
1197 }
1198 }
1199
1200 /**
1201 * Check if selection is available.
1202 *
1203 * @return true if a selection has been made
1204 */
1205 public boolean hasSelection() {
1206 return inSelection;
1207 }
1208
1209 /**
1210 * Get the entire contents of the editor as one string.
1211 *
1212 * @return the editor contents
1213 */
1214 public String getText() {
1215 return document.getText();
1216 }
1217
1218 /**
1219 * Set the entire contents of the editor from one string.
1220 *
1221 * @param text the new contents
1222 */
1223 public void setText(final String text) {
1224 document = new Document(text, defaultColor);
1225 unsetSelection();
1226 topLine = 0;
1227 leftColumn = 0;
1228 }
1229
1230 // ------------------------------------------------------------------------
1231 // EditMenuUser -----------------------------------------------------------
1232 // ------------------------------------------------------------------------
1233
1234 /**
1235 * Check if the cut menu item should be enabled.
1236 *
1237 * @return true if the cut menu item should be enabled
1238 */
1239 public boolean isEditMenuCut() {
1240 return true;
1241 }
1242
1243 /**
1244 * Check if the copy menu item should be enabled.
1245 *
1246 * @return true if the copy menu item should be enabled
1247 */
1248 public boolean isEditMenuCopy() {
1249 return true;
1250 }
1251
1252 /**
1253 * Check if the paste menu item should be enabled.
1254 *
1255 * @return true if the paste menu item should be enabled
1256 */
1257 public boolean isEditMenuPaste() {
1258 return true;
1259 }
1260
1261 /**
1262 * Check if the clear menu item should be enabled.
1263 *
1264 * @return true if the clear menu item should be enabled
1265 */
1266 public boolean isEditMenuClear() {
1267 return true;
1268 }
1269
1270 }