2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2017 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 jexer
.bits
.CellAttributes
;
32 import jexer
.event
.TKeypressEvent
;
33 import jexer
.event
.TMouseEvent
;
34 import jexer
.event
.TResizeEvent
;
35 import jexer
.teditor
.Document
;
36 import jexer
.teditor
.Line
;
37 import jexer
.teditor
.Word
;
38 import static jexer
.TKeypress
.*;
41 * TEditorWidget displays an editable text document. It is unaware of
42 * scrolling behavior, but can respond to mouse and keyboard events.
44 public final class TEditorWidget
extends TWidget
{
47 * The document being edited.
49 private Document document
;
52 * The default color for the TEditor class.
54 private CellAttributes defaultColor
= null;
57 * The topmost line number in the visible area. 0-based.
59 private int topLine
= 0;
62 * The leftmost column number in the visible area. 0-based.
64 private int leftColumn
= 0;
69 * @param parent parent widget
70 * @param text text on the screen
71 * @param x column relative to parent
72 * @param y row relative to parent
73 * @param width width of text area
74 * @param height height of text area
76 public TEditorWidget(final TWidget parent
, final String text
, final int x
,
77 final int y
, final int width
, final int height
) {
79 // Set parent and window
80 super(parent
, x
, y
, width
, height
);
82 setCursorVisible(true);
84 defaultColor
= getTheme().getColor("teditor");
85 document
= new Document(text
, defaultColor
);
93 for (int i
= 0; i
< getHeight(); i
++) {
95 getScreen().hLineXY(0, i
, getWidth(), ' ', defaultColor
);
97 // Now draw document's line
98 if (topLine
+ i
< document
.getLineCount()) {
99 Line line
= document
.getLine(topLine
+ i
);
101 for (Word word
: line
.getWords()) {
102 // For now, we are cheating: draw outside the left region
103 // if needed and let screen do the clipping.
104 getScreen().putStringXY(x
- leftColumn
, i
, word
.getText(),
106 x
+= word
.getDisplayLength();
107 if (x
- leftColumn
> getWidth()) {
117 * Handle mouse press events.
119 * @param mouse mouse button press event
122 public void onMouseDown(final TMouseEvent mouse
) {
123 if (mouse
.isMouseWheelUp()) {
124 if (getCursorY() == getHeight() - 1) {
134 setCursorY(getCursorY() + 1);
139 if (mouse
.isMouseWheelDown()) {
140 if (getCursorY() == 0) {
141 if (document
.down()) {
142 if (topLine
< document
.getLineNumber()) {
148 if (topLine
< document
.getLineCount() - getHeight()) {
150 setCursorY(getCursorY() - 1);
156 if (mouse
.isMouse1()) {
157 // Set the row and column
158 int newLine
= topLine
+ mouse
.getY();
159 int newX
= leftColumn
+ mouse
.getX();
160 if (newLine
> document
.getLineCount()) {
162 document
.setLineNumber(document
.getLineCount() - 1);
164 if (document
.getLineCount() > getHeight()) {
165 setCursorY(getHeight() - 1);
167 setCursorY(document
.getLineCount() - 1);
173 document
.setLineNumber(newLine
);
174 setCursorY(mouse
.getY());
175 if (newX
> document
.getCurrentLine().getDisplayLength()) {
179 setCursorX(mouse
.getX());
185 super.onMouseDown(mouse
);
189 * Align visible cursor with document cursor.
191 private void alignCursor() {
192 int width
= getWidth();
194 int desiredX
= document
.getCursor() - leftColumn
;
196 // We need to push the screen to the left.
197 leftColumn
= document
.getCursor();
198 } else if (desiredX
> width
- 1) {
199 // We need to push the screen to the right.
200 leftColumn
= document
.getCursor() - (width
- 1);
204 System.err.println("document cursor " + document.getCursor() +
205 " leftColumn " + leftColumn);
208 setCursorX(document
.getCursor() - leftColumn
);
214 * @param keypress keystroke event
217 public void onKeypress(final TKeypressEvent keypress
) {
218 if (keypress
.equals(kbLeft
)) {
219 if (document
.left()) {
222 } else if (keypress
.equals(kbRight
)) {
223 if (document
.right()) {
226 } else if (keypress
.equals(kbUp
)) {
228 if (getCursorY() > 0) {
229 setCursorY(getCursorY() - 1);
237 } else if (keypress
.equals(kbDown
)) {
238 if (document
.down()) {
239 if (getCursorY() < getHeight() - 1) {
240 setCursorY(getCursorY() + 1);
242 if (topLine
< document
.getLineCount() - getHeight()) {
248 } else if (keypress
.equals(kbPgUp
)) {
249 for (int i
= 0; i
< getHeight() - 1; i
++) {
251 if (getCursorY() > 0) {
252 setCursorY(getCursorY() - 1);
263 } else if (keypress
.equals(kbPgDn
)) {
264 for (int i
= 0; i
< getHeight() - 1; i
++) {
265 if (document
.down()) {
266 if (getCursorY() < getHeight() - 1) {
267 setCursorY(getCursorY() + 1);
269 if (topLine
< document
.getLineCount() - getHeight()) {
278 } else if (keypress
.equals(kbHome
)) {
279 if (document
.home()) {
281 if (leftColumn
< 0) {
286 } else if (keypress
.equals(kbEnd
)) {
287 if (document
.end()) {
290 } else if (keypress
.equals(kbCtrlHome
)) {
291 document
.setLineNumber(0);
297 } else if (keypress
.equals(kbCtrlEnd
)) {
298 document
.setLineNumber(document
.getLineCount() - 1);
300 topLine
= document
.getLineCount() - getHeight();
304 if (document
.getLineCount() > getHeight()) {
305 setCursorY(getHeight() - 1);
307 setCursorY(document
.getLineCount() - 1);
310 } else if (keypress
.equals(kbIns
)) {
311 document
.setOverwrite(!document
.getOverwrite());
312 } else if (keypress
.equals(kbDel
)) {
314 } else if (keypress
.equals(kbBackspace
)) {
315 document
.backspace();
317 } else if (!keypress
.getKey().isFnKey()
318 && !keypress
.getKey().isAlt()
319 && !keypress
.getKey().isCtrl()
321 // Plain old keystroke, process it
322 document
.addChar(keypress
.getKey().getChar());
324 // Pass other keys (tab etc.) on to TWidget
325 super.onKeypress(keypress
);
330 * Method that subclasses can override to handle window/screen resize
333 * @param resize resize event
336 public void onResize(final TResizeEvent resize
) {
337 // Change my width/height, and pull the cursor in as needed.
338 if (resize
.getType() == TResizeEvent
.Type
.WIDGET
) {
339 setWidth(resize
.getWidth());
340 setHeight(resize
.getHeight());
341 // See if the cursor is now outside the window, and if so move
343 if (getCursorX() >= getWidth()) {
344 leftColumn
+= getCursorX() - (getWidth() - 1);
345 setCursorX(getWidth() - 1);
347 if (getCursorY() >= getHeight()) {
348 topLine
+= getCursorY() - (getHeight() - 1);
349 setCursorY(getHeight() - 1);
352 // Let superclass handle it
353 super.onResize(resize
);