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