custom command line terminal window, blinking in color theme, scrolling region resize
[fanfix.git] / src / jexer / TWidget.java
CommitLineData
daa4106c 1/*
48e27807
KL
2 * Jexer - Java Text User Interface
3 *
e16dda65 4 * The MIT License (MIT)
48e27807 5 *
a2018e99 6 * Copyright (C) 2017 Kevin Lamonte
48e27807 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:
48e27807 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.
48e27807 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.
48e27807
KL
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
29package jexer;
30
0d47c546 31import java.io.IOException;
48e27807 32import java.util.List;
2ce6dab2 33import java.util.ArrayList;
48e27807 34
42873e30 35import jexer.backend.Screen;
928811d8 36import jexer.bits.ColorTheme;
48e27807
KL
37import jexer.event.TCommandEvent;
38import jexer.event.TInputEvent;
39import jexer.event.TKeypressEvent;
40import jexer.event.TMenuEvent;
41import jexer.event.TMouseEvent;
42import jexer.event.TResizeEvent;
928811d8 43import jexer.menu.TMenu;
48e27807
KL
44import static jexer.TKeypress.*;
45
46/**
47 * TWidget is the base class of all objects that can be drawn on screen or
48 * handle user input events.
49 */
2b9c27db 50public abstract class TWidget implements Comparable<TWidget> {
48e27807 51
2ce6dab2
KL
52 // ------------------------------------------------------------------------
53 // Common widget attributes -----------------------------------------------
54 // ------------------------------------------------------------------------
55
48e27807
KL
56 /**
57 * Every widget has a parent widget that it may be "contained" in. For
58 * example, a TWindow might contain several TTextFields, or a TComboBox
59 * may contain a TScrollBar.
60 */
fca67db0
KL
61 private TWidget parent = null;
62
928811d8
KL
63 /**
64 * Get parent widget.
65 *
66 * @return parent widget
67 */
68 public final TWidget getParent() {
69 return parent;
70 }
71
48e27807
KL
72 /**
73 * Child widgets that this widget contains.
74 */
75 private List<TWidget> children;
76
928811d8
KL
77 /**
78 * Get the list of child widgets that this widget contains.
79 *
80 * @return the list of child widgets
81 */
82 public List<TWidget> getChildren() {
83 return children;
84 }
85
48e27807
KL
86 /**
87 * The currently active child widget that will receive keypress events.
88 */
89 private TWidget activeChild = null;
90
91 /**
92 * If true, this widget will receive events.
93 */
fca67db0
KL
94 private boolean active = false;
95
96 /**
97 * Get active flag.
98 *
99 * @return if true, this widget will receive events
100 */
7c870d89 101 public final boolean isActive() {
fca67db0
KL
102 return active;
103 }
104
105 /**
106 * Set active flag.
107 *
108 * @param active if true, this widget will receive events
109 */
928811d8 110 public final void setActive(final boolean active) {
fca67db0
KL
111 this.active = active;
112 }
48e27807
KL
113
114 /**
115 * The window that this widget draws to.
116 */
fca67db0 117 private TWindow window = null;
48e27807 118
30d336cc
KL
119 /**
120 * Get the window this widget is on.
121 *
122 * @return the window
123 */
124 public final TWindow getWindow() {
125 return window;
126 }
127
48e27807
KL
128 /**
129 * Absolute X position of the top-left corner.
130 */
fca67db0
KL
131 private int x = 0;
132
133 /**
134 * Get X position.
135 *
136 * @return absolute X position of the top-left corner
137 */
138 public final int getX() {
139 return x;
140 }
141
142 /**
143 * Set X position.
144 *
145 * @param x absolute X position of the top-left corner
146 */
147 public final void setX(final int x) {
148 this.x = x;
149 }
48e27807
KL
150
151 /**
152 * Absolute Y position of the top-left corner.
153 */
fca67db0
KL
154 private int y = 0;
155
156 /**
157 * Get Y position.
158 *
159 * @return absolute Y position of the top-left corner
160 */
161 public final int getY() {
162 return y;
163 }
164
165 /**
166 * Set Y position.
167 *
168 * @param y absolute Y position of the top-left corner
169 */
170 public final void setY(final int y) {
171 this.y = y;
172 }
48e27807
KL
173
174 /**
175 * Width.
176 */
fca67db0
KL
177 private int width = 0;
178
179 /**
180 * Get the width.
181 *
182 * @return widget width
183 */
184 public final int getWidth() {
185 return this.width;
186 }
187
188 /**
189 * Change the width.
190 *
191 * @param width new widget width
192 */
193 public final void setWidth(final int width) {
194 this.width = width;
195 }
48e27807
KL
196
197 /**
198 * Height.
199 */
fca67db0
KL
200 private int height = 0;
201
202 /**
203 * Get the height.
204 *
205 * @return widget height
206 */
207 public final int getHeight() {
208 return this.height;
209 }
210
211 /**
212 * Change the height.
213 *
214 * @param height new widget height
215 */
216 public final void setHeight(final int height) {
217 this.height = height;
218 }
48e27807 219
0ee88b6d
KL
220 /**
221 * Change the dimensions.
222 *
223 * @param x absolute X position of the top-left corner
224 * @param y absolute Y position of the top-left corner
225 * @param width new widget width
226 * @param height new widget height
227 */
228 public final void setDimensions(final int x, final int y, final int width,
229 final int height) {
230
231 setX(x);
232 setY(y);
233 setWidth(width);
234 setHeight(height);
235 }
236
48e27807
KL
237 /**
238 * My tab order inside a window or containing widget.
239 */
240 private int tabOrder = 0;
241
242 /**
243 * If true, this widget can be tabbed to or receive events.
244 */
245 private boolean enabled = true;
246
247 /**
248 * Get enabled flag.
249 *
250 * @return if true, this widget can be tabbed to or receive events
251 */
7c870d89 252 public final boolean isEnabled() {
48e27807
KL
253 return enabled;
254 }
255
256 /**
257 * Set enabled flag.
258 *
259 * @param enabled if true, this widget can be tabbed to or receive events
260 */
261 public final void setEnabled(final boolean enabled) {
262 this.enabled = enabled;
fca67db0 263 if (!enabled) {
48e27807
KL
264 active = false;
265 // See if there are any active siblings to switch to
266 boolean foundSibling = false;
fca67db0
KL
267 if (parent != null) {
268 for (TWidget w: parent.children) {
269 if ((w.enabled)
270 && !(this instanceof THScroller)
271 && !(this instanceof TVScroller)
48e27807
KL
272 ) {
273 parent.activate(w);
274 foundSibling = true;
275 break;
276 }
277 }
278 if (!foundSibling) {
279 parent.activeChild = null;
280 }
281 }
282 }
48e27807
KL
283 }
284
285 /**
286 * If true, this widget has a cursor.
287 */
7c870d89 288 private boolean cursorVisible = false;
48e27807 289
7272e49f
KL
290 /**
291 * Set visible cursor flag.
292 *
7c870d89 293 * @param cursorVisible if true, this widget has a cursor
7272e49f 294 */
7c870d89
KL
295 public final void setCursorVisible(final boolean cursorVisible) {
296 this.cursorVisible = cursorVisible;
7272e49f
KL
297 }
298
a06459bd
KL
299 /**
300 * See if this widget has a visible cursor.
301 *
302 * @return if true, this widget has a visible cursor
303 */
7c870d89 304 public final boolean isCursorVisible() {
24489803
KL
305 // If cursor is out of my bounds, it is not visible.
306 if ((cursorX >= width)
307 || (cursorX < 0)
308 || (cursorY >= height)
309 || (cursorY < 0)
310 ) {
311 return false;
312 }
313
314 // If cursor is out of my window's bounds, it is not visible.
315 if ((getCursorAbsoluteX() >= window.getAbsoluteX()
316 + window.getWidth() - 1)
317 || (getCursorAbsoluteX() < 0)
318 || (getCursorAbsoluteY() >= window.getAbsoluteY()
319 + window.getHeight() - 1)
320 || (getCursorAbsoluteY() < 0)
321 ) {
322 return false;
323 }
7c870d89 324 return cursorVisible;
a06459bd
KL
325 }
326
48e27807
KL
327 /**
328 * Cursor column position in relative coordinates.
329 */
330 private int cursorX = 0;
331
7272e49f
KL
332 /**
333 * Get cursor X value.
334 *
335 * @return cursor column position in relative coordinates
336 */
337 public final int getCursorX() {
338 return cursorX;
339 }
340
341 /**
342 * Set cursor X value.
343 *
344 * @param cursorX column position in relative coordinates
345 */
346 public final void setCursorX(final int cursorX) {
347 this.cursorX = cursorX;
348 }
349
48e27807
KL
350 /**
351 * Cursor row position in relative coordinates.
352 */
353 private int cursorY = 0;
354
7272e49f
KL
355 /**
356 * Get cursor Y value.
357 *
358 * @return cursor row position in relative coordinates
359 */
360 public final int getCursorY() {
361 return cursorY;
362 }
363
364 /**
365 * Set cursor Y value.
366 *
367 * @param cursorY row position in relative coordinates
368 */
369 public final void setCursorY(final int cursorY) {
370 this.cursorY = cursorY;
371 }
372
2ce6dab2
KL
373 // ------------------------------------------------------------------------
374 // TApplication integration -----------------------------------------------
375 // ------------------------------------------------------------------------
376
377 /**
378 * Get this TWidget's parent TApplication.
379 *
380 * @return the parent TApplication
381 */
382 public TApplication getApplication() {
383 return window.getApplication();
384 }
385
386 /**
387 * Get the Screen.
388 *
389 * @return the Screen
390 */
391 public Screen getScreen() {
392 return window.getScreen();
393 }
394
48e27807 395 /**
329fd62e 396 * Comparison operator. For various subclasses it sorts on:
7668cb45
KL
397 * <ul>
398 * <li>tabOrder for TWidgets</li>
399 * <li>z for TWindows</li>
400 * <li>text for TTreeItems</li>
401 * </ul>
402 *
403 * @param that another TWidget, TWindow, or TTreeItem instance
2b9c27db 404 * @return difference between this.tabOrder and that.tabOrder, or
7668cb45 405 * difference between this.z and that.z, or String.compareTo(text)
2b9c27db 406 */
2b9c27db
KL
407 public final int compareTo(final TWidget that) {
408 if ((this instanceof TWindow)
7272e49f 409 && (that instanceof TWindow)
2b9c27db
KL
410 ) {
411 return (((TWindow) this).getZ() - ((TWindow) that).getZ());
412 }
7668cb45
KL
413 if ((this instanceof TTreeItem)
414 && (that instanceof TTreeItem)
415 ) {
416 return (((TTreeItem) this).getText().compareTo(
417 ((TTreeItem) that).getText()));
418 }
48e27807
KL
419 return (this.tabOrder - that.tabOrder);
420 }
421
422 /**
423 * See if this widget should render with the active color.
424 *
425 * @return true if this widget is active and all of its parents are
426 * active.
427 */
7c870d89 428 public final boolean isAbsoluteActive() {
48e27807
KL
429 if (parent == this) {
430 return active;
431 }
7c870d89 432 return (active && parent.isAbsoluteActive());
48e27807
KL
433 }
434
435 /**
436 * Returns the cursor X position.
437 *
438 * @return absolute screen column number for the cursor's X position
439 */
440 public final int getCursorAbsoluteX() {
7c870d89 441 assert (cursorVisible);
48e27807
KL
442 return getAbsoluteX() + cursorX;
443 }
444
445 /**
446 * Returns the cursor Y position.
447 *
448 * @return absolute screen row number for the cursor's Y position
449 */
450 public final int getCursorAbsoluteY() {
7c870d89 451 assert (cursorVisible);
48e27807
KL
452 return getAbsoluteY() + cursorY;
453 }
454
455 /**
456 * Compute my absolute X position as the sum of my X plus all my parent's
457 * X's.
458 *
459 * @return absolute screen column number for my X position
460 */
461 public final int getAbsoluteX() {
462 assert (parent != null);
463 if (parent == this) {
464 return x;
465 }
0ee88b6d
KL
466 if ((parent instanceof TWindow)
467 && !(parent instanceof TMenu)
468 && !(parent instanceof TDesktop)
469 ) {
48e27807
KL
470 // Widgets on a TWindow have (0,0) as their top-left, but this is
471 // actually the TWindow's (1,1).
472 return parent.getAbsoluteX() + x + 1;
473 }
474 return parent.getAbsoluteX() + x;
475 }
476
477 /**
478 * Compute my absolute Y position as the sum of my Y plus all my parent's
479 * Y's.
480 *
481 * @return absolute screen row number for my Y position
482 */
483 public final int getAbsoluteY() {
484 assert (parent != null);
485 if (parent == this) {
486 return y;
487 }
0ee88b6d
KL
488 if ((parent instanceof TWindow)
489 && !(parent instanceof TMenu)
490 && !(parent instanceof TDesktop)
491 ) {
48e27807
KL
492 // Widgets on a TWindow have (0,0) as their top-left, but this is
493 // actually the TWindow's (1,1).
494 return parent.getAbsoluteY() + y + 1;
495 }
496 return parent.getAbsoluteY() + y;
497 }
498
928811d8
KL
499 /**
500 * Get the global color theme.
501 *
502 * @return the ColorTheme
503 */
504 public final ColorTheme getTheme() {
505 return window.getApplication().getTheme();
506 }
507
48e27807
KL
508 /**
509 * Draw my specific widget. When called, the screen rectangle I draw
510 * into is already setup (offset and clipping).
511 */
512 public void draw() {
513 // Default widget draws nothing.
514 }
515
516 /**
517 * Called by parent to render to TWindow.
518 */
519 public final void drawChildren() {
520 // Set my clipping rectangle
521 assert (window != null);
92554d64
KL
522 assert (getScreen() != null);
523 Screen screen = getScreen();
48e27807 524
2ce6dab2
KL
525 // Special case: TStatusBar is drawn by TApplication, not anything
526 // else.
527 if (this instanceof TStatusBar) {
528 return;
529 }
530
48e27807
KL
531 screen.setClipRight(width);
532 screen.setClipBottom(height);
533
92554d64
KL
534 int absoluteRightEdge = window.getAbsoluteX() + window.getWidth();
535 int absoluteBottomEdge = window.getAbsoluteY() + window.getHeight();
48e27807
KL
536 if (!(this instanceof TWindow) && !(this instanceof TVScroller)) {
537 absoluteRightEdge -= 1;
538 }
539 if (!(this instanceof TWindow) && !(this instanceof THScroller)) {
540 absoluteBottomEdge -= 1;
541 }
542 int myRightEdge = getAbsoluteX() + width;
543 int myBottomEdge = getAbsoluteY() + height;
544 if (getAbsoluteX() > absoluteRightEdge) {
545 // I am offscreen
546 screen.setClipRight(0);
547 } else if (myRightEdge > absoluteRightEdge) {
548 screen.setClipRight(screen.getClipRight()
92554d64 549 - (myRightEdge - absoluteRightEdge));
48e27807
KL
550 }
551 if (getAbsoluteY() > absoluteBottomEdge) {
552 // I am offscreen
553 screen.setClipBottom(0);
554 } else if (myBottomEdge > absoluteBottomEdge) {
555 screen.setClipBottom(screen.getClipBottom()
92554d64 556 - (myBottomEdge - absoluteBottomEdge));
48e27807
KL
557 }
558
559 // Set my offset
560 screen.setOffsetX(getAbsoluteX());
561 screen.setOffsetY(getAbsoluteY());
562
563 // Draw me
564 draw();
565
566 // Continue down the chain
567 for (TWidget widget: children) {
568 widget.drawChildren();
569 }
570 }
571
be72cb5c
KL
572 /**
573 * Repaint the screen on the next update.
574 */
575 public void doRepaint() {
576 window.getApplication().doRepaint();
577 }
578
2ce6dab2
KL
579 // ------------------------------------------------------------------------
580 // Constructors -----------------------------------------------------------
581 // ------------------------------------------------------------------------
582
48e27807 583 /**
fca67db0 584 * Default constructor for subclasses.
48e27807
KL
585 */
586 protected TWidget() {
2ce6dab2 587 children = new ArrayList<TWidget>();
48e27807
KL
588 }
589
590 /**
591 * Protected constructor.
592 *
593 * @param parent parent widget
594 */
595 protected TWidget(final TWidget parent) {
30d336cc
KL
596 this(parent, true);
597 }
598
a83fea2b
KL
599 /**
600 * Protected constructor.
601 *
602 * @param parent parent widget
603 * @param x column relative to parent
604 * @param y row relative to parent
605 * @param width width of widget
606 * @param height height of widget
607 */
608 protected TWidget(final TWidget parent, final int x, final int y,
609 final int width, final int height) {
610
611 this(parent, true, x, y, width, height);
612 }
613
30d336cc
KL
614 /**
615 * Protected constructor used by subclasses that are disabled by default.
616 *
617 * @param parent parent widget
618 * @param enabled if true assume enabled
619 */
620 protected TWidget(final TWidget parent, final boolean enabled) {
621 this.enabled = enabled;
48e27807
KL
622 this.parent = parent;
623 this.window = parent.window;
2ce6dab2 624 children = new ArrayList<TWidget>();
1c19fdea
KL
625
626 // Do not add TStatusBars, they are drawn by TApplication
627 if (this instanceof TStatusBar) {
628 } else {
629 parent.addChild(this);
630 }
48e27807
KL
631 }
632
a83fea2b
KL
633 /**
634 * Protected constructor used by subclasses that are disabled by default.
635 *
636 * @param parent parent widget
637 * @param enabled if true assume enabled
638 * @param x column relative to parent
639 * @param y row relative to parent
640 * @param width width of widget
641 * @param height height of widget
642 */
643 protected TWidget(final TWidget parent, final boolean enabled,
644 final int x, final int y, final int width, final int height) {
645
646 this.enabled = enabled;
647 this.parent = parent;
648 this.window = parent.window;
2ce6dab2 649 children = new ArrayList<TWidget>();
1c19fdea
KL
650
651 // Do not add TStatusBars, they are drawn by TApplication
652 if (this instanceof TStatusBar) {
653 } else {
654 parent.addChild(this);
655 }
a83fea2b
KL
656
657 this.x = x;
658 this.y = y;
659 this.width = width;
660 this.height = height;
661 }
662
2ce6dab2
KL
663 /**
664 * Backdoor access for TWindow's constructor. ONLY TWindow USES THIS.
665 *
666 * @param window the top-level window
667 * @param x column relative to parent
668 * @param y row relative to parent
669 * @param width width of window
670 * @param height height of window
671 */
672 protected final void setupForTWindow(final TWindow window,
673 final int x, final int y, final int width, final int height) {
674
675 this.parent = window;
676 this.window = window;
677 this.x = x;
678 this.y = y;
679 this.width = width;
680 this.height = height;
681 }
682
683 // ------------------------------------------------------------------------
684 // General behavior -------------------------------------------------------
685 // ------------------------------------------------------------------------
686
48e27807
KL
687 /**
688 * Add a child widget to my list of children. We set its tabOrder to 0
689 * and increment the tabOrder of all other children.
690 *
691 * @param child TWidget to add
692 */
693 private void addChild(final TWidget child) {
694 children.add(child);
695
696 if ((child.enabled)
697 && !(child instanceof THScroller)
698 && !(child instanceof TVScroller)
699 ) {
700 for (TWidget widget: children) {
701 widget.active = false;
702 }
703 child.active = true;
704 activeChild = child;
705 }
706 for (int i = 0; i < children.size(); i++) {
707 children.get(i).tabOrder = i;
708 }
709 }
710
711 /**
712 * Switch the active child.
713 *
714 * @param child TWidget to activate
715 */
716 public final void activate(final TWidget child) {
717 assert (child.enabled);
718 if ((child instanceof THScroller)
719 || (child instanceof TVScroller)
720 ) {
721 return;
722 }
723
724 if (child != activeChild) {
725 if (activeChild != null) {
726 activeChild.active = false;
727 }
728 child.active = true;
729 activeChild = child;
730 }
731 }
732
733 /**
734 * Switch the active child.
735 *
736 * @param tabOrder tabOrder of the child to activate. If that child
737 * isn't enabled, then the next enabled child will be activated.
738 */
739 public final void activate(final int tabOrder) {
740 if (activeChild == null) {
741 return;
742 }
743 TWidget child = null;
744 for (TWidget widget: children) {
745 if ((widget.enabled)
746 && !(widget instanceof THScroller)
747 && !(widget instanceof TVScroller)
748 && (widget.tabOrder >= tabOrder)
749 ) {
750 child = widget;
751 break;
752 }
753 }
754 if ((child != null) && (child != activeChild)) {
755 activeChild.active = false;
756 assert (child.enabled);
757 child.active = true;
758 activeChild = child;
759 }
760 }
761
762 /**
763 * Switch the active widget with the next in the tab order.
764 *
765 * @param forward if true, then switch to the next enabled widget in the
766 * list, otherwise switch to the previous enabled widget in the list
767 */
768 public final void switchWidget(final boolean forward) {
769
770 // Only switch if there are multiple enabled widgets
771 if ((children.size() < 2) || (activeChild == null)) {
772 return;
773 }
774
775 int tabOrder = activeChild.tabOrder;
776 do {
777 if (forward) {
778 tabOrder++;
779 } else {
780 tabOrder--;
781 }
782 if (tabOrder < 0) {
783
784 // If at the end, pass the switch to my parent.
785 if ((!forward) && (parent != this)) {
786 parent.switchWidget(forward);
787 return;
788 }
789
790 tabOrder = children.size() - 1;
791 } else if (tabOrder == children.size()) {
792 // If at the end, pass the switch to my parent.
793 if ((forward) && (parent != this)) {
794 parent.switchWidget(forward);
795 return;
796 }
797
798 tabOrder = 0;
799 }
800 if (activeChild.tabOrder == tabOrder) {
801 // We wrapped around
802 break;
803 }
804 } while ((!children.get(tabOrder).enabled)
805 && !(children.get(tabOrder) instanceof THScroller)
806 && !(children.get(tabOrder) instanceof TVScroller));
807
808 assert (children.get(tabOrder).enabled);
809
810 activeChild.active = false;
811 children.get(tabOrder).active = true;
812 activeChild = children.get(tabOrder);
48e27807
KL
813 }
814
815 /**
816 * Returns my active widget.
817 *
818 * @return widget that is active, or this if no children
819 */
928811d8 820 public TWidget getActiveChild() {
48e27807
KL
821 if ((this instanceof THScroller)
822 || (this instanceof TVScroller)
823 ) {
824 return parent;
825 }
826
827 for (TWidget widget: children) {
828 if (widget.active) {
829 return widget.getActiveChild();
830 }
831 }
832 // No active children, return me
833 return this;
834 }
835
2ce6dab2
KL
836 // ------------------------------------------------------------------------
837 // Event handlers ---------------------------------------------------------
838 // ------------------------------------------------------------------------
839
840 /**
841 * Check if a mouse press/release event coordinate is contained in this
842 * widget.
843 *
844 * @param mouse a mouse-based event
845 * @return whether or not a mouse click would be sent to this widget
846 */
847 public final boolean mouseWouldHit(final TMouseEvent mouse) {
848
849 if (!enabled) {
850 return false;
851 }
852
853 if ((mouse.getAbsoluteX() >= getAbsoluteX())
854 && (mouse.getAbsoluteX() < getAbsoluteX() + width)
855 && (mouse.getAbsoluteY() >= getAbsoluteY())
856 && (mouse.getAbsoluteY() < getAbsoluteY() + height)
857 ) {
858 return true;
859 }
860 return false;
861 }
862
48e27807
KL
863 /**
864 * Method that subclasses can override to handle keystrokes.
865 *
866 * @param keypress keystroke event
867 */
868 public void onKeypress(final TKeypressEvent keypress) {
869
870 if ((children.size() == 0)
a043164f 871 || (this instanceof TTreeView)
a83fea2b 872 || (this instanceof TText)
48e27807
KL
873 ) {
874
875 // Defaults:
876 // tab / shift-tab - switch to next/previous widget
877 // right-arrow or down-arrow: same as tab
878 // left-arrow or up-arrow: same as shift-tab
879 if ((keypress.equals(kbTab))
880 || (keypress.equals(kbRight))
881 || (keypress.equals(kbDown))
882 ) {
883 parent.switchWidget(true);
884 return;
885 } else if ((keypress.equals(kbShiftTab))
886 || (keypress.equals(kbBackTab))
887 || (keypress.equals(kbLeft))
888 || (keypress.equals(kbUp))
889 ) {
890 parent.switchWidget(false);
891 return;
892 }
893 }
894
895 // If I have any buttons on me AND this is an Alt-key that matches
896 // its mnemonic, send it an Enter keystroke
897 for (TWidget widget: children) {
92554d64
KL
898 if (widget instanceof TButton) {
899 TButton button = (TButton) widget;
7c870d89
KL
900 if (button.isEnabled()
901 && !keypress.getKey().isFnKey()
902 && keypress.getKey().isAlt()
903 && !keypress.getKey().isCtrl()
92554d64 904 && (Character.toLowerCase(button.getMnemonic().getShortcut())
7c870d89 905 == Character.toLowerCase(keypress.getKey().getChar()))
92554d64
KL
906 ) {
907
908 widget.handleEvent(new TKeypressEvent(kbEnter));
48e27807
KL
909 return;
910 }
911 }
48e27807
KL
912 }
913
914 // Dispatch the keypress to an active widget
915 for (TWidget widget: children) {
916 if (widget.active) {
48e27807
KL
917 widget.handleEvent(keypress);
918 return;
919 }
920 }
921 }
922
923 /**
924 * Method that subclasses can override to handle mouse button presses.
925 *
926 * @param mouse mouse button event
927 */
928 public void onMouseDown(final TMouseEvent mouse) {
929 // Default: do nothing, pass to children instead
49befbbc
KL
930 for (int i = children.size() - 1 ; i >= 0 ; i--) {
931 TWidget widget = children.get(i);
48e27807
KL
932 if (widget.mouseWouldHit(mouse)) {
933 // Dispatch to this child, also activate it
934 activate(widget);
935
936 // Set x and y relative to the child's coordinates
937 mouse.setX(mouse.getAbsoluteX() - widget.getAbsoluteX());
938 mouse.setY(mouse.getAbsoluteY() - widget.getAbsoluteY());
939 widget.handleEvent(mouse);
940 return;
941 }
942 }
943 }
944
945 /**
946 * Method that subclasses can override to handle mouse button releases.
947 *
948 * @param mouse mouse button event
949 */
950 public void onMouseUp(final TMouseEvent mouse) {
951 // Default: do nothing, pass to children instead
49befbbc
KL
952 for (int i = children.size() - 1 ; i >= 0 ; i--) {
953 TWidget widget = children.get(i);
48e27807
KL
954 if (widget.mouseWouldHit(mouse)) {
955 // Dispatch to this child, also activate it
956 activate(widget);
957
958 // Set x and y relative to the child's coordinates
959 mouse.setX(mouse.getAbsoluteX() - widget.getAbsoluteX());
960 mouse.setY(mouse.getAbsoluteY() - widget.getAbsoluteY());
961 widget.handleEvent(mouse);
962 return;
963 }
964 }
965 }
966
967 /**
968 * Method that subclasses can override to handle mouse movements.
969 *
970 * @param mouse mouse motion event
971 */
972 public void onMouseMotion(final TMouseEvent mouse) {
973 // Default: do nothing, pass it on to ALL of my children. This way
974 // the children can see the mouse "leaving" their area.
975 for (TWidget widget: children) {
976 // Set x and y relative to the child's coordinates
977 mouse.setX(mouse.getAbsoluteX() - widget.getAbsoluteX());
978 mouse.setY(mouse.getAbsoluteY() - widget.getAbsoluteY());
979 widget.handleEvent(mouse);
980 }
981 }
982
983 /**
984 * Method that subclasses can override to handle window/screen resize
985 * events.
986 *
987 * @param resize resize event
988 */
989 public void onResize(final TResizeEvent resize) {
12b55d76
KL
990 // Default: change my width/height.
991 if (resize.getType() == TResizeEvent.Type.WIDGET) {
992 width = resize.getWidth();
993 height = resize.getHeight();
994 } else {
995 // Let children see the screen resize
996 for (TWidget widget: children) {
997 widget.onResize(resize);
998 }
48e27807
KL
999 }
1000 }
1001
1002 /**
1003 * Method that subclasses can override to handle posted command events.
1004 *
1005 * @param command command event
1006 */
1007 public void onCommand(final TCommandEvent command) {
1008 // Default: do nothing, pass to children instead
1009 for (TWidget widget: children) {
1010 widget.onCommand(command);
1011 }
1012 }
1013
1014 /**
1015 * Method that subclasses can override to handle menu or posted menu
1016 * events.
1017 *
1018 * @param menu menu event
1019 */
1020 public void onMenu(final TMenuEvent menu) {
1021 // Default: do nothing, pass to children instead
1022 for (TWidget widget: children) {
1023 widget.onMenu(menu);
1024 }
1025 }
1026
1027 /**
1028 * Method that subclasses can override to do processing when the UI is
be72cb5c
KL
1029 * idle. Note that repainting is NOT assumed. To get a refresh after
1030 * onIdle, call doRepaint().
48e27807
KL
1031 */
1032 public void onIdle() {
1033 // Default: do nothing, pass to children instead
1034 for (TWidget widget: children) {
1035 widget.onIdle();
1036 }
1037 }
1038
1039 /**
1040 * Consume event. Subclasses that want to intercept all events in one go
1041 * can override this method.
1042 *
1043 * @param event keyboard, mouse, resize, command, or menu event
1044 */
1045 public void handleEvent(final TInputEvent event) {
1046 // System.err.printf("TWidget (%s) event: %s\n", this.getClass().getName(),
1047 // event);
1048
1049 if (!enabled) {
1050 // Discard event
1051 // System.err.println(" -- discard --");
1052 return;
1053 }
1054
1055 if (event instanceof TKeypressEvent) {
1056 onKeypress((TKeypressEvent) event);
1057 } else if (event instanceof TMouseEvent) {
1058
1059 TMouseEvent mouse = (TMouseEvent) event;
1060
1061 switch (mouse.getType()) {
1062
1063 case MOUSE_DOWN:
1064 onMouseDown(mouse);
1065 break;
1066
1067 case MOUSE_UP:
1068 onMouseUp(mouse);
1069 break;
1070
1071 case MOUSE_MOTION:
1072 onMouseMotion(mouse);
1073 break;
1074
1075 default:
1076 throw new IllegalArgumentException("Invalid mouse event type: "
1077 + mouse.getType());
1078 }
1079 } else if (event instanceof TResizeEvent) {
1080 onResize((TResizeEvent) event);
1081 } else if (event instanceof TCommandEvent) {
1082 onCommand((TCommandEvent) event);
1083 } else if (event instanceof TMenuEvent) {
1084 onMenu((TMenuEvent) event);
1085 }
1086
1087 // Do nothing else
1088 return;
1089 }
1090
2ce6dab2
KL
1091 // ------------------------------------------------------------------------
1092 // Other TWidget constructors ---------------------------------------------
1093 // ------------------------------------------------------------------------
48e27807 1094
30d336cc
KL
1095 /**
1096 * Convenience function to add a label to this container/window.
1097 *
1098 * @param text label
1099 * @param x column relative to parent
1100 * @param y row relative to parent
1101 * @return the new label
1102 */
1103 public final TLabel addLabel(final String text, final int x, final int y) {
1104 return addLabel(text, x, y, "tlabel");
1105 }
1106
1107 /**
1108 * Convenience function to add a label to this container/window.
1109 *
1110 * @param text label
1111 * @param x column relative to parent
1112 * @param y row relative to parent
1113 * @param colorKey ColorTheme key color to use for foreground text.
1114 * Default is "tlabel"
1115 * @return the new label
1116 */
1117 public final TLabel addLabel(final String text, final int x, final int y,
1118 final String colorKey) {
1119
1120 return new TLabel(this, text, x, y, colorKey);
1121 }
1122
1123 /**
1124 * Convenience function to add a button to this container/window.
1125 *
1126 * @param text label on the button
1127 * @param x column relative to parent
1128 * @param y row relative to parent
1129 * @param action to call when button is pressed
1130 * @return the new button
1131 */
1132 public final TButton addButton(final String text, final int x, final int y,
1133 final TAction action) {
1134
1135 return new TButton(this, text, x, y, action);
1136 }
1137
7272e49f
KL
1138 /**
1139 * Convenience function to add a checkbox to this container/window.
1140 *
1141 * @param x column relative to parent
1142 * @param y row relative to parent
1143 * @param label label to display next to (right of) the checkbox
1144 * @param checked initial check state
1145 * @return the new checkbox
1146 */
1147 public final TCheckbox addCheckbox(final int x, final int y,
1148 final String label, final boolean checked) {
1149
1150 return new TCheckbox(this, x, y, label, checked);
1151 }
30d336cc 1152
d502a0e9
KL
1153 /**
1154 * Convenience function to add a progress bar to this container/window.
1155 *
1156 * @param x column relative to parent
1157 * @param y row relative to parent
1158 * @param width width of progress bar
1159 * @param value initial value of percent complete
00d2622b 1160 * @return the new progress bar
d502a0e9
KL
1161 */
1162 public final TProgressBar addProgressBar(final int x, final int y,
1163 final int width, final int value) {
1164
1165 return new TProgressBar(this, x, y, width, value);
1166 }
1167
00d2622b
KL
1168 /**
1169 * Convenience function to add a radio button group to this
1170 * container/window.
1171 *
1172 * @param x column relative to parent
1173 * @param y row relative to parent
1174 * @param label label to display on the group box
1175 * @return the new radio button group
1176 */
1177 public final TRadioGroup addRadioGroup(final int x, final int y,
1178 final String label) {
1179
1180 return new TRadioGroup(this, x, y, label);
1181 }
1182
128e5be1
KL
1183 /**
1184 * Convenience function to add a text field to this container/window.
1185 *
1186 * @param x column relative to parent
1187 * @param y row relative to parent
1188 * @param width visible text width
1189 * @param fixed if true, the text cannot exceed the display width
1190 * @return the new text field
1191 */
1192 public final TField addField(final int x, final int y,
1193 final int width, final boolean fixed) {
1194
1195 return new TField(this, x, y, width, fixed);
1196 }
1197
1198 /**
1199 * Convenience function to add a text field to this container/window.
1200 *
1201 * @param x column relative to parent
1202 * @param y row relative to parent
1203 * @param width visible text width
1204 * @param fixed if true, the text cannot exceed the display width
1205 * @param text initial text, default is empty string
1206 * @return the new text field
1207 */
1208 public final TField addField(final int x, final int y,
1209 final int width, final boolean fixed, final String text) {
1210
1211 return new TField(this, x, y, width, fixed, text);
1212 }
1213
1214 /**
1215 * Convenience function to add a text field to this container/window.
1216 *
1217 * @param x column relative to parent
1218 * @param y row relative to parent
1219 * @param width visible text width
1220 * @param fixed if true, the text cannot exceed the display width
1221 * @param text initial text, default is empty string
1222 * @param enterAction function to call when enter key is pressed
1223 * @param updateAction function to call when the text is updated
1224 * @return the new text field
1225 */
1226 public final TField addField(final int x, final int y,
1227 final int width, final boolean fixed, final String text,
1228 final TAction enterAction, final TAction updateAction) {
1229
1230 return new TField(this, x, y, width, fixed, text, enterAction,
1231 updateAction);
1232 }
00d2622b 1233
cc99cba8
KL
1234 /**
1235 * Convenience function to add a scrollable text box to this
1236 * container/window.
1237 *
1238 * @param text text on the screen
1239 * @param x column relative to parent
1240 * @param y row relative to parent
1241 * @param width width of text area
1242 * @param height height of text area
1243 * @param colorKey ColorTheme key color to use for foreground text
1244 * @return the new text box
1245 */
c6940ed9 1246 public final TText addText(final String text, final int x,
cc99cba8
KL
1247 final int y, final int width, final int height, final String colorKey) {
1248
1249 return new TText(this, text, x, y, width, height, colorKey);
1250 }
1251
1252 /**
1253 * Convenience function to add a scrollable text box to this
1254 * container/window.
1255 *
1256 * @param text text on the screen
1257 * @param x column relative to parent
1258 * @param y row relative to parent
1259 * @param width width of text area
1260 * @param height height of text area
1261 * @return the new text box
1262 */
c6940ed9 1263 public final TText addText(final String text, final int x, final int y,
cc99cba8
KL
1264 final int width, final int height) {
1265
1266 return new TText(this, text, x, y, width, height, "ttext");
1267 }
1268
12b55d76
KL
1269 /**
1270 * Convenience function to add an editable text area box to this
1271 * container/window.
1272 *
1273 * @param text text on the screen
1274 * @param x column relative to parent
1275 * @param y row relative to parent
1276 * @param width width of text area
1277 * @param height height of text area
1278 * @return the new text box
1279 */
1280 public final TEditorWidget addEditor(final String text, final int x,
1281 final int y, final int width, final int height) {
1282
1283 return new TEditorWidget(this, text, x, y, width, height);
1284 }
1285
c6940ed9
KL
1286 /**
1287 * Convenience function to spawn a message box.
1288 *
1289 * @param title window title, will be centered along the top border
1290 * @param caption message to display. Use embedded newlines to get a
1291 * multi-line box.
1292 * @return the new message box
1293 */
1294 public final TMessageBox messageBox(final String title,
1295 final String caption) {
1296
1297 return getApplication().messageBox(title, caption, TMessageBox.Type.OK);
1298 }
1299
1300 /**
1301 * Convenience function to spawn a message box.
1302 *
1303 * @param title window title, will be centered along the top border
1304 * @param caption message to display. Use embedded newlines to get a
1305 * multi-line box.
1306 * @param type one of the TMessageBox.Type constants. Default is
1307 * Type.OK.
1308 * @return the new message box
1309 */
1310 public final TMessageBox messageBox(final String title,
1311 final String caption, final TMessageBox.Type type) {
1312
1313 return getApplication().messageBox(title, caption, type);
1314 }
1315
1316 /**
1317 * Convenience function to spawn an input box.
1318 *
1319 * @param title window title, will be centered along the top border
1320 * @param caption message to display. Use embedded newlines to get a
1321 * multi-line box.
1322 * @return the new input box
1323 */
1324 public final TInputBox inputBox(final String title, final String caption) {
1325
1326 return getApplication().inputBox(title, caption);
1327 }
1328
1329 /**
1330 * Convenience function to spawn an input box.
1331 *
1332 * @param title window title, will be centered along the top border
1333 * @param caption message to display. Use embedded newlines to get a
1334 * multi-line box.
1335 * @param text initial text to seed the field with
1336 * @return the new input box
1337 */
1338 public final TInputBox inputBox(final String title, final String caption,
1339 final String text) {
1340
1341 return getApplication().inputBox(title, caption, text);
1342 }
cc99cba8 1343
87a17f3c
KL
1344 /**
1345 * Convenience function to add a password text field to this
1346 * container/window.
1347 *
1348 * @param x column relative to parent
1349 * @param y row relative to parent
1350 * @param width visible text width
1351 * @param fixed if true, the text cannot exceed the display width
1352 * @return the new text field
1353 */
1354 public final TPasswordField addPasswordField(final int x, final int y,
1355 final int width, final boolean fixed) {
1356
1357 return new TPasswordField(this, x, y, width, fixed);
1358 }
1359
1360 /**
1361 * Convenience function to add a password text field to this
1362 * container/window.
1363 *
1364 * @param x column relative to parent
1365 * @param y row relative to parent
1366 * @param width visible text width
1367 * @param fixed if true, the text cannot exceed the display width
1368 * @param text initial text, default is empty string
1369 * @return the new text field
1370 */
1371 public final TPasswordField addPasswordField(final int x, final int y,
1372 final int width, final boolean fixed, final String text) {
1373
1374 return new TPasswordField(this, x, y, width, fixed, text);
1375 }
1376
1377 /**
1378 * Convenience function to add a password text field to this
1379 * container/window.
1380 *
1381 * @param x column relative to parent
1382 * @param y row relative to parent
1383 * @param width visible text width
1384 * @param fixed if true, the text cannot exceed the display width
1385 * @param text initial text, default is empty string
1386 * @param enterAction function to call when enter key is pressed
1387 * @param updateAction function to call when the text is updated
1388 * @return the new text field
1389 */
1390 public final TPasswordField addPasswordField(final int x, final int y,
1391 final int width, final boolean fixed, final String text,
1392 final TAction enterAction, final TAction updateAction) {
1393
1394 return new TPasswordField(this, x, y, width, fixed, text, enterAction,
1395 updateAction);
1396 }
1397
7668cb45
KL
1398 /**
1399 * Convenience function to add a tree view to this container/window.
1400 *
1401 * @param x column relative to parent
1402 * @param y row relative to parent
1403 * @param width width of tree view
1404 * @param height height of tree view
329fd62e 1405 * @return the new tree view
7668cb45
KL
1406 */
1407 public final TTreeView addTreeView(final int x, final int y,
1408 final int width, final int height) {
1409
1410 return new TTreeView(this, x, y, width, height);
1411 }
1412
1413 /**
1414 * Convenience function to add a tree view to this container/window.
1415 *
1416 * @param x column relative to parent
1417 * @param y row relative to parent
1418 * @param width width of tree view
1419 * @param height height of tree view
1420 * @param action action to perform when an item is selected
329fd62e 1421 * @return the new tree view
7668cb45
KL
1422 */
1423 public final TTreeView addTreeView(final int x, final int y,
1424 final int width, final int height, final TAction action) {
1425
1426 return new TTreeView(this, x, y, width, height, action);
1427 }
1428
0d47c546
KL
1429 /**
1430 * Convenience function to spawn a file open box.
1431 *
1432 * @param path path of selected file
1433 * @return the result of the new file open box
329fd62e 1434 * @throws IOException if a java.io operation throws
0d47c546
KL
1435 */
1436 public final String fileOpenBox(final String path) throws IOException {
1437 return getApplication().fileOpenBox(path);
1438 }
1439
1440 /**
1441 * Convenience function to spawn a file open box.
1442 *
1443 * @param path path of selected file
1444 * @param type one of the Type constants
1445 * @return the result of the new file open box
329fd62e 1446 * @throws IOException if a java.io operation throws
0d47c546
KL
1447 */
1448 public final String fileOpenBox(final String path,
1449 final TFileOpenBox.Type type) throws IOException {
1450
1451 return getApplication().fileOpenBox(path, type);
1452 }
1453 /**
1454 * Convenience function to add a directory list to this container/window.
1455 *
1456 * @param path directory path, must be a directory
1457 * @param x column relative to parent
1458 * @param y row relative to parent
1459 * @param width width of text area
1460 * @param height height of text area
329fd62e 1461 * @return the new directory list
0d47c546
KL
1462 */
1463 public final TDirectoryList addDirectoryList(final String path, final int x,
1464 final int y, final int width, final int height) {
1465
1466 return new TDirectoryList(this, path, x, y, width, height, null);
1467 }
1468
1469 /**
1470 * Convenience function to add a directory list to this container/window.
1471 *
1472 * @param path directory path, must be a directory
1473 * @param x column relative to parent
1474 * @param y row relative to parent
1475 * @param width width of text area
1476 * @param height height of text area
1477 * @param action action to perform when an item is selected
329fd62e 1478 * @return the new directory list
0d47c546
KL
1479 */
1480 public final TDirectoryList addDirectoryList(final String path, final int x,
1481 final int y, final int width, final int height, final TAction action) {
1482
1483 return new TDirectoryList(this, path, x, y, width, height, action);
1484 }
7668cb45 1485
3649b921
KL
1486 /**
1487 * Convenience function to add a directory list to this container/window.
1488 *
1489 * @param strings list of strings to show
1490 * @param x column relative to parent
1491 * @param y row relative to parent
1492 * @param width width of text area
1493 * @param height height of text area
1494 * @return the new directory list
1495 */
1496 public final TList addList(final List<String> strings, final int x,
1497 final int y, final int width, final int height) {
1498
1499 return new TList(this, strings, x, y, width, height, null);
1500 }
1501
1502 /**
1503 * Convenience function to add a directory list to this container/window.
1504 *
1505 * @param strings list of strings to show
1506 * @param x column relative to parent
1507 * @param y row relative to parent
1508 * @param width width of text area
1509 * @param height height of text area
1510 * @param enterAction action to perform when an item is selected
1511 * @return the new directory list
1512 */
1513 public final TList addList(final List<String> strings, final int x,
1514 final int y, final int width, final int height,
1515 final TAction enterAction) {
1516
1517 return new TList(this, strings, x, y, width, height, enterAction);
1518 }
1519
1520 /**
1521 * Convenience function to add a directory list to this container/window.
1522 *
1523 * @param strings list of strings to show
1524 * @param x column relative to parent
1525 * @param y row relative to parent
1526 * @param width width of text area
1527 * @param height height of text area
1528 * @param enterAction action to perform when an item is selected
1529 * @param moveAction action to perform when the user navigates to a new
1530 * item with arrow/page keys
1531 * @return the new directory list
1532 */
1533 public final TList addList(final List<String> strings, final int x,
1534 final int y, final int width, final int height,
1535 final TAction enterAction, final TAction moveAction) {
1536
1537 return new TList(this, strings, x, y, width, height, enterAction,
1538 moveAction);
1539 }
1540
48e27807 1541}