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 jexer
.bits
.CellAttributes
;
32 import jexer
.bits
.GraphicsChars
;
33 import jexer
.event
.TKeypressEvent
;
34 import jexer
.event
.TMouseEvent
;
35 import static jexer
.TKeypress
.*;
38 * TField implements an editable text field.
40 public class TField
extends TWidget
{
42 // ------------------------------------------------------------------------
43 // Variables --------------------------------------------------------------
44 // ------------------------------------------------------------------------
49 protected String text
= "";
52 * If true, only allow enough characters that will fit in the width. If
53 * false, allow the field to scroll to the right.
55 protected boolean fixed
= false;
58 * Current editing position within text.
60 protected int position
= 0;
63 * Beginning of visible portion.
65 protected int windowStart
= 0;
68 * If true, new characters are inserted at position.
70 protected boolean insertMode
= true;
73 * Remember mouse state.
75 protected TMouseEvent mouse
;
78 * The action to perform when the user presses enter.
80 protected TAction enterAction
;
83 * The action to perform when the text is updated.
85 protected TAction updateAction
;
87 // ------------------------------------------------------------------------
88 // Constructors -----------------------------------------------------------
89 // ------------------------------------------------------------------------
94 * @param parent parent widget
95 * @param x column relative to parent
96 * @param y row relative to parent
97 * @param width visible text width
98 * @param fixed if true, the text cannot exceed the display width
100 public TField(final TWidget parent
, final int x
, final int y
,
101 final int width
, final boolean fixed
) {
103 this(parent
, x
, y
, width
, fixed
, "", null, null);
107 * Public constructor.
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 * @param text initial text, default is empty string
116 public TField(final TWidget parent
, final int x
, final int y
,
117 final int width
, final boolean fixed
, final String text
) {
119 this(parent
, x
, y
, width
, fixed
, text
, null, null);
123 * Public constructor.
125 * @param parent parent widget
126 * @param x column relative to parent
127 * @param y row relative to parent
128 * @param width visible text width
129 * @param fixed if true, the text cannot exceed the display width
130 * @param text initial text, default is empty string
131 * @param enterAction function to call when enter key is pressed
132 * @param updateAction function to call when the text is updated
134 public TField(final TWidget parent
, final int x
, final int y
,
135 final int width
, final boolean fixed
, final String text
,
136 final TAction enterAction
, final TAction updateAction
) {
138 // Set parent and window
139 super(parent
, x
, y
, width
, 1);
141 setCursorVisible(true);
144 this.enterAction
= enterAction
;
145 this.updateAction
= updateAction
;
148 // ------------------------------------------------------------------------
149 // Event handlers ---------------------------------------------------------
150 // ------------------------------------------------------------------------
153 * Returns true if the mouse is currently on the field.
155 * @return if true the mouse is currently on the field
157 protected boolean mouseOnField() {
158 int rightEdge
= getWidth() - 1;
160 && (mouse
.getY() == 0)
161 && (mouse
.getX() >= 0)
162 && (mouse
.getX() <= rightEdge
)
170 * Handle mouse button presses.
172 * @param mouse mouse button event
175 public void onMouseDown(final TMouseEvent mouse
) {
178 if ((mouseOnField()) && (mouse
.isMouse1())) {
180 int deltaX
= mouse
.getX() - getCursorX();
182 if (position
> text
.length()) {
183 position
= text
.length();
193 * @param keypress keystroke event
196 public void onKeypress(final TKeypressEvent keypress
) {
198 if (keypress
.equals(kbLeft
)) {
202 if (fixed
== false) {
203 if ((position
== windowStart
) && (windowStart
> 0)) {
207 normalizeWindowStart();
211 if (keypress
.equals(kbRight
)) {
212 if (position
< text
.length()) {
215 if (position
== getWidth()) {
219 if ((position
- windowStart
) == getWidth()) {
227 if (keypress
.equals(kbEnter
)) {
232 if (keypress
.equals(kbIns
)) {
233 insertMode
= !insertMode
;
236 if (keypress
.equals(kbHome
)) {
241 if (keypress
.equals(kbEnd
)) {
246 if (keypress
.equals(kbDel
)) {
247 if ((text
.length() > 0) && (position
< text
.length())) {
248 text
= text
.substring(0, position
)
249 + text
.substring(position
+ 1);
255 if (keypress
.equals(kbBackspace
) || keypress
.equals(kbBackspaceDel
)) {
258 text
= text
.substring(0, position
)
259 + text
.substring(position
+ 1);
261 if (fixed
== false) {
262 if ((position
== windowStart
)
269 normalizeWindowStart();
273 if (!keypress
.getKey().isFnKey()
274 && !keypress
.getKey().isAlt()
275 && !keypress
.getKey().isCtrl()
277 // Plain old keystroke, process it
278 if ((position
== text
.length())
279 && (text
.length() < getWidth())) {
282 appendChar(keypress
.getKey().getChar());
283 } else if ((position
< text
.length())
284 && (text
.length() < getWidth())) {
286 // Overwrite or insert a character
287 if (insertMode
== false) {
289 text
= text
.substring(0, position
)
290 + keypress
.getKey().getChar()
291 + text
.substring(position
+ 1);
295 insertChar(keypress
.getKey().getChar());
297 } else if ((position
< text
.length())
298 && (text
.length() >= getWidth())) {
300 // Multiple cases here
301 if ((fixed
== true) && (insertMode
== true)) {
302 // Buffer is full, do nothing
303 } else if ((fixed
== true) && (insertMode
== false)) {
304 // Overwrite the last character, maybe move position
305 text
= text
.substring(0, position
)
306 + keypress
.getKey().getChar()
307 + text
.substring(position
+ 1);
308 if (position
< getWidth() - 1) {
311 } else if ((fixed
== false) && (insertMode
== false)) {
312 // Overwrite the last character, definitely move position
313 text
= text
.substring(0, position
)
314 + keypress
.getKey().getChar()
315 + text
.substring(position
+ 1);
318 if (position
== text
.length()) {
319 // Append this character
320 appendChar(keypress
.getKey().getChar());
322 // Insert this character
323 insertChar(keypress
.getKey().getChar());
329 // Append this character
330 appendChar(keypress
.getKey().getChar());
336 // Pass to parent for the things we don't care about.
337 super.onKeypress(keypress
);
340 // ------------------------------------------------------------------------
341 // TWidget ----------------------------------------------------------------
342 // ------------------------------------------------------------------------
345 * Draw the text field.
349 CellAttributes fieldColor
;
351 if (isAbsoluteActive()) {
352 fieldColor
= getTheme().getColor("tfield.active");
354 fieldColor
= getTheme().getColor("tfield.inactive");
357 int end
= windowStart
+ getWidth();
358 if (end
> text
.length()) {
361 hLineXY(0, 0, getWidth(), GraphicsChars
.HATCH
, fieldColor
);
362 putStringXY(0, 0, text
.substring(windowStart
, end
), fieldColor
);
364 // Fix the cursor, it will be rendered by TApplication.drawAll().
368 // ------------------------------------------------------------------------
369 // TField -----------------------------------------------------------------
370 // ------------------------------------------------------------------------
377 public final String
getText() {
384 * @param text the new field text
386 public void setText(final String text
) {
387 assert (text
!= null);
394 * Dispatch to the action function.
396 * @param enter if true, the user pressed Enter, else this was an update
399 protected void dispatch(final boolean enter
) {
401 if (enterAction
!= null) {
405 if (updateAction
!= null) {
412 * Update the visible cursor position to match the location of position
415 protected void updateCursor() {
416 if ((position
> getWidth()) && fixed
) {
417 setCursorX(getWidth());
418 } else if ((position
- windowStart
== getWidth()) && !fixed
) {
419 setCursorX(getWidth() - 1);
421 setCursorX(position
- windowStart
);
426 * Normalize windowStart such that most of the field data if visible.
428 protected void normalizeWindowStart() {
430 // windowStart had better be zero, there is nothing to do here.
431 assert (windowStart
== 0);
434 windowStart
= position
- (getWidth() - 1);
435 if (windowStart
< 0) {
443 * Append char to the end of the field.
445 * @param ch = char to append
447 protected void appendChar(final char ch
) {
448 // Append the LAST character
452 assert (position
== text
.length());
455 if (position
== getWidth()) {
459 if ((position
- windowStart
) == getWidth()) {
466 * Insert char somewhere in the middle of the field.
468 * @param ch char to append
470 protected void insertChar(final char ch
) {
471 text
= text
.substring(0, position
) + ch
+ text
.substring(position
);
473 if ((position
- windowStart
) == getWidth()) {
480 * Position the cursor at the first column. The field may adjust the
481 * window start to show as much of the field as possible.
489 * Set the editing position to the last filled character. The field may
490 * adjust the window start to show as much of the field as possible.
493 position
= text
.length();
495 if (position
>= getWidth()) {
496 position
= text
.length() - 1;
499 windowStart
= text
.length() - getWidth() + 1;
500 if (windowStart
< 0) {
507 * Set the editing position. The field may adjust the window start to
508 * show as much of the field as possible.
510 * @param position the new position
511 * @throws IndexOutOfBoundsException if position is outside the range of
514 public void setPosition(final int position
) {
515 if ((position
< 0) || (position
>= text
.length())) {
516 throw new IndexOutOfBoundsException("Max length is " +
517 text
.length() + ", requested position " + position
);
519 this.position
= position
;
520 normalizeWindowStart();