editor cut/copy/paste
[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
KL
166 // Selection.
167 if (inSelection) {
168 selectionColumn1 = leftColumn + mouse.getX();
169 selectionLine1 = topLine + mouse.getY();
43ee96da 170 } else if (mouse.isShift()) {
101bc589
KL
171 inSelection = true;
172 selectionColumn0 = leftColumn + mouse.getX();
173 selectionLine0 = topLine + mouse.getY();
174 selectionColumn1 = selectionColumn0;
175 selectionLine1 = selectionLine0;
101bc589
KL
176 }
177
178 // Set the row and column
179 int newLine = topLine + mouse.getY();
180 int newX = leftColumn + mouse.getX();
181 if (newLine > document.getLineCount() - 1) {
182 // Go to the end
183 document.setLineNumber(document.getLineCount() - 1);
184 document.end();
185 if (newLine > document.getLineCount() - 1) {
186 setCursorY(document.getLineCount() - 1 - topLine);
187 } else {
188 setCursorY(mouse.getY());
189 }
190 alignCursor();
191 if (inSelection) {
192 selectionColumn1 = document.getCursor();
193 selectionLine1 = document.getLineNumber();
101bc589
KL
194 }
195 return;
196 }
197
198 document.setLineNumber(newLine);
199 setCursorY(mouse.getY());
200 if (newX >= document.getCurrentLine().getDisplayLength()) {
201 document.end();
202 alignCursor();
203 } else {
204 document.setCursor(newX);
205 setCursorX(mouse.getX());
206 }
207 if (inSelection) {
208 selectionColumn1 = document.getCursor();
209 selectionLine1 = document.getLineNumber();
101bc589
KL
210 }
211 return;
212 } else {
213 inSelection = false;
214 }
215
216 // Pass to children
217 super.onMouseDown(mouse);
218 }
219
220 /**
221 * Handle mouse motion events.
222 *
223 * @param mouse mouse motion event
224 */
225 @Override
226 public void onMouseMotion(final TMouseEvent mouse) {
227
228 if (mouse.isMouse1()) {
229 // Selection.
230 if (inSelection) {
231 selectionColumn1 = leftColumn + mouse.getX();
232 selectionLine1 = topLine + mouse.getY();
43ee96da 233 } else if (mouse.isShift()) {
101bc589
KL
234 inSelection = true;
235 selectionColumn0 = leftColumn + mouse.getX();
236 selectionLine0 = topLine + mouse.getY();
237 selectionColumn1 = selectionColumn0;
238 selectionLine1 = selectionLine0;
101bc589
KL
239 }
240
e8a11f98
KL
241 // Set the row and column
242 int newLine = topLine + mouse.getY();
243 int newX = leftColumn + mouse.getX();
df602ccf 244 if (newLine > document.getLineCount() - 1) {
e8a11f98
KL
245 // Go to the end
246 document.setLineNumber(document.getLineCount() - 1);
247 document.end();
df602ccf
KL
248 if (newLine > document.getLineCount() - 1) {
249 setCursorY(document.getLineCount() - 1 - topLine);
e8a11f98 250 } else {
df602ccf 251 setCursorY(mouse.getY());
e8a11f98
KL
252 }
253 alignCursor();
101bc589
KL
254 if (inSelection) {
255 selectionColumn1 = document.getCursor();
256 selectionLine1 = document.getLineNumber();
101bc589 257 }
e8a11f98
KL
258 return;
259 }
260
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 274 return;
101bc589
KL
275 } else {
276 inSelection = false;
e8a11f98 277 }
12b55d76
KL
278
279 // Pass to children
280 super.onMouseDown(mouse);
281 }
282
101bc589
KL
283 /**
284 * Handle mouse release events.
285 *
286 * @param mouse mouse button release event
287 */
288 @Override
289 public void onMouseUp(final TMouseEvent mouse) {
290 inSelection = false;
291
292 // Pass to children
293 super.onMouseDown(mouse);
294 }
295
12b55d76
KL
296 /**
297 * Handle keystrokes.
298 *
299 * @param keypress keystroke event
300 */
301 @Override
302 public void onKeypress(final TKeypressEvent keypress) {
43ee96da 303 if (keypress.getKey().isShift()) {
101bc589
KL
304 // Selection.
305 if (!inSelection) {
306 inSelection = true;
307 selectionColumn0 = document.getCursor();
308 selectionLine0 = document.getLineNumber();
309 selectionColumn1 = selectionColumn0;
310 selectionLine1 = selectionLine0;
101bc589
KL
311 }
312 } else {
43ee96da
KL
313 if (keypress.equals(kbLeft)
314 || keypress.equals(kbRight)
315 || keypress.equals(kbUp)
316 || keypress.equals(kbDown)
317 || keypress.equals(kbPgDn)
318 || keypress.equals(kbPgUp)
319 || keypress.equals(kbHome)
320 || keypress.equals(kbEnd)
321 ) {
322 // Non-shifted navigation keys disable selection.
323 inSelection = false;
324 }
101bc589
KL
325 }
326
327 if (keypress.equals(kbLeft)
328 || keypress.equals(kbShiftLeft)
329 ) {
df602ccf
KL
330 document.left();
331 alignTopLine(false);
101bc589
KL
332 } else if (keypress.equals(kbRight)
333 || keypress.equals(kbShiftRight)
334 ) {
df602ccf
KL
335 document.right();
336 alignTopLine(true);
e23989a4
KL
337 } else if (keypress.equals(kbAltLeft)
338 || keypress.equals(kbCtrlLeft)
43ee96da
KL
339 || keypress.equals(kbAltShiftLeft)
340 || keypress.equals(kbCtrlShiftLeft)
e23989a4
KL
341 ) {
342 document.backwardsWord();
343 alignTopLine(false);
344 } else if (keypress.equals(kbAltRight)
345 || keypress.equals(kbCtrlRight)
43ee96da
KL
346 || keypress.equals(kbAltShiftRight)
347 || keypress.equals(kbCtrlShiftRight)
e23989a4
KL
348 ) {
349 document.forwardsWord();
350 alignTopLine(true);
101bc589
KL
351 } else if (keypress.equals(kbUp)
352 || keypress.equals(kbShiftUp)
353 ) {
71a389c9
KL
354 document.up();
355 alignTopLine(false);
101bc589
KL
356 } else if (keypress.equals(kbDown)
357 || keypress.equals(kbShiftDown)
358 ) {
71a389c9
KL
359 document.down();
360 alignTopLine(true);
43ee96da
KL
361 } else if (keypress.equals(kbPgUp)
362 || keypress.equals(kbShiftPgUp)
363 ) {
71a389c9
KL
364 document.up(getHeight() - 1);
365 alignTopLine(false);
43ee96da
KL
366 } else if (keypress.equals(kbPgDn)
367 || keypress.equals(kbShiftPgDn)
368 ) {
71a389c9
KL
369 document.down(getHeight() - 1);
370 alignTopLine(true);
43ee96da
KL
371 } else if (keypress.equals(kbHome)
372 || keypress.equals(kbShiftHome)
373 ) {
e8a11f98
KL
374 if (document.home()) {
375 leftColumn = 0;
376 if (leftColumn < 0) {
377 leftColumn = 0;
378 }
379 setCursorX(0);
380 }
43ee96da
KL
381 } else if (keypress.equals(kbEnd)
382 || keypress.equals(kbShiftEnd)
383 ) {
e8a11f98
KL
384 if (document.end()) {
385 alignCursor();
386 }
43ee96da
KL
387 } else if (keypress.equals(kbCtrlHome)
388 || keypress.equals(kbCtrlShiftHome)
389 ) {
12b55d76
KL
390 document.setLineNumber(0);
391 document.home();
e8a11f98
KL
392 topLine = 0;
393 leftColumn = 0;
394 setCursorX(0);
395 setCursorY(0);
43ee96da
KL
396 } else if (keypress.equals(kbCtrlEnd)
397 || keypress.equals(kbCtrlShiftEnd)
398 ) {
12b55d76
KL
399 document.setLineNumber(document.getLineCount() - 1);
400 document.end();
71a389c9 401 alignTopLine(false);
12b55d76
KL
402 } else if (keypress.equals(kbIns)) {
403 document.setOverwrite(!document.getOverwrite());
404 } else if (keypress.equals(kbDel)) {
43ee96da
KL
405 if (inSelection) {
406 deleteSelection();
407 } else {
408 document.del();
409 }
71a389c9 410 alignCursor();
39e86397
KL
411 } else if (keypress.equals(kbBackspace)
412 || keypress.equals(kbBackspaceDel)
413 ) {
43ee96da
KL
414 if (inSelection) {
415 deleteSelection();
416 } else {
417 document.backspace();
418 }
df602ccf
KL
419 alignTopLine(false);
420 } else if (keypress.equals(kbTab)) {
43ee96da 421 deleteSelection();
101bc589 422 // Add spaces until we hit modulo 8.
df602ccf
KL
423 for (int i = document.getCursor(); (i + 1) % 8 != 0; i++) {
424 document.addChar(' ');
425 }
e8a11f98 426 alignCursor();
71a389c9 427 } else if (keypress.equals(kbEnter)) {
43ee96da 428 deleteSelection();
df602ccf
KL
429 document.enter();
430 alignTopLine(true);
12b55d76
KL
431 } else if (!keypress.getKey().isFnKey()
432 && !keypress.getKey().isAlt()
433 && !keypress.getKey().isCtrl()
434 ) {
435 // Plain old keystroke, process it
43ee96da 436 deleteSelection();
2d3f60d8 437 document.addChar(keypress.getKey().getChar());
71a389c9 438 alignCursor();
12b55d76
KL
439 } else {
440 // Pass other keys (tab etc.) on to TWidget
441 super.onKeypress(keypress);
442 }
101bc589
KL
443
444 if (inSelection) {
445 selectionColumn1 = document.getCursor();
446 selectionLine1 = document.getLineNumber();
101bc589 447 }
12b55d76
KL
448 }
449
e8a11f98
KL
450 /**
451 * Method that subclasses can override to handle window/screen resize
452 * events.
453 *
454 * @param resize resize event
455 */
456 @Override
457 public void onResize(final TResizeEvent resize) {
458 // Change my width/height, and pull the cursor in as needed.
459 if (resize.getType() == TResizeEvent.Type.WIDGET) {
460 setWidth(resize.getWidth());
461 setHeight(resize.getHeight());
462 // See if the cursor is now outside the window, and if so move
463 // things.
464 if (getCursorX() >= getWidth()) {
465 leftColumn += getCursorX() - (getWidth() - 1);
466 setCursorX(getWidth() - 1);
467 }
468 if (getCursorY() >= getHeight()) {
469 topLine += getCursorY() - (getHeight() - 1);
470 setCursorY(getHeight() - 1);
471 }
472 } else {
473 // Let superclass handle it
474 super.onResize(resize);
475 }
476 }
477
101bc589
KL
478 /**
479 * Handle posted command events.
480 *
481 * @param command command event
482 */
483 @Override
484 public void onCommand(final TCommandEvent command) {
485 if (command.equals(cmCut)) {
486 // Copy text to clipboard, and then remove it.
43ee96da 487 copySelection();
101bc589
KL
488 deleteSelection();
489 return;
490 }
491
492 if (command.equals(cmCopy)) {
493 // Copy text to clipboard.
43ee96da 494 copySelection();
101bc589
KL
495 return;
496 }
497
498 if (command.equals(cmPaste)) {
499 // Delete selected text, then paste text from clipboard.
500 deleteSelection();
501
502 String text = getClipboard().pasteText();
503 if (text != null) {
504 for (int i = 0; i < text.length(); ) {
505 int ch = text.codePointAt(i);
506 onKeypress(new TKeypressEvent(false, 0, ch, false, false,
507 false));
508 i += Character.charCount(ch);
509 }
510 }
511 return;
512 }
513
514 if (command.equals(cmClear)) {
515 // Remove text.
516 deleteSelection();
517 return;
518 }
519
520 }
521
522 // ------------------------------------------------------------------------
523 // TWidget ----------------------------------------------------------------
524 // ------------------------------------------------------------------------
525
526 /**
527 * Draw the text box.
528 */
529 @Override
530 public void draw() {
43ee96da
KL
531 CellAttributes selectedColor = getTheme().getColor("teditor.selected");
532
533 int startCol = selectionColumn0;
534 int startRow = selectionLine0;
535 int endCol = selectionColumn1;
536 int endRow = selectionLine1;
537
538 if (((selectionColumn1 < selectionColumn0)
539 && (selectionLine1 <= selectionLine0))
540 || ((selectionColumn1 <= selectionColumn0)
541 && (selectionLine1 < selectionLine0))
542 ) {
543 // The user selected from bottom-right to top-left. Reverse the
544 // coordinates for the inverted section.
545 startCol = selectionColumn1;
546 startRow = selectionLine1;
547 endCol = selectionColumn0;
548 endRow = selectionLine0;
549 }
550
101bc589
KL
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 }
101bc589 569
43ee96da
KL
570 // Highlight selected region
571 if (inSelection) {
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 }
101bc589
KL
598 }
599 }
600
615a0d99
KL
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
71a389c9
KL
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
b6faeac0
KL
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
71a389c9
KL
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) {
fe0770f9
KL
742 assert (row > 0);
743 if ((row > 0) && (row < document.getLineCount())) {
744 document.setLineNumber(row - 1);
745 alignTopLine(true);
746 }
71a389c9
KL
747 }
748
1bc08f18
KL
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
71a389c9
KL
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) {
fe0770f9
KL
788 if ((column > 0) && (column < document.getLineLength())) {
789 document.setCursor(column - 1);
790 alignCursor();
791 }
71a389c9
KL
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 dirty value.
814 *
815 * @return true if the buffer is dirty
816 */
817 public boolean isDirty() {
818 return document.isDirty();
819 }
820
821 /**
822 * Save contents to file.
823 *
824 * @param filename file to save to
825 * @throws IOException if a java.io operation throws
826 */
827 public void saveToFilename(final String filename) throws IOException {
828 document.saveToFilename(filename);
829 }
830
101bc589
KL
831 /**
832 * Delete text within the selection bounds.
833 */
834 private void deleteSelection() {
835 if (inSelection == false) {
836 return;
837 }
43ee96da
KL
838 inSelection = false;
839
840 int startCol = selectionColumn0;
841 int startRow = selectionLine0;
842 int endCol = selectionColumn1;
843 int endRow = selectionLine1;
844
845 if (((selectionColumn1 < selectionColumn0)
846 && (selectionLine1 <= selectionLine0))
847 || ((selectionColumn1 <= selectionColumn0)
848 && (selectionLine1 < selectionLine0))
849 ) {
850 // The user selected from bottom-right to top-left. Reverse the
851 // coordinates for the inverted section.
852 startCol = selectionColumn1;
853 startRow = selectionLine1;
854 endCol = selectionColumn0;
855 endRow = selectionLine0;
856 }
857
858 // Place the cursor on the selection end, and "press backspace" until
859 // the cursor matches the selection start.
860 document.setLineNumber(endRow);
861 document.setCursor(endCol + 1);
862 while (!((document.getLineNumber() == startRow)
863 && (document.getCursor() == startCol))
864 ) {
865 document.backspace();
866 }
867 alignTopLine(true);
868 }
869
870 /**
871 * Copy text within the selection bounds to clipboard.
872 */
873 private void copySelection() {
874 if (inSelection == false) {
875 return;
876 }
877
878 int startCol = selectionColumn0;
879 int startRow = selectionLine0;
880 int endCol = selectionColumn1;
881 int endRow = selectionLine1;
882
883 if (((selectionColumn1 < selectionColumn0)
884 && (selectionLine1 <= selectionLine0))
885 || ((selectionColumn1 <= selectionColumn0)
886 && (selectionLine1 < selectionLine0))
887 ) {
888 // The user selected from bottom-right to top-left. Reverse the
889 // coordinates for the inverted section.
890 startCol = selectionColumn1;
891 startRow = selectionLine1;
892 endCol = selectionColumn0;
893 endRow = selectionLine0;
894 }
895
896 StringBuilder sb = new StringBuilder();
897
898 if (endRow > startRow) {
899 // First line
900 String line = document.getLine(startRow).getRawString();
901 int x = 0;
902 for (int i = 0; i < line.length(); ) {
903 int ch = line.codePointAt(i);
904
905 if (x >= startCol) {
906 sb.append(Character.toChars(ch));
907 }
908 x += StringUtils.width(ch);
909 i += Character.charCount(ch);
910 }
911 sb.append("\n");
912
913 // Middle lines
914 for (int y = startRow + 1; y < endRow; y++) {
915 sb.append(document.getLine(y).getRawString());
916 sb.append("\n");
917 }
918
919 // Final line
920 line = document.getLine(endRow).getRawString();
921 x = 0;
922 for (int i = 0; i < line.length(); ) {
923 int ch = line.codePointAt(i);
924
925 if (x > endCol) {
926 break;
927 }
928
929 sb.append(Character.toChars(ch));
930 x += StringUtils.width(ch);
931 i += Character.charCount(ch);
932 }
933 } else {
934 assert (startRow == endRow);
935
936 // Only one line
937 String line = document.getLine(startRow).getRawString();
938 int x = 0;
939 for (int i = 0; i < line.length(); ) {
940 int ch = line.codePointAt(i);
941
942 if ((x >= startCol) && (x <= endCol)) {
943 sb.append(Character.toChars(ch));
944 }
945
946 x += StringUtils.width(ch);
947 i += Character.charCount(ch);
948 }
949 }
101bc589 950
43ee96da 951 getClipboard().copyText(sb.toString());
101bc589
KL
952 }
953
954 // ------------------------------------------------------------------------
955 // EditMenuUser -----------------------------------------------------------
956 // ------------------------------------------------------------------------
957
958 /**
959 * Check if the cut menu item should be enabled.
960 *
961 * @return true if the cut menu item should be enabled
962 */
963 public boolean isEditMenuCut() {
964 return true;
965 }
966
967 /**
968 * Check if the copy menu item should be enabled.
969 *
970 * @return true if the copy menu item should be enabled
971 */
972 public boolean isEditMenuCopy() {
973 return true;
974 }
975
976 /**
977 * Check if the paste menu item should be enabled.
978 *
979 * @return true if the paste menu item should be enabled
980 */
981 public boolean isEditMenuPaste() {
982 return true;
983 }
984
985 /**
986 * Check if the clear menu item should be enabled.
987 *
988 * @return true if the clear menu item should be enabled
989 */
990 public boolean isEditMenuClear() {
991 return true;
992 }
993
12b55d76 994}