TEditor 50% complete
[nikiroo-utils.git] / src / jexer / TEditorWidget.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2017 Kevin Lamonte
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 */
29 package jexer;
30
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.*;
39
40 /**
41 * TEditorWidget displays an editable text document. It is unaware of
42 * scrolling behavior, but can respond to mouse and keyboard events.
43 */
44 public final class TEditorWidget extends TWidget {
45
46 /**
47 * The document being edited.
48 */
49 private Document document;
50
51 /**
52 * The default color for the TEditor class.
53 */
54 private CellAttributes defaultColor = null;
55
56 /**
57 * The topmost line number in the visible area. 0-based.
58 */
59 private int topLine = 0;
60
61 /**
62 * The leftmost column number in the visible area. 0-based.
63 */
64 private int leftColumn = 0;
65
66 /**
67 * Public constructor.
68 *
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
75 */
76 public TEditorWidget(final TWidget parent, final String text, final int x,
77 final int y, final int width, final int height) {
78
79 // Set parent and window
80 super(parent, x, y, width, height);
81
82 setCursorVisible(true);
83
84 defaultColor = getTheme().getColor("teditor");
85 document = new Document(text, defaultColor);
86 }
87
88 /**
89 * Draw the text box.
90 */
91 @Override
92 public void draw() {
93 for (int i = 0; i < getHeight(); i++) {
94 // Background line
95 getScreen().hLineXY(0, i, getWidth(), ' ', defaultColor);
96
97 // Now draw document's line
98 if (topLine + i < document.getLineCount()) {
99 Line line = document.getLine(topLine + i);
100 int x = 0;
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(),
105 word.getColor());
106 x += word.getDisplayLength();
107 if (x - leftColumn > getWidth()) {
108 break;
109 }
110 }
111 }
112 }
113
114 }
115
116 /**
117 * Handle mouse press events.
118 *
119 * @param mouse mouse button press event
120 */
121 @Override
122 public void onMouseDown(final TMouseEvent mouse) {
123 if (mouse.isMouseWheelUp()) {
124 if (getCursorY() == getHeight() - 1) {
125 if (document.up()) {
126 if (topLine > 0) {
127 topLine--;
128 }
129 alignCursor();
130 }
131 } else {
132 if (topLine > 0) {
133 topLine--;
134 setCursorY(getCursorY() + 1);
135 }
136 }
137 return;
138 }
139 if (mouse.isMouseWheelDown()) {
140 if (getCursorY() == 0) {
141 if (document.down()) {
142 if (topLine < document.getLineNumber()) {
143 topLine++;
144 }
145 alignCursor();
146 }
147 } else {
148 if (topLine < document.getLineCount() - getHeight()) {
149 topLine++;
150 setCursorY(getCursorY() - 1);
151 }
152 }
153 return;
154 }
155
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()) {
161 // Go to the end
162 document.setLineNumber(document.getLineCount() - 1);
163 document.end();
164 if (document.getLineCount() > getHeight()) {
165 setCursorY(getHeight() - 1);
166 } else {
167 setCursorY(document.getLineCount() - 1);
168 }
169 alignCursor();
170 return;
171 }
172
173 document.setLineNumber(newLine);
174 setCursorY(mouse.getY());
175 if (newX > document.getCurrentLine().getDisplayLength()) {
176 document.end();
177 alignCursor();
178 } else {
179 setCursorX(mouse.getX());
180 }
181 return;
182 }
183
184 // Pass to children
185 super.onMouseDown(mouse);
186 }
187
188 /**
189 * Align visible cursor with document cursor.
190 */
191 private void alignCursor() {
192 int width = getWidth();
193
194 int desiredX = document.getCursor() - leftColumn;
195 if (desiredX < 0) {
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);
201 }
202
203 /*
204 System.err.println("document cursor " + document.getCursor() +
205 " leftColumn " + leftColumn);
206 */
207
208 setCursorX(document.getCursor() - leftColumn);
209 }
210
211 /**
212 * Handle keystrokes.
213 *
214 * @param keypress keystroke event
215 */
216 @Override
217 public void onKeypress(final TKeypressEvent keypress) {
218 if (keypress.equals(kbLeft)) {
219 if (document.left()) {
220 alignCursor();
221 }
222 } else if (keypress.equals(kbRight)) {
223 if (document.right()) {
224 alignCursor();
225 }
226 } else if (keypress.equals(kbUp)) {
227 if (document.up()) {
228 if (getCursorY() > 0) {
229 setCursorY(getCursorY() - 1);
230 } else {
231 if (topLine > 0) {
232 topLine--;
233 }
234 }
235 alignCursor();
236 }
237 } else if (keypress.equals(kbDown)) {
238 if (document.down()) {
239 if (getCursorY() < getHeight() - 1) {
240 setCursorY(getCursorY() + 1);
241 } else {
242 if (topLine < document.getLineCount() - getHeight()) {
243 topLine++;
244 }
245 }
246 alignCursor();
247 }
248 } else if (keypress.equals(kbPgUp)) {
249 for (int i = 0; i < getHeight() - 1; i++) {
250 if (document.up()) {
251 if (getCursorY() > 0) {
252 setCursorY(getCursorY() - 1);
253 } else {
254 if (topLine > 0) {
255 topLine--;
256 }
257 }
258 alignCursor();
259 } else {
260 break;
261 }
262 }
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);
268 } else {
269 if (topLine < document.getLineCount() - getHeight()) {
270 topLine++;
271 }
272 }
273 alignCursor();
274 } else {
275 break;
276 }
277 }
278 } else if (keypress.equals(kbHome)) {
279 if (document.home()) {
280 leftColumn = 0;
281 if (leftColumn < 0) {
282 leftColumn = 0;
283 }
284 setCursorX(0);
285 }
286 } else if (keypress.equals(kbEnd)) {
287 if (document.end()) {
288 alignCursor();
289 }
290 } else if (keypress.equals(kbCtrlHome)) {
291 document.setLineNumber(0);
292 document.home();
293 topLine = 0;
294 leftColumn = 0;
295 setCursorX(0);
296 setCursorY(0);
297 } else if (keypress.equals(kbCtrlEnd)) {
298 document.setLineNumber(document.getLineCount() - 1);
299 document.end();
300 topLine = document.getLineCount() - getHeight();
301 if (topLine < 0) {
302 topLine = 0;
303 }
304 if (document.getLineCount() > getHeight()) {
305 setCursorY(getHeight() - 1);
306 } else {
307 setCursorY(document.getLineCount() - 1);
308 }
309 alignCursor();
310 } else if (keypress.equals(kbIns)) {
311 document.setOverwrite(!document.getOverwrite());
312 } else if (keypress.equals(kbDel)) {
313 document.del();
314 } else if (keypress.equals(kbBackspace)) {
315 document.backspace();
316 alignCursor();
317 } else if (!keypress.getKey().isFnKey()
318 && !keypress.getKey().isAlt()
319 && !keypress.getKey().isCtrl()
320 ) {
321 // Plain old keystroke, process it
322 document.addChar(keypress.getKey().getChar());
323 } else {
324 // Pass other keys (tab etc.) on to TWidget
325 super.onKeypress(keypress);
326 }
327 }
328
329 /**
330 * Method that subclasses can override to handle window/screen resize
331 * events.
332 *
333 * @param resize resize event
334 */
335 @Override
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
342 // things.
343 if (getCursorX() >= getWidth()) {
344 leftColumn += getCursorX() - (getWidth() - 1);
345 setCursorX(getWidth() - 1);
346 }
347 if (getCursorY() >= getHeight()) {
348 topLine += getCursorY() - (getHeight() - 1);
349 setCursorY(getHeight() - 1);
350 }
351 } else {
352 // Let superclass handle it
353 super.onResize(resize);
354 }
355 }
356
357 }