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