083fc90e7d18239892624bc634f60f4b97cebfad
[nikiroo-utils.git] / src / jexer / TField.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * License: LGPLv3 or later
5 *
6 * This module is licensed under the GNU Lesser General Public License
7 * Version 3. Please see the file "COPYING" in this directory for more
8 * information about the GNU Lesser General Public License Version 3.
9 *
10 * Copyright (C) 2015 Kevin Lamonte
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Lesser General Public License
14 * as published by the Free Software Foundation; either version 3 of
15 * the License, or (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with this program; if not, see
24 * http://www.gnu.org/licenses/, or write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
26 * 02110-1301 USA
27 *
28 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
29 * @version 1
30 */
31 package jexer;
32
33 import jexer.bits.CellAttributes;
34 import jexer.bits.GraphicsChars;
35 import jexer.event.TKeypressEvent;
36 import jexer.event.TMouseEvent;
37 import static jexer.TKeypress.*;
38
39 /**
40 * TField implements an editable text field.
41 */
42 public class TField extends TWidget {
43
44 /**
45 * Field text.
46 */
47 protected String text = "";
48
49 /**
50 * Get field text.
51 *
52 * @return field text
53 */
54 public final String getText() {
55 return text;
56 }
57
58 /**
59 * Set field text.
60 *
61 * @param text the new field text
62 */
63 public final void setText(String text) {
64 this.text = text;
65 position = 0;
66 windowStart = 0;
67 }
68
69 /**
70 * If true, only allow enough characters that will fit in the width. If
71 * false, allow the field to scroll to the right.
72 */
73 protected boolean fixed = false;
74
75 /**
76 * Current editing position within text.
77 */
78 protected int position = 0;
79
80 /**
81 * Beginning of visible portion.
82 */
83 protected int windowStart = 0;
84
85 /**
86 * If true, new characters are inserted at position.
87 */
88 protected boolean insertMode = true;
89
90 /**
91 * Remember mouse state.
92 */
93 protected TMouseEvent mouse;
94
95 /**
96 * The action to perform when the user presses enter.
97 */
98 protected TAction enterAction;
99
100 /**
101 * The action to perform when the text is updated.
102 */
103 protected TAction updateAction;
104
105 /**
106 * Public constructor.
107 *
108 * @param parent parent widget
109 * @param x column relative to parent
110 * @param y row relative to parent
111 * @param width visible text width
112 * @param fixed if true, the text cannot exceed the display width
113 */
114 public TField(final TWidget parent, final int x, final int y,
115 final int width, final boolean fixed) {
116
117 this(parent, x, y, width, fixed, "", null, null);
118 }
119
120 /**
121 * Public constructor.
122 *
123 * @param parent parent widget
124 * @param x column relative to parent
125 * @param y row relative to parent
126 * @param width visible text width
127 * @param fixed if true, the text cannot exceed the display width
128 * @param text initial text, default is empty string
129 */
130 public TField(final TWidget parent, final int x, final int y,
131 final int width, final boolean fixed, final String text) {
132
133 this(parent, x, y, width, fixed, text, null, null);
134 }
135
136 /**
137 * Public constructor.
138 *
139 * @param parent parent widget
140 * @param x column relative to parent
141 * @param y row relative to parent
142 * @param width visible text width
143 * @param fixed if true, the text cannot exceed the display width
144 * @param text initial text, default is empty string
145 * @param enterAction function to call when enter key is pressed
146 * @param updateAction function to call when the text is updated
147 */
148 public TField(final TWidget parent, final int x, final int y,
149 final int width, final boolean fixed, final String text,
150 final TAction enterAction, final TAction updateAction) {
151
152 // Set parent and window
153 super(parent, x, y, width, 1);
154
155 setCursorVisible(true);
156 this.fixed = fixed;
157 this.text = text;
158 this.enterAction = enterAction;
159 this.updateAction = updateAction;
160 }
161
162 /**
163 * Returns true if the mouse is currently on the field.
164 *
165 * @return if true the mouse is currently on the field
166 */
167 protected boolean mouseOnField() {
168 int rightEdge = getWidth() - 1;
169 if ((mouse != null)
170 && (mouse.getY() == 0)
171 && (mouse.getX() >= 0)
172 && (mouse.getX() <= rightEdge)
173 ) {
174 return true;
175 }
176 return false;
177 }
178
179 /**
180 * Dispatch to the action function.
181 *
182 * @param enter if true, the user pressed Enter, else this was an update
183 * to the text.
184 */
185 protected void dispatch(final boolean enter) {
186 if (enter) {
187 if (enterAction != null) {
188 enterAction.DO();
189 }
190 } else {
191 if (updateAction != null) {
192 updateAction.DO();
193 }
194 }
195 }
196
197 /**
198 * Draw the text field.
199 */
200 @Override
201 public void draw() {
202 CellAttributes fieldColor;
203
204 if (isAbsoluteActive()) {
205 fieldColor = getTheme().getColor("tfield.active");
206 } else {
207 fieldColor = getTheme().getColor("tfield.inactive");
208 }
209
210 int end = windowStart + getWidth();
211 if (end > text.length()) {
212 end = text.length();
213 }
214 getScreen().hLineXY(0, 0, getWidth(), GraphicsChars.HATCH, fieldColor);
215 getScreen().putStringXY(0, 0, text.substring(windowStart, end),
216 fieldColor);
217
218 // Fix the cursor, it will be rendered by TApplication.drawAll().
219 updateCursor();
220 }
221
222 /**
223 * Update the cursor position.
224 */
225 protected void updateCursor() {
226 if ((position > getWidth()) && fixed) {
227 setCursorX(getWidth());
228 } else if ((position - windowStart == getWidth()) && !fixed) {
229 setCursorX(getWidth() - 1);
230 } else {
231 setCursorX(position - windowStart);
232 }
233 }
234
235 /**
236 * Handle mouse button presses.
237 *
238 * @param mouse mouse button event
239 */
240 @Override
241 public void onMouseDown(final TMouseEvent mouse) {
242 this.mouse = mouse;
243
244 if ((mouseOnField()) && (mouse.isMouse1())) {
245 // Move cursor
246 int deltaX = mouse.getX() - getCursorX();
247 position += deltaX;
248 if (position > text.length()) {
249 position = text.length();
250 }
251 updateCursor();
252 return;
253 }
254 }
255
256 /**
257 * Handle keystrokes.
258 *
259 * @param keypress keystroke event
260 */
261 @Override
262 public void onKeypress(final TKeypressEvent keypress) {
263
264 if (keypress.equals(kbLeft)) {
265 if (position > 0) {
266 position--;
267 }
268 if (fixed == false) {
269 if ((position == windowStart) && (windowStart > 0)) {
270 windowStart--;
271 }
272 }
273 return;
274 }
275
276 if (keypress.equals(kbRight)) {
277 if (position < text.length()) {
278 position++;
279 if (fixed == true) {
280 if (position == getWidth()) {
281 position--;
282 }
283 } else {
284 if ((position - windowStart) == getWidth()) {
285 windowStart++;
286 }
287 }
288 }
289 return;
290 }
291
292 if (keypress.equals(kbEnter)) {
293 dispatch(true);
294 return;
295 }
296
297 if (keypress.equals(kbIns)) {
298 insertMode = !insertMode;
299 return;
300 }
301 if (keypress.equals(kbHome)) {
302 position = 0;
303 windowStart = 0;
304 return;
305 }
306
307 if (keypress.equals(kbEnd)) {
308 position = text.length();
309 if (fixed == true) {
310 if (position >= getWidth()) {
311 position = text.length() - 1;
312 }
313 } else {
314 windowStart = text.length() - getWidth() + 1;
315 if (windowStart < 0) {
316 windowStart = 0;
317 }
318 }
319 return;
320 }
321
322 if (keypress.equals(kbDel)) {
323 if ((text.length() > 0) && (position < text.length())) {
324 text = text.substring(0, position)
325 + text.substring(position + 1);
326 }
327 return;
328 }
329
330 if (keypress.equals(kbBackspace) || keypress.equals(kbBackspaceDel)) {
331 if (position > 0) {
332 position--;
333 text = text.substring(0, position)
334 + text.substring(position + 1);
335 }
336 if (fixed == false) {
337 if ((position == windowStart)
338 && (windowStart > 0)
339 ) {
340 windowStart--;
341 }
342 }
343 dispatch(false);
344 return;
345 }
346
347 if (!keypress.getKey().isFnKey()
348 && !keypress.getKey().isAlt()
349 && !keypress.getKey().isCtrl()
350 ) {
351 // Plain old keystroke, process it
352 if ((position == text.length())
353 && (text.length() < getWidth())) {
354
355 // Append case
356 appendChar(keypress.getKey().getChar());
357 } else if ((position < text.length())
358 && (text.length() < getWidth())) {
359
360 // Overwrite or insert a character
361 if (insertMode == false) {
362 // Replace character
363 text = text.substring(0, position)
364 + keypress.getKey().getChar()
365 + text.substring(position + 1);
366 position++;
367 } else {
368 // Insert character
369 insertChar(keypress.getKey().getChar());
370 }
371 } else if ((position < text.length())
372 && (text.length() >= getWidth())) {
373
374 // Multiple cases here
375 if ((fixed == true) && (insertMode == true)) {
376 // Buffer is full, do nothing
377 } else if ((fixed == true) && (insertMode == false)) {
378 // Overwrite the last character, maybe move position
379 text = text.substring(0, position)
380 + keypress.getKey().getChar()
381 + text.substring(position + 1);
382 if (position < getWidth() - 1) {
383 position++;
384 }
385 } else if ((fixed == false) && (insertMode == false)) {
386 // Overwrite the last character, definitely move position
387 text = text.substring(0, position)
388 + keypress.getKey().getChar()
389 + text.substring(position + 1);
390 position++;
391 } else {
392 if (position == text.length()) {
393 // Append this character
394 appendChar(keypress.getKey().getChar());
395 } else {
396 // Insert this character
397 insertChar(keypress.getKey().getChar());
398 }
399 }
400 } else {
401 assert (!fixed);
402
403 // Append this character
404 appendChar(keypress.getKey().getChar());
405 }
406 dispatch(false);
407 return;
408 }
409
410 // Pass to parent for the things we don't care about.
411 super.onKeypress(keypress);
412 }
413
414 /**
415 * Append char to the end of the field.
416 *
417 * @param ch = char to append
418 */
419 protected void appendChar(final char ch) {
420 // Append the LAST character
421 text += ch;
422 position++;
423
424 assert (position == text.length());
425
426 if (fixed) {
427 if (position == getWidth()) {
428 position--;
429 }
430 } else {
431 if ((position - windowStart) == getWidth()) {
432 windowStart++;
433 }
434 }
435 }
436
437 /**
438 * Insert char somewhere in the middle of the field.
439 *
440 * @param ch char to append
441 */
442 protected void insertChar(final char ch) {
443 text = text.substring(0, position) + ch + text.substring(position);
444 position++;
445 if ((position - windowStart) == getWidth()) {
446 assert (!fixed);
447 windowStart++;
448 }
449 }
450
451 }