TTerminalWindow working
[fanfix.git] / src / jexer / TField.java
CommitLineData
128e5be1
KL
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 */
31package jexer;
32
33import jexer.bits.CellAttributes;
34import jexer.bits.GraphicsChars;
35import jexer.event.TKeypressEvent;
36import jexer.event.TMouseEvent;
37import static jexer.TKeypress.*;
38
39/**
87a17f3c 40 * TField implements an editable text field.
128e5be1 41 */
87a17f3c 42public class TField extends TWidget {
128e5be1
KL
43
44 /**
45 * Field text.
46 */
87a17f3c 47 protected String text = "";
128e5be1
KL
48
49 /**
50 * Get field text.
51 *
52 * @return field text
53 */
87a17f3c 54 public final String getText() {
128e5be1
KL
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 */
87a17f3c 62 protected boolean fixed = false;
128e5be1
KL
63
64 /**
65 * Current editing position within text.
66 */
87a17f3c 67 protected int position = 0;
128e5be1
KL
68
69 /**
70 * Beginning of visible portion.
71 */
87a17f3c 72 protected int windowStart = 0;
128e5be1
KL
73
74 /**
75 * If true, new characters are inserted at position.
76 */
87a17f3c 77 protected boolean insertMode = true;
128e5be1
KL
78
79 /**
80 * Remember mouse state.
81 */
87a17f3c 82 protected TMouseEvent mouse;
128e5be1
KL
83
84 /**
85 * The action to perform when the user presses enter.
86 */
87a17f3c 87 protected TAction enterAction;
128e5be1
KL
88
89 /**
90 * The action to perform when the text is updated.
91 */
87a17f3c 92 protected TAction updateAction;
128e5be1
KL
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);
143 setX(x);
144 setY(y);
145 setHeight(1);
146 setWidth(width);
147 setHasCursor(true);
148
149 this.fixed = fixed;
150 this.text = text;
151 this.enterAction = enterAction;
152 this.updateAction = updateAction;
153 }
154
155 /**
156 * Returns true if the mouse is currently on the field.
157 *
158 * @return if true the mouse is currently on the field
159 */
87a17f3c 160 protected boolean mouseOnField() {
128e5be1
KL
161 int rightEdge = getWidth() - 1;
162 if ((mouse != null)
163 && (mouse.getY() == 0)
164 && (mouse.getX() >= 0)
165 && (mouse.getX() <= rightEdge)
166 ) {
167 return true;
168 }
169 return false;
170 }
171
172 /**
173 * Dispatch to the action function.
174 *
175 * @param enter if true, the user pressed Enter, else this was an update
176 * to the text.
177 */
87a17f3c 178 protected void dispatch(final boolean enter) {
128e5be1
KL
179 if (enter) {
180 if (enterAction != null) {
181 enterAction.DO();
182 }
183 } else {
184 if (updateAction != null) {
185 updateAction.DO();
186 }
187 }
188 }
189
190 /**
191 * Draw the text field.
192 */
193 @Override
194 public void draw() {
195 CellAttributes fieldColor;
196
197 if (getAbsoluteActive()) {
198 fieldColor = getTheme().getColor("tfield.active");
199 } else {
200 fieldColor = getTheme().getColor("tfield.inactive");
201 }
202
203 int end = windowStart + getWidth();
204 if (end > text.length()) {
205 end = text.length();
206 }
207 getScreen().hLineXY(0, 0, getWidth(), GraphicsChars.HATCH, fieldColor);
208 getScreen().putStrXY(0, 0, text.substring(windowStart, end),
209 fieldColor);
210
211 // Fix the cursor, it will be rendered by TApplication.drawAll().
212 updateCursor();
213 }
214
215 /**
216 * Update the cursor position.
217 */
87a17f3c 218 protected void updateCursor() {
128e5be1
KL
219 if ((position > getWidth()) && fixed) {
220 setCursorX(getWidth());
221 } else if ((position - windowStart == getWidth()) && !fixed) {
222 setCursorX(getWidth() - 1);
223 } else {
224 setCursorX(position - windowStart);
225 }
226 }
227
228 /**
229 * Handle mouse button presses.
230 *
231 * @param mouse mouse button event
232 */
233 @Override
234 public void onMouseDown(final TMouseEvent mouse) {
235 this.mouse = mouse;
236
237 if ((mouseOnField()) && (mouse.getMouse1())) {
238 // Move cursor
239 int deltaX = mouse.getX() - getCursorX();
240 position += deltaX;
241 if (position > text.length()) {
242 position = text.length();
243 }
244 updateCursor();
245 return;
246 }
247 }
248
249 /**
250 * Handle keystrokes.
251 *
252 * @param keypress keystroke event
253 */
254 @Override
255 public void onKeypress(final TKeypressEvent keypress) {
256
257 if (keypress.equals(kbLeft)) {
258 if (position > 0) {
259 position--;
260 }
261 if (fixed == false) {
262 if ((position == windowStart) && (windowStart > 0)) {
263 windowStart--;
264 }
265 }
266 return;
267 }
268
269 if (keypress.equals(kbRight)) {
270 if (position < text.length()) {
271 position++;
272 if (fixed == true) {
273 if (position == getWidth()) {
274 position--;
275 }
276 } else {
277 if ((position - windowStart) == getWidth()) {
278 windowStart++;
279 }
280 }
281 }
282 return;
283 }
284
285 if (keypress.equals(kbEnter)) {
286 dispatch(true);
287 return;
288 }
289
290 if (keypress.equals(kbIns)) {
291 insertMode = !insertMode;
292 return;
293 }
294 if (keypress.equals(kbHome)) {
295 position = 0;
296 windowStart = 0;
297 return;
298 }
299
300 if (keypress.equals(kbEnd)) {
301 position = text.length();
302 if (fixed == true) {
303 if (position >= getWidth()) {
304 position = text.length() - 1;
305 }
306 } else {
307 windowStart = text.length() - getWidth() + 1;
308 if (windowStart < 0) {
309 windowStart = 0;
310 }
311 }
312 return;
313 }
314
315 if (keypress.equals(kbDel)) {
316 if ((text.length() > 0) && (position < text.length())) {
317 text = text.substring(0, position)
318 + text.substring(position + 1);
319 }
320 return;
321 }
322
323 if (keypress.equals(kbBackspace) || keypress.equals(kbBackspaceDel)) {
324 if (position > 0) {
325 position--;
326 text = text.substring(0, position)
327 + text.substring(position + 1);
328 }
329 if (fixed == false) {
330 if ((position == windowStart)
331 && (windowStart > 0)
332 ) {
333 windowStart--;
334 }
335 }
336 dispatch(false);
337 return;
338 }
339
340 if (!keypress.getKey().getIsKey()
341 && !keypress.getKey().getAlt()
342 && !keypress.getKey().getCtrl()
343 ) {
344 // Plain old keystroke, process it
345 if ((position == text.length())
346 && (text.length() < getWidth())) {
347
348 // Append case
349 appendChar(keypress.getKey().getCh());
350 } else if ((position < text.length())
351 && (text.length() < getWidth())) {
352
353 // Overwrite or insert a character
354 if (insertMode == false) {
355 // Replace character
356 text = text.substring(0, position)
357 + keypress.getKey().getCh()
358 + text.substring(position + 1);
359 position++;
360 } else {
361 // Insert character
362 insertChar(keypress.getKey().getCh());
363 }
364 } else if ((position < text.length())
365 && (text.length() >= getWidth())) {
366
367 // Multiple cases here
368 if ((fixed == true) && (insertMode == true)) {
369 // Buffer is full, do nothing
370 } else if ((fixed == true) && (insertMode == false)) {
371 // Overwrite the last character, maybe move position
372 text = text.substring(0, position)
373 + keypress.getKey().getCh()
374 + text.substring(position + 1);
375 if (position < getWidth() - 1) {
376 position++;
377 }
378 } else if ((fixed == false) && (insertMode == false)) {
379 // Overwrite the last character, definitely move position
380 text = text.substring(0, position)
381 + keypress.getKey().getCh()
382 + text.substring(position + 1);
383 position++;
384 } else {
385 if (position == text.length()) {
386 // Append this character
387 appendChar(keypress.getKey().getCh());
388 } else {
389 // Insert this character
390 insertChar(keypress.getKey().getCh());
391 }
392 }
393 } else {
394 assert (!fixed);
395
396 // Append this character
397 appendChar(keypress.getKey().getCh());
398 }
399 dispatch(false);
400 return;
401 }
402
403 // Pass to parent for the things we don't care about.
404 super.onKeypress(keypress);
405 }
406
407 /**
408 * Append char to the end of the field.
409 *
410 * @param ch = char to append
411 */
87a17f3c 412 protected void appendChar(final char ch) {
128e5be1
KL
413 // Append the LAST character
414 text += ch;
415 position++;
416
417 assert (position == text.length());
418
419 if (fixed) {
420 if (position == getWidth()) {
421 position--;
422 }
423 } else {
424 if ((position - windowStart) == getWidth()) {
425 windowStart++;
426 }
427 }
428 }
429
430 /**
431 * Insert char somewhere in the middle of the field.
432 *
433 * @param ch char to append
434 */
87a17f3c 435 protected void insertChar(final char ch) {
128e5be1
KL
436 text = text.substring(0, position) + ch + text.substring(position);
437 position++;
438 if ((position - windowStart) == getWidth()) {
439 assert (!fixed);
440 windowStart++;
441 }
442 }
443
444}