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