stubs for TTableWidget
[fanfix.git] / src / jexer / TField.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2019 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.bits.GraphicsChars;
33 import jexer.event.TKeypressEvent;
34 import jexer.event.TMouseEvent;
35 import static jexer.TKeypress.*;
36
37 /**
38 * TField implements an editable text field.
39 */
40 public class TField extends TWidget {
41
42 // ------------------------------------------------------------------------
43 // Variables --------------------------------------------------------------
44 // ------------------------------------------------------------------------
45
46 /**
47 * Background character for unfilled-in text.
48 */
49 protected char backgroundChar = GraphicsChars.HATCH;
50
51 /**
52 * Field text.
53 */
54 protected String text = "";
55
56 /**
57 * If true, only allow enough characters that will fit in the width. If
58 * false, allow the field to scroll to the right.
59 */
60 protected boolean fixed = false;
61
62 /**
63 * Current editing position within text.
64 */
65 protected int position = 0;
66
67 /**
68 * Beginning of visible portion.
69 */
70 protected int windowStart = 0;
71
72 /**
73 * If true, new characters are inserted at position.
74 */
75 protected boolean insertMode = true;
76
77 /**
78 * Remember mouse state.
79 */
80 protected TMouseEvent mouse;
81
82 /**
83 * The action to perform when the user presses enter.
84 */
85 protected TAction enterAction;
86
87 /**
88 * The action to perform when the text is updated.
89 */
90 protected TAction updateAction;
91
92 /**
93 * The color to use when this field is active.
94 */
95 private String activeColorKey = "tfield.active";
96
97 /**
98 * The color to use when this field is not active.
99 */
100 private String inactiveColorKey = "tfield.inactive";
101
102 // ------------------------------------------------------------------------
103 // Constructors -----------------------------------------------------------
104 // ------------------------------------------------------------------------
105
106 /**
107 * Public constructor.
108 *
109 * @param parent parent widget
110 * @param x column relative to parent
111 * @param y row relative to parent
112 * @param width visible text width
113 * @param fixed if true, the text cannot exceed the display width
114 */
115 public TField(final TWidget parent, final int x, final int y,
116 final int width, final boolean fixed) {
117
118 this(parent, x, y, width, fixed, "", null, null);
119 }
120
121 /**
122 * Public constructor.
123 *
124 * @param parent parent widget
125 * @param x column relative to parent
126 * @param y row relative to parent
127 * @param width visible text width
128 * @param fixed if true, the text cannot exceed the display width
129 * @param text initial text, default is empty string
130 */
131 public TField(final TWidget parent, final int x, final int y,
132 final int width, final boolean fixed, final String text) {
133
134 this(parent, x, y, width, fixed, text, null, null);
135 }
136
137 /**
138 * Public constructor.
139 *
140 * @param parent parent widget
141 * @param x column relative to parent
142 * @param y row relative to parent
143 * @param width visible text width
144 * @param fixed if true, the text cannot exceed the display width
145 * @param text initial text, default is empty string
146 * @param enterAction function to call when enter key is pressed
147 * @param updateAction function to call when the text is updated
148 */
149 public TField(final TWidget parent, final int x, final int y,
150 final int width, final boolean fixed, final String text,
151 final TAction enterAction, final TAction updateAction) {
152
153 // Set parent and window
154 super(parent, x, y, width, 1);
155
156 setCursorVisible(true);
157 this.fixed = fixed;
158 this.text = text;
159 this.enterAction = enterAction;
160 this.updateAction = updateAction;
161 }
162
163 // ------------------------------------------------------------------------
164 // Event handlers ---------------------------------------------------------
165 // ------------------------------------------------------------------------
166
167 /**
168 * Returns true if the mouse is currently on the field.
169 *
170 * @return if true the mouse is currently on the field
171 */
172 protected boolean mouseOnField() {
173 int rightEdge = getWidth() - 1;
174 if ((mouse != null)
175 && (mouse.getY() == 0)
176 && (mouse.getX() >= 0)
177 && (mouse.getX() <= rightEdge)
178 ) {
179 return true;
180 }
181 return false;
182 }
183
184 /**
185 * Handle mouse button presses.
186 *
187 * @param mouse mouse button event
188 */
189 @Override
190 public void onMouseDown(final TMouseEvent mouse) {
191 this.mouse = mouse;
192
193 if ((mouseOnField()) && (mouse.isMouse1())) {
194 // Move cursor
195 int deltaX = mouse.getX() - getCursorX();
196 position += deltaX;
197 if (position > text.length()) {
198 position = text.length();
199 }
200 updateCursor();
201 return;
202 }
203 }
204
205 /**
206 * Handle keystrokes.
207 *
208 * @param keypress keystroke event
209 */
210 @Override
211 public void onKeypress(final TKeypressEvent keypress) {
212
213 if (keypress.equals(kbLeft)) {
214 if (position > 0) {
215 position--;
216 }
217 if (fixed == false) {
218 if ((position == windowStart) && (windowStart > 0)) {
219 windowStart--;
220 }
221 }
222 normalizeWindowStart();
223 return;
224 }
225
226 if (keypress.equals(kbRight)) {
227 if (position < text.length()) {
228 position++;
229 if (fixed == true) {
230 if (position == getWidth()) {
231 position--;
232 }
233 } else {
234 if ((position - windowStart) == getWidth()) {
235 windowStart++;
236 }
237 }
238 }
239 return;
240 }
241
242 if (keypress.equals(kbEnter)) {
243 dispatch(true);
244 return;
245 }
246
247 if (keypress.equals(kbIns)) {
248 insertMode = !insertMode;
249 return;
250 }
251 if (keypress.equals(kbHome)) {
252 home();
253 return;
254 }
255
256 if (keypress.equals(kbEnd)) {
257 end();
258 return;
259 }
260
261 if (keypress.equals(kbDel)) {
262 if ((text.length() > 0) && (position < text.length())) {
263 text = text.substring(0, position)
264 + text.substring(position + 1);
265 }
266 dispatch(false);
267 return;
268 }
269
270 if (keypress.equals(kbBackspace) || keypress.equals(kbBackspaceDel)) {
271 if (position > 0) {
272 position--;
273 text = text.substring(0, position)
274 + text.substring(position + 1);
275 }
276 if (fixed == false) {
277 if ((position == windowStart)
278 && (windowStart > 0)
279 ) {
280 windowStart--;
281 }
282 }
283 dispatch(false);
284 normalizeWindowStart();
285 return;
286 }
287
288 if (!keypress.getKey().isFnKey()
289 && !keypress.getKey().isAlt()
290 && !keypress.getKey().isCtrl()
291 ) {
292 // Plain old keystroke, process it
293 if ((position == text.length())
294 && (text.length() < getWidth())) {
295
296 // Append case
297 appendChar(keypress.getKey().getChar());
298 } else if ((position < text.length())
299 && (text.length() < getWidth())) {
300
301 // Overwrite or insert a character
302 if (insertMode == false) {
303 // Replace character
304 text = text.substring(0, position)
305 + keypress.getKey().getChar()
306 + text.substring(position + 1);
307 position++;
308 } else {
309 // Insert character
310 insertChar(keypress.getKey().getChar());
311 }
312 } else if ((position < text.length())
313 && (text.length() >= getWidth())) {
314
315 // Multiple cases here
316 if ((fixed == true) && (insertMode == true)) {
317 // Buffer is full, do nothing
318 } else if ((fixed == true) && (insertMode == false)) {
319 // Overwrite the last character, maybe move position
320 text = text.substring(0, position)
321 + keypress.getKey().getChar()
322 + text.substring(position + 1);
323 if (position < getWidth() - 1) {
324 position++;
325 }
326 } else if ((fixed == false) && (insertMode == false)) {
327 // Overwrite the last character, definitely move position
328 text = text.substring(0, position)
329 + keypress.getKey().getChar()
330 + text.substring(position + 1);
331 position++;
332 } else {
333 if (position == text.length()) {
334 // Append this character
335 appendChar(keypress.getKey().getChar());
336 } else {
337 // Insert this character
338 insertChar(keypress.getKey().getChar());
339 }
340 }
341 } else {
342 assert (!fixed);
343
344 // Append this character
345 appendChar(keypress.getKey().getChar());
346 }
347 dispatch(false);
348 return;
349 }
350
351 // Pass to parent for the things we don't care about.
352 super.onKeypress(keypress);
353 }
354
355 // ------------------------------------------------------------------------
356 // TWidget ----------------------------------------------------------------
357 // ------------------------------------------------------------------------
358
359 /**
360 * Draw the text field.
361 */
362 @Override
363 public void draw() {
364 CellAttributes fieldColor;
365
366 if (isAbsoluteActive()) {
367 fieldColor = getTheme().getColor(activeColorKey);
368 } else {
369 fieldColor = getTheme().getColor(inactiveColorKey);
370 }
371
372 int end = windowStart + getWidth();
373 if (end > text.length()) {
374 end = text.length();
375 }
376 hLineXY(0, 0, getWidth(), backgroundChar, fieldColor);
377 putStringXY(0, 0, text.substring(windowStart, end), fieldColor);
378
379 // Fix the cursor, it will be rendered by TApplication.drawAll().
380 updateCursor();
381 }
382
383 // ------------------------------------------------------------------------
384 // TField -----------------------------------------------------------------
385 // ------------------------------------------------------------------------
386
387 /**
388 * Get field background character.
389 *
390 * @return background character
391 */
392 public final char getBackgroundChar() {
393 return backgroundChar;
394 }
395
396 /**
397 * Set field background character.
398 *
399 * @param backgroundChar the background character
400 */
401 public void setBackgroundChar(final char backgroundChar) {
402 this.backgroundChar = backgroundChar;
403 }
404
405 /**
406 * Get field text.
407 *
408 * @return field text
409 */
410 public final String getText() {
411 return text;
412 }
413
414 /**
415 * Set field text.
416 *
417 * @param text the new field text
418 */
419 public void setText(final String text) {
420 assert (text != null);
421 this.text = text;
422 position = 0;
423 windowStart = 0;
424 }
425
426 /**
427 * Dispatch to the action function.
428 *
429 * @param enter if true, the user pressed Enter, else this was an update
430 * to the text.
431 */
432 protected void dispatch(final boolean enter) {
433 if (enter) {
434 if (enterAction != null) {
435 enterAction.DO();
436 }
437 } else {
438 if (updateAction != null) {
439 updateAction.DO();
440 }
441 }
442 }
443
444 /**
445 * Update the visible cursor position to match the location of position
446 * and windowStart.
447 */
448 protected void updateCursor() {
449 if ((position > getWidth()) && fixed) {
450 setCursorX(getWidth());
451 } else if ((position - windowStart == getWidth()) && !fixed) {
452 setCursorX(getWidth() - 1);
453 } else {
454 setCursorX(position - windowStart);
455 }
456 }
457
458 /**
459 * Normalize windowStart such that most of the field data if visible.
460 */
461 protected void normalizeWindowStart() {
462 if (fixed) {
463 // windowStart had better be zero, there is nothing to do here.
464 assert (windowStart == 0);
465 return;
466 }
467 windowStart = position - (getWidth() - 1);
468 if (windowStart < 0) {
469 windowStart = 0;
470 }
471
472 updateCursor();
473 }
474
475 /**
476 * Append char to the end of the field.
477 *
478 * @param ch = char to append
479 */
480 protected void appendChar(final char ch) {
481 // Append the LAST character
482 text += ch;
483 position++;
484
485 assert (position == text.length());
486
487 if (fixed) {
488 if (position == getWidth()) {
489 position--;
490 }
491 } else {
492 if ((position - windowStart) == getWidth()) {
493 windowStart++;
494 }
495 }
496 }
497
498 /**
499 * Insert char somewhere in the middle of the field.
500 *
501 * @param ch char to append
502 */
503 protected void insertChar(final char ch) {
504 text = text.substring(0, position) + ch + text.substring(position);
505 position++;
506 if ((position - windowStart) == getWidth()) {
507 assert (!fixed);
508 windowStart++;
509 }
510 }
511
512 /**
513 * Position the cursor at the first column. The field may adjust the
514 * window start to show as much of the field as possible.
515 */
516 public void home() {
517 position = 0;
518 windowStart = 0;
519 }
520
521 /**
522 * Set the editing position to the last filled character. The field may
523 * adjust the window start to show as much of the field as possible.
524 */
525 public void end() {
526 position = text.length();
527 if (fixed == true) {
528 if (position >= getWidth()) {
529 position = text.length() - 1;
530 }
531 } else {
532 windowStart = text.length() - getWidth() + 1;
533 if (windowStart < 0) {
534 windowStart = 0;
535 }
536 }
537 }
538
539 /**
540 * Set the editing position. The field may adjust the window start to
541 * show as much of the field as possible.
542 *
543 * @param position the new position
544 * @throws IndexOutOfBoundsException if position is outside the range of
545 * the available text
546 */
547 public void setPosition(final int position) {
548 if ((position < 0) || (position >= text.length())) {
549 throw new IndexOutOfBoundsException("Max length is " +
550 text.length() + ", requested position " + position);
551 }
552 this.position = position;
553 normalizeWindowStart();
554 }
555
556 /**
557 * Set the active color key.
558 *
559 * @param activeColorKey ColorTheme key color to use when this field is
560 * active
561 */
562 public void setActiveColorKey(final String activeColorKey) {
563 this.activeColorKey = activeColorKey;
564 }
565
566 /**
567 * Set the inactive color key.
568 *
569 * @param inactiveColorKey ColorTheme key color to use when this field is
570 * inactive
571 */
572 public void setInactiveColorKey(final String inactiveColorKey) {
573 this.inactiveColorKey = inactiveColorKey;
574 }
575
576
577 }