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