2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2019 Kevin Lamonte
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:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
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.
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
31 import java
.io
.IOException
;
33 import jexer
.bits
.CellAttributes
;
34 import jexer
.event
.TKeypressEvent
;
35 import jexer
.event
.TMouseEvent
;
36 import jexer
.event
.TResizeEvent
;
37 import jexer
.teditor
.Document
;
38 import jexer
.teditor
.Line
;
39 import jexer
.teditor
.Word
;
40 import static jexer
.TKeypress
.*;
43 * TEditorWidget displays an editable text document. It is unaware of
44 * scrolling behavior, but can respond to mouse and keyboard events.
46 public class TEditorWidget
extends TWidget
{
48 // ------------------------------------------------------------------------
49 // Constants --------------------------------------------------------------
50 // ------------------------------------------------------------------------
53 * The number of lines to scroll on mouse wheel up/down.
55 private static final int wheelScrollSize
= 3;
57 // ------------------------------------------------------------------------
58 // Variables --------------------------------------------------------------
59 // ------------------------------------------------------------------------
62 * The document being edited.
64 private Document document
;
67 * The default color for the TEditor class.
69 private CellAttributes defaultColor
= null;
72 * The topmost line number in the visible area. 0-based.
74 private int topLine
= 0;
77 * The leftmost column number in the visible area. 0-based.
79 private int leftColumn
= 0;
81 // ------------------------------------------------------------------------
82 // Constructors -----------------------------------------------------------
83 // ------------------------------------------------------------------------
88 * @param parent parent widget
89 * @param text text on the screen
90 * @param x column relative to parent
91 * @param y row relative to parent
92 * @param width width of text area
93 * @param height height of text area
95 public TEditorWidget(final TWidget parent
, final String text
, final int x
,
96 final int y
, final int width
, final int height
) {
98 // Set parent and window
99 super(parent
, x
, y
, width
, height
);
101 setCursorVisible(true);
103 defaultColor
= getTheme().getColor("teditor");
104 document
= new Document(text
, defaultColor
);
107 // ------------------------------------------------------------------------
108 // TWidget ----------------------------------------------------------------
109 // ------------------------------------------------------------------------
116 for (int i
= 0; i
< getHeight(); i
++) {
118 getScreen().hLineXY(0, i
, getWidth(), ' ', defaultColor
);
120 // Now draw document's line
121 if (topLine
+ i
< document
.getLineCount()) {
122 Line line
= document
.getLine(topLine
+ i
);
124 for (Word word
: line
.getWords()) {
125 // For now, we are cheating: draw outside the left region
126 // if needed and let screen do the clipping.
127 getScreen().putStringXY(x
- leftColumn
, i
, word
.getText(),
129 x
+= word
.getDisplayLength();
130 if (x
- leftColumn
> getWidth()) {
139 * Handle mouse press events.
141 * @param mouse mouse button press event
144 public void onMouseDown(final TMouseEvent mouse
) {
145 if (mouse
.isMouseWheelUp()) {
146 for (int i
= 0; i
< wheelScrollSize
; i
++) {
149 alignDocument(false);
154 if (mouse
.isMouseWheelDown()) {
155 for (int i
= 0; i
< wheelScrollSize
; i
++) {
156 if (topLine
< document
.getLineCount() - 1) {
164 if (mouse
.isMouse1()) {
165 // Set the row and column
166 int newLine
= topLine
+ mouse
.getY();
167 int newX
= leftColumn
+ mouse
.getX();
168 if (newLine
> document
.getLineCount() - 1) {
170 document
.setLineNumber(document
.getLineCount() - 1);
172 if (newLine
> document
.getLineCount() - 1) {
173 setCursorY(document
.getLineCount() - 1 - topLine
);
175 setCursorY(mouse
.getY());
181 document
.setLineNumber(newLine
);
182 setCursorY(mouse
.getY());
183 if (newX
>= document
.getCurrentLine().getDisplayLength()) {
187 document
.setCursor(newX
);
188 setCursorX(mouse
.getX());
194 super.onMouseDown(mouse
);
200 * @param keypress keystroke event
203 public void onKeypress(final TKeypressEvent keypress
) {
204 if (keypress
.equals(kbLeft
)) {
207 } else if (keypress
.equals(kbRight
)) {
210 } else if (keypress
.equals(kbAltLeft
)
211 || keypress
.equals(kbCtrlLeft
)
213 document
.backwardsWord();
215 } else if (keypress
.equals(kbAltRight
)
216 || keypress
.equals(kbCtrlRight
)
218 document
.forwardsWord();
220 } else if (keypress
.equals(kbUp
)) {
223 } else if (keypress
.equals(kbDown
)) {
226 } else if (keypress
.equals(kbPgUp
)) {
227 document
.up(getHeight() - 1);
229 } else if (keypress
.equals(kbPgDn
)) {
230 document
.down(getHeight() - 1);
232 } else if (keypress
.equals(kbHome
)) {
233 if (document
.home()) {
235 if (leftColumn
< 0) {
240 } else if (keypress
.equals(kbEnd
)) {
241 if (document
.end()) {
244 } else if (keypress
.equals(kbCtrlHome
)) {
245 document
.setLineNumber(0);
251 } else if (keypress
.equals(kbCtrlEnd
)) {
252 document
.setLineNumber(document
.getLineCount() - 1);
255 } else if (keypress
.equals(kbIns
)) {
256 document
.setOverwrite(!document
.getOverwrite());
257 } else if (keypress
.equals(kbDel
)) {
260 } else if (keypress
.equals(kbBackspace
)
261 || keypress
.equals(kbBackspaceDel
)
263 document
.backspace();
265 } else if (keypress
.equals(kbTab
)) {
266 // TODO: tab character. For now just add spaces until we hit
268 for (int i
= document
.getCursor(); (i
+ 1) % 8 != 0; i
++) {
269 document
.addChar(' ');
272 } else if (keypress
.equals(kbEnter
)) {
275 } else if (!keypress
.getKey().isFnKey()
276 && !keypress
.getKey().isAlt()
277 && !keypress
.getKey().isCtrl()
279 // Plain old keystroke, process it
280 document
.addChar(keypress
.getKey().getChar());
283 // Pass other keys (tab etc.) on to TWidget
284 super.onKeypress(keypress
);
289 * Method that subclasses can override to handle window/screen resize
292 * @param resize resize event
295 public void onResize(final TResizeEvent resize
) {
296 // Change my width/height, and pull the cursor in as needed.
297 if (resize
.getType() == TResizeEvent
.Type
.WIDGET
) {
298 setWidth(resize
.getWidth());
299 setHeight(resize
.getHeight());
300 // See if the cursor is now outside the window, and if so move
302 if (getCursorX() >= getWidth()) {
303 leftColumn
+= getCursorX() - (getWidth() - 1);
304 setCursorX(getWidth() - 1);
306 if (getCursorY() >= getHeight()) {
307 topLine
+= getCursorY() - (getHeight() - 1);
308 setCursorY(getHeight() - 1);
311 // Let superclass handle it
312 super.onResize(resize
);
316 // ------------------------------------------------------------------------
317 // TEditorWidget ----------------------------------------------------------
318 // ------------------------------------------------------------------------
321 * Align visible area with document current line.
323 * @param topLineIsTop if true, make the top visible line the document
324 * current line if it was off-screen. If false, make the bottom visible
325 * line the document current line.
327 private void alignTopLine(final boolean topLineIsTop
) {
328 int line
= document
.getLineNumber();
330 if ((line
< topLine
) || (line
> topLine
+ getHeight() - 1)) {
331 // Need to move topLine to bring document back into view.
333 topLine
= line
- (getHeight() - 1);
337 assert (topLine
>= 0);
340 assert (topLine
>= 0);
345 System.err.println("line " + line + " topLine " + topLine);
348 // Document is in view, let's set cursorY
349 assert (line
>= topLine
);
350 setCursorY(line
- topLine
);
355 * Align document current line with visible area.
357 * @param topLineIsTop if true, make the top visible line the document
358 * current line if it was off-screen. If false, make the bottom visible
359 * line the document current line.
361 private void alignDocument(final boolean topLineIsTop
) {
362 int line
= document
.getLineNumber();
363 int cursor
= document
.getCursor();
365 if ((line
< topLine
) || (line
> topLine
+ getHeight() - 1)) {
366 // Need to move document to ensure it fits view.
368 document
.setLineNumber(topLine
);
370 document
.setLineNumber(topLine
+ (getHeight() - 1));
372 if (cursor
< document
.getCurrentLine().getDisplayLength()) {
373 document
.setCursor(cursor
);
378 System.err.println("getLineNumber() " + document.getLineNumber() +
379 " topLine " + topLine);
382 // Document is in view, let's set cursorY
383 setCursorY(document
.getLineNumber() - topLine
);
388 * Align visible cursor with document cursor.
390 private void alignCursor() {
391 int width
= getWidth();
393 int desiredX
= document
.getCursor() - leftColumn
;
395 // We need to push the screen to the left.
396 leftColumn
= document
.getCursor();
397 } else if (desiredX
> width
- 1) {
398 // We need to push the screen to the right.
399 leftColumn
= document
.getCursor() - (width
- 1);
403 System.err.println("document cursor " + document.getCursor() +
404 " leftColumn " + leftColumn);
408 setCursorX(document
.getCursor() - leftColumn
);
412 * Get the number of lines in the underlying Document.
414 * @return the number of lines
416 public int getLineCount() {
417 return document
.getLineCount();
421 * Get the current visible top row number. 1-based.
423 * @return the visible top row number. Row 1 is the first row.
425 public int getVisibleRowNumber() {
430 * Set the current visible row number. 1-based.
432 * @param row the new visible row number. Row 1 is the first row.
434 public void setVisibleRowNumber(final int row
) {
436 if ((row
> 0) && (row
< document
.getLineCount())) {
443 * Get the current editing row number. 1-based.
445 * @return the editing row number. Row 1 is the first row.
447 public int getEditingRowNumber() {
448 return document
.getLineNumber() + 1;
452 * Set the current editing row number. 1-based.
454 * @param row the new editing row number. Row 1 is the first row.
456 public void setEditingRowNumber(final int row
) {
458 if ((row
> 0) && (row
< document
.getLineCount())) {
459 document
.setLineNumber(row
- 1);
465 * Set the current visible column number. 1-based.
467 * @return the visible column number. Column 1 is the first column.
469 public int getVisibleColumnNumber() {
470 return leftColumn
+ 1;
474 * Set the current visible column number. 1-based.
476 * @param column the new visible column number. Column 1 is the first
479 public void setVisibleColumnNumber(final int column
) {
481 if ((column
> 0) && (column
< document
.getLineLengthMax())) {
482 leftColumn
= column
- 1;
488 * Get the current editing column number. 1-based.
490 * @return the editing column number. Column 1 is the first column.
492 public int getEditingColumnNumber() {
493 return document
.getCursor() + 1;
497 * Set the current editing column number. 1-based.
499 * @param column the new editing column number. Column 1 is the first
502 public void setEditingColumnNumber(final int column
) {
503 if ((column
> 0) && (column
< document
.getLineLength())) {
504 document
.setCursor(column
- 1);
510 * Get the maximum possible row number. 1-based.
512 * @return the maximum row number. Row 1 is the first row.
514 public int getMaximumRowNumber() {
515 return document
.getLineCount() + 1;
519 * Get the maximum possible column number. 1-based.
521 * @return the maximum column number. Column 1 is the first column.
523 public int getMaximumColumnNumber() {
524 return document
.getLineLengthMax() + 1;
528 * Get the dirty value.
530 * @return true if the buffer is dirty
532 public boolean isDirty() {
533 return document
.isDirty();
537 * Save contents to file.
539 * @param filename file to save to
540 * @throws IOException if a java.io operation throws
542 public void saveToFilename(final String filename
) throws IOException
{
543 document
.saveToFilename(filename
);