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