#19 TField 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.backend.Screen;
36 import jexer.bits.ColorTheme;
37 import jexer.event.TCommandEvent;
38 import jexer.event.TInputEvent;
39 import jexer.event.TKeypressEvent;
40 import jexer.event.TMenuEvent;
41 import jexer.event.TMouseEvent;
42 import jexer.event.TResizeEvent;
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 * 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
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 */
252 public final boolean isEnabled() {
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;
263 if (!enabled) {
264 active = false;
265 // See if there are any active siblings to switch to
266 boolean foundSibling = false;
267 if (parent != null) {
268 for (TWidget w: parent.children) {
269 if ((w.enabled)
270 && !(this instanceof THScroller)
271 && !(this instanceof TVScroller)
272 ) {
273 parent.activate(w);
274 foundSibling = true;
275 break;
276 }
277 }
278 if (!foundSibling) {
279 parent.activeChild = null;
280 }
281 }
282 }
283 }
284
285 /**
286 * If true, this widget has a cursor.
287 */
288 private boolean cursorVisible = false;
289
290 /**
291 * Set visible cursor flag.
292 *
293 * @param cursorVisible if true, this widget has a cursor
294 */
295 public final void setCursorVisible(final boolean cursorVisible) {
296 this.cursorVisible = cursorVisible;
297 }
298
299 /**
300 * See if this widget has a visible cursor.
301 *
302 * @return if true, this widget has a visible cursor
303 */
304 public final boolean isCursorVisible() {
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 }
324 return cursorVisible;
325 }
326
327 /**
328 * Cursor column position in relative coordinates.
329 */
330 private int cursorX = 0;
331
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
350 /**
351 * Cursor row position in relative coordinates.
352 */
353 private int cursorY = 0;
354
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
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
395 /**
396 * Comparison operator. For various subclasses it sorts on:
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
404 * @return difference between this.tabOrder and that.tabOrder, or
405 * difference between this.z and that.z, or String.compareTo(text)
406 */
407 public final int compareTo(final TWidget that) {
408 if ((this instanceof TWindow)
409 && (that instanceof TWindow)
410 ) {
411 return (((TWindow) this).getZ() - ((TWindow) that).getZ());
412 }
413 if ((this instanceof TTreeItem)
414 && (that instanceof TTreeItem)
415 ) {
416 return (((TTreeItem) this).getText().compareTo(
417 ((TTreeItem) that).getText()));
418 }
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 */
428 public final boolean isAbsoluteActive() {
429 if (parent == this) {
430 return active;
431 }
432 return (active && parent.isAbsoluteActive());
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() {
441 assert (cursorVisible);
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() {
451 assert (cursorVisible);
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 }
466 if ((parent instanceof TWindow)
467 && !(parent instanceof TMenu)
468 && !(parent instanceof TDesktop)
469 ) {
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 }
488 if ((parent instanceof TWindow)
489 && !(parent instanceof TMenu)
490 && !(parent instanceof TDesktop)
491 ) {
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
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
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);
522 assert (getScreen() != null);
523 Screen screen = getScreen();
524
525 // Special case: TStatusBar is drawn by TApplication, not anything
526 // else.
527 if (this instanceof TStatusBar) {
528 return;
529 }
530
531 screen.setClipRight(width);
532 screen.setClipBottom(height);
533
534 int absoluteRightEdge = window.getAbsoluteX() + window.getWidth();
535 int absoluteBottomEdge = window.getAbsoluteY() + window.getHeight();
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()
549 - (myRightEdge - absoluteRightEdge));
550 }
551 if (getAbsoluteY() > absoluteBottomEdge) {
552 // I am offscreen
553 screen.setClipBottom(0);
554 } else if (myBottomEdge > absoluteBottomEdge) {
555 screen.setClipBottom(screen.getClipBottom()
556 - (myBottomEdge - absoluteBottomEdge));
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
572 /**
573 * Repaint the screen on the next update.
574 */
575 public void doRepaint() {
576 window.getApplication().doRepaint();
577 }
578
579 // ------------------------------------------------------------------------
580 // Constructors -----------------------------------------------------------
581 // ------------------------------------------------------------------------
582
583 /**
584 * Default constructor for subclasses.
585 */
586 protected TWidget() {
587 children = new ArrayList<TWidget>();
588 }
589
590 /**
591 * Protected constructor.
592 *
593 * @param parent parent widget
594 */
595 protected TWidget(final TWidget parent) {
596 this(parent, true);
597 }
598
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
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;
622 this.parent = parent;
623 this.window = parent.window;
624 children = new ArrayList<TWidget>();
625 parent.addChild(this);
626 }
627
628 /**
629 * Protected constructor used by subclasses that are disabled by default.
630 *
631 * @param parent parent widget
632 * @param enabled if true assume enabled
633 * @param x column relative to parent
634 * @param y row relative to parent
635 * @param width width of widget
636 * @param height height of widget
637 */
638 protected TWidget(final TWidget parent, final boolean enabled,
639 final int x, final int y, final int width, final int height) {
640
641 this.enabled = enabled;
642 this.parent = parent;
643 this.window = parent.window;
644 children = new ArrayList<TWidget>();
645 parent.addChild(this);
646
647 this.x = x;
648 this.y = y;
649 this.width = width;
650 this.height = height;
651 }
652
653 /**
654 * Backdoor access for TWindow's constructor. ONLY TWindow USES THIS.
655 *
656 * @param window the top-level window
657 * @param x column relative to parent
658 * @param y row relative to parent
659 * @param width width of window
660 * @param height height of window
661 */
662 protected final void setupForTWindow(final TWindow window,
663 final int x, final int y, final int width, final int height) {
664
665 this.parent = window;
666 this.window = window;
667 this.x = x;
668 this.y = y;
669 this.width = width;
670 this.height = height;
671 }
672
673 // ------------------------------------------------------------------------
674 // General behavior -------------------------------------------------------
675 // ------------------------------------------------------------------------
676
677 /**
678 * Add a child widget to my list of children. We set its tabOrder to 0
679 * and increment the tabOrder of all other children.
680 *
681 * @param child TWidget to add
682 */
683 private void addChild(final TWidget child) {
684 children.add(child);
685
686 if ((child.enabled)
687 && !(child instanceof THScroller)
688 && !(child instanceof TVScroller)
689 ) {
690 for (TWidget widget: children) {
691 widget.active = false;
692 }
693 child.active = true;
694 activeChild = child;
695 }
696 for (int i = 0; i < children.size(); i++) {
697 children.get(i).tabOrder = i;
698 }
699 }
700
701 /**
702 * Switch the active child.
703 *
704 * @param child TWidget to activate
705 */
706 public final void activate(final TWidget child) {
707 assert (child.enabled);
708 if ((child instanceof THScroller)
709 || (child instanceof TVScroller)
710 ) {
711 return;
712 }
713
714 if (child != activeChild) {
715 if (activeChild != null) {
716 activeChild.active = false;
717 }
718 child.active = true;
719 activeChild = child;
720 }
721 }
722
723 /**
724 * Switch the active child.
725 *
726 * @param tabOrder tabOrder of the child to activate. If that child
727 * isn't enabled, then the next enabled child will be activated.
728 */
729 public final void activate(final int tabOrder) {
730 if (activeChild == null) {
731 return;
732 }
733 TWidget child = null;
734 for (TWidget widget: children) {
735 if ((widget.enabled)
736 && !(widget instanceof THScroller)
737 && !(widget instanceof TVScroller)
738 && (widget.tabOrder >= tabOrder)
739 ) {
740 child = widget;
741 break;
742 }
743 }
744 if ((child != null) && (child != activeChild)) {
745 activeChild.active = false;
746 assert (child.enabled);
747 child.active = true;
748 activeChild = child;
749 }
750 }
751
752 /**
753 * Switch the active widget with the next in the tab order.
754 *
755 * @param forward if true, then switch to the next enabled widget in the
756 * list, otherwise switch to the previous enabled widget in the list
757 */
758 public final void switchWidget(final boolean forward) {
759
760 // Only switch if there are multiple enabled widgets
761 if ((children.size() < 2) || (activeChild == null)) {
762 return;
763 }
764
765 int tabOrder = activeChild.tabOrder;
766 do {
767 if (forward) {
768 tabOrder++;
769 } else {
770 tabOrder--;
771 }
772 if (tabOrder < 0) {
773
774 // If at the end, pass the switch to my parent.
775 if ((!forward) && (parent != this)) {
776 parent.switchWidget(forward);
777 return;
778 }
779
780 tabOrder = children.size() - 1;
781 } else if (tabOrder == children.size()) {
782 // If at the end, pass the switch to my parent.
783 if ((forward) && (parent != this)) {
784 parent.switchWidget(forward);
785 return;
786 }
787
788 tabOrder = 0;
789 }
790 if (activeChild.tabOrder == tabOrder) {
791 // We wrapped around
792 break;
793 }
794 } while ((!children.get(tabOrder).enabled)
795 && !(children.get(tabOrder) instanceof THScroller)
796 && !(children.get(tabOrder) instanceof TVScroller));
797
798 assert (children.get(tabOrder).enabled);
799
800 activeChild.active = false;
801 children.get(tabOrder).active = true;
802 activeChild = children.get(tabOrder);
803 }
804
805 /**
806 * Returns my active widget.
807 *
808 * @return widget that is active, or this if no children
809 */
810 public TWidget getActiveChild() {
811 if ((this instanceof THScroller)
812 || (this instanceof TVScroller)
813 ) {
814 return parent;
815 }
816
817 for (TWidget widget: children) {
818 if (widget.active) {
819 return widget.getActiveChild();
820 }
821 }
822 // No active children, return me
823 return this;
824 }
825
826 // ------------------------------------------------------------------------
827 // Event handlers ---------------------------------------------------------
828 // ------------------------------------------------------------------------
829
830 /**
831 * Check if a mouse press/release event coordinate is contained in this
832 * widget.
833 *
834 * @param mouse a mouse-based event
835 * @return whether or not a mouse click would be sent to this widget
836 */
837 public final boolean mouseWouldHit(final TMouseEvent mouse) {
838
839 if (!enabled) {
840 return false;
841 }
842
843 if ((mouse.getAbsoluteX() >= getAbsoluteX())
844 && (mouse.getAbsoluteX() < getAbsoluteX() + width)
845 && (mouse.getAbsoluteY() >= getAbsoluteY())
846 && (mouse.getAbsoluteY() < getAbsoluteY() + height)
847 ) {
848 return true;
849 }
850 return false;
851 }
852
853 /**
854 * Method that subclasses can override to handle keystrokes.
855 *
856 * @param keypress keystroke event
857 */
858 public void onKeypress(final TKeypressEvent keypress) {
859
860 if ((children.size() == 0)
861 || (this instanceof TTreeView)
862 || (this instanceof TText)
863 ) {
864
865 // Defaults:
866 // tab / shift-tab - switch to next/previous widget
867 // right-arrow or down-arrow: same as tab
868 // left-arrow or up-arrow: same as shift-tab
869 if ((keypress.equals(kbTab))
870 || (keypress.equals(kbRight))
871 || (keypress.equals(kbDown))
872 ) {
873 parent.switchWidget(true);
874 return;
875 } else if ((keypress.equals(kbShiftTab))
876 || (keypress.equals(kbBackTab))
877 || (keypress.equals(kbLeft))
878 || (keypress.equals(kbUp))
879 ) {
880 parent.switchWidget(false);
881 return;
882 }
883 }
884
885 // If I have any buttons on me AND this is an Alt-key that matches
886 // its mnemonic, send it an Enter keystroke
887 for (TWidget widget: children) {
888 if (widget instanceof TButton) {
889 TButton button = (TButton) widget;
890 if (button.isEnabled()
891 && !keypress.getKey().isFnKey()
892 && keypress.getKey().isAlt()
893 && !keypress.getKey().isCtrl()
894 && (Character.toLowerCase(button.getMnemonic().getShortcut())
895 == Character.toLowerCase(keypress.getKey().getChar()))
896 ) {
897
898 widget.handleEvent(new TKeypressEvent(kbEnter));
899 return;
900 }
901 }
902 }
903
904 // Dispatch the keypress to an active widget
905 for (TWidget widget: children) {
906 if (widget.active) {
907 widget.handleEvent(keypress);
908 return;
909 }
910 }
911 }
912
913 /**
914 * Method that subclasses can override to handle mouse button presses.
915 *
916 * @param mouse mouse button event
917 */
918 public void onMouseDown(final TMouseEvent mouse) {
919 // Default: do nothing, pass to children instead
920 for (int i = children.size() - 1 ; i >= 0 ; i--) {
921 TWidget widget = children.get(i);
922 if (widget.mouseWouldHit(mouse)) {
923 // Dispatch to this child, also activate it
924 activate(widget);
925
926 // Set x and y relative to the child's coordinates
927 mouse.setX(mouse.getAbsoluteX() - widget.getAbsoluteX());
928 mouse.setY(mouse.getAbsoluteY() - widget.getAbsoluteY());
929 widget.handleEvent(mouse);
930 return;
931 }
932 }
933 }
934
935 /**
936 * Method that subclasses can override to handle mouse button releases.
937 *
938 * @param mouse mouse button event
939 */
940 public void onMouseUp(final TMouseEvent mouse) {
941 // Default: do nothing, pass to children instead
942 for (int i = children.size() - 1 ; i >= 0 ; i--) {
943 TWidget widget = children.get(i);
944 if (widget.mouseWouldHit(mouse)) {
945 // Dispatch to this child, also activate it
946 activate(widget);
947
948 // Set x and y relative to the child's coordinates
949 mouse.setX(mouse.getAbsoluteX() - widget.getAbsoluteX());
950 mouse.setY(mouse.getAbsoluteY() - widget.getAbsoluteY());
951 widget.handleEvent(mouse);
952 return;
953 }
954 }
955 }
956
957 /**
958 * Method that subclasses can override to handle mouse movements.
959 *
960 * @param mouse mouse motion event
961 */
962 public void onMouseMotion(final TMouseEvent mouse) {
963 // Default: do nothing, pass it on to ALL of my children. This way
964 // the children can see the mouse "leaving" their area.
965 for (TWidget widget: children) {
966 // Set x and y relative to the child's coordinates
967 mouse.setX(mouse.getAbsoluteX() - widget.getAbsoluteX());
968 mouse.setY(mouse.getAbsoluteY() - widget.getAbsoluteY());
969 widget.handleEvent(mouse);
970 }
971 }
972
973 /**
974 * Method that subclasses can override to handle window/screen resize
975 * events.
976 *
977 * @param resize resize event
978 */
979 public void onResize(final TResizeEvent resize) {
980 // Default: change my width/height.
981 if (resize.getType() == TResizeEvent.Type.WIDGET) {
982 width = resize.getWidth();
983 height = resize.getHeight();
984 } else {
985 // Let children see the screen resize
986 for (TWidget widget: children) {
987 widget.onResize(resize);
988 }
989 }
990 }
991
992 /**
993 * Method that subclasses can override to handle posted command events.
994 *
995 * @param command command event
996 */
997 public void onCommand(final TCommandEvent command) {
998 // Default: do nothing, pass to children instead
999 for (TWidget widget: children) {
1000 widget.onCommand(command);
1001 }
1002 }
1003
1004 /**
1005 * Method that subclasses can override to handle menu or posted menu
1006 * events.
1007 *
1008 * @param menu menu event
1009 */
1010 public void onMenu(final TMenuEvent menu) {
1011 // Default: do nothing, pass to children instead
1012 for (TWidget widget: children) {
1013 widget.onMenu(menu);
1014 }
1015 }
1016
1017 /**
1018 * Method that subclasses can override to do processing when the UI is
1019 * idle. Note that repainting is NOT assumed. To get a refresh after
1020 * onIdle, call doRepaint().
1021 */
1022 public void onIdle() {
1023 // Default: do nothing, pass to children instead
1024 for (TWidget widget: children) {
1025 widget.onIdle();
1026 }
1027 }
1028
1029 /**
1030 * Consume event. Subclasses that want to intercept all events in one go
1031 * can override this method.
1032 *
1033 * @param event keyboard, mouse, resize, command, or menu event
1034 */
1035 public void handleEvent(final TInputEvent event) {
1036 // System.err.printf("TWidget (%s) event: %s\n", this.getClass().getName(),
1037 // event);
1038
1039 if (!enabled) {
1040 // Discard event
1041 // System.err.println(" -- discard --");
1042 return;
1043 }
1044
1045 if (event instanceof TKeypressEvent) {
1046 onKeypress((TKeypressEvent) event);
1047 } else if (event instanceof TMouseEvent) {
1048
1049 TMouseEvent mouse = (TMouseEvent) event;
1050
1051 switch (mouse.getType()) {
1052
1053 case MOUSE_DOWN:
1054 onMouseDown(mouse);
1055 break;
1056
1057 case MOUSE_UP:
1058 onMouseUp(mouse);
1059 break;
1060
1061 case MOUSE_MOTION:
1062 onMouseMotion(mouse);
1063 break;
1064
1065 default:
1066 throw new IllegalArgumentException("Invalid mouse event type: "
1067 + mouse.getType());
1068 }
1069 } else if (event instanceof TResizeEvent) {
1070 onResize((TResizeEvent) event);
1071 } else if (event instanceof TCommandEvent) {
1072 onCommand((TCommandEvent) event);
1073 } else if (event instanceof TMenuEvent) {
1074 onMenu((TMenuEvent) event);
1075 }
1076
1077 // Do nothing else
1078 return;
1079 }
1080
1081 // ------------------------------------------------------------------------
1082 // Other TWidget constructors ---------------------------------------------
1083 // ------------------------------------------------------------------------
1084
1085 /**
1086 * Convenience function to add a label to this container/window.
1087 *
1088 * @param text label
1089 * @param x column relative to parent
1090 * @param y row relative to parent
1091 * @return the new label
1092 */
1093 public final TLabel addLabel(final String text, final int x, final int y) {
1094 return addLabel(text, x, y, "tlabel");
1095 }
1096
1097 /**
1098 * Convenience function to add a label to this container/window.
1099 *
1100 * @param text label
1101 * @param x column relative to parent
1102 * @param y row relative to parent
1103 * @param colorKey ColorTheme key color to use for foreground text.
1104 * Default is "tlabel"
1105 * @return the new label
1106 */
1107 public final TLabel addLabel(final String text, final int x, final int y,
1108 final String colorKey) {
1109
1110 return new TLabel(this, text, x, y, colorKey);
1111 }
1112
1113 /**
1114 * Convenience function to add a button to this container/window.
1115 *
1116 * @param text label on the button
1117 * @param x column relative to parent
1118 * @param y row relative to parent
1119 * @param action to call when button is pressed
1120 * @return the new button
1121 */
1122 public final TButton addButton(final String text, final int x, final int y,
1123 final TAction action) {
1124
1125 return new TButton(this, text, x, y, action);
1126 }
1127
1128 /**
1129 * Convenience function to add a checkbox to this container/window.
1130 *
1131 * @param x column relative to parent
1132 * @param y row relative to parent
1133 * @param label label to display next to (right of) the checkbox
1134 * @param checked initial check state
1135 * @return the new checkbox
1136 */
1137 public final TCheckbox addCheckbox(final int x, final int y,
1138 final String label, final boolean checked) {
1139
1140 return new TCheckbox(this, x, y, label, checked);
1141 }
1142
1143 /**
1144 * Convenience function to add a progress bar to this container/window.
1145 *
1146 * @param x column relative to parent
1147 * @param y row relative to parent
1148 * @param width width of progress bar
1149 * @param value initial value of percent complete
1150 * @return the new progress bar
1151 */
1152 public final TProgressBar addProgressBar(final int x, final int y,
1153 final int width, final int value) {
1154
1155 return new TProgressBar(this, x, y, width, value);
1156 }
1157
1158 /**
1159 * Convenience function to add a radio button group to this
1160 * container/window.
1161 *
1162 * @param x column relative to parent
1163 * @param y row relative to parent
1164 * @param label label to display on the group box
1165 * @return the new radio button group
1166 */
1167 public final TRadioGroup addRadioGroup(final int x, final int y,
1168 final String label) {
1169
1170 return new TRadioGroup(this, x, y, label);
1171 }
1172
1173 /**
1174 * Convenience function to add a text field to this container/window.
1175 *
1176 * @param x column relative to parent
1177 * @param y row relative to parent
1178 * @param width visible text width
1179 * @param fixed if true, the text cannot exceed the display width
1180 * @return the new text field
1181 */
1182 public final TField addField(final int x, final int y,
1183 final int width, final boolean fixed) {
1184
1185 return new TField(this, x, y, width, fixed);
1186 }
1187
1188 /**
1189 * Convenience function to add a text field to this container/window.
1190 *
1191 * @param x column relative to parent
1192 * @param y row relative to parent
1193 * @param width visible text width
1194 * @param fixed if true, the text cannot exceed the display width
1195 * @param text initial text, default is empty string
1196 * @return the new text field
1197 */
1198 public final TField addField(final int x, final int y,
1199 final int width, final boolean fixed, final String text) {
1200
1201 return new TField(this, x, y, width, fixed, text);
1202 }
1203
1204 /**
1205 * Convenience function to add a text field to this container/window.
1206 *
1207 * @param x column relative to parent
1208 * @param y row relative to parent
1209 * @param width visible text width
1210 * @param fixed if true, the text cannot exceed the display width
1211 * @param text initial text, default is empty string
1212 * @param enterAction function to call when enter key is pressed
1213 * @param updateAction function to call when the text is updated
1214 * @return the new text field
1215 */
1216 public final TField addField(final int x, final int y,
1217 final int width, final boolean fixed, final String text,
1218 final TAction enterAction, final TAction updateAction) {
1219
1220 return new TField(this, x, y, width, fixed, text, enterAction,
1221 updateAction);
1222 }
1223
1224 /**
1225 * Convenience function to add a scrollable text box to this
1226 * container/window.
1227 *
1228 * @param text text on the screen
1229 * @param x column relative to parent
1230 * @param y row relative to parent
1231 * @param width width of text area
1232 * @param height height of text area
1233 * @param colorKey ColorTheme key color to use for foreground text
1234 * @return the new text box
1235 */
1236 public final TText addText(final String text, final int x,
1237 final int y, final int width, final int height, final String colorKey) {
1238
1239 return new TText(this, text, x, y, width, height, colorKey);
1240 }
1241
1242 /**
1243 * Convenience function to add a scrollable text box to this
1244 * container/window.
1245 *
1246 * @param text text on the screen
1247 * @param x column relative to parent
1248 * @param y row relative to parent
1249 * @param width width of text area
1250 * @param height height of text area
1251 * @return the new text box
1252 */
1253 public final TText addText(final String text, final int x, final int y,
1254 final int width, final int height) {
1255
1256 return new TText(this, text, x, y, width, height, "ttext");
1257 }
1258
1259 /**
1260 * Convenience function to add an editable text area box to this
1261 * container/window.
1262 *
1263 * @param text text on the screen
1264 * @param x column relative to parent
1265 * @param y row relative to parent
1266 * @param width width of text area
1267 * @param height height of text area
1268 * @return the new text box
1269 */
1270 public final TEditorWidget addEditor(final String text, final int x,
1271 final int y, final int width, final int height) {
1272
1273 return new TEditorWidget(this, text, x, y, width, height);
1274 }
1275
1276 /**
1277 * Convenience function to spawn a message box.
1278 *
1279 * @param title window title, will be centered along the top border
1280 * @param caption message to display. Use embedded newlines to get a
1281 * multi-line box.
1282 * @return the new message box
1283 */
1284 public final TMessageBox messageBox(final String title,
1285 final String caption) {
1286
1287 return getApplication().messageBox(title, caption, TMessageBox.Type.OK);
1288 }
1289
1290 /**
1291 * Convenience function to spawn a message box.
1292 *
1293 * @param title window title, will be centered along the top border
1294 * @param caption message to display. Use embedded newlines to get a
1295 * multi-line box.
1296 * @param type one of the TMessageBox.Type constants. Default is
1297 * Type.OK.
1298 * @return the new message box
1299 */
1300 public final TMessageBox messageBox(final String title,
1301 final String caption, final TMessageBox.Type type) {
1302
1303 return getApplication().messageBox(title, caption, type);
1304 }
1305
1306 /**
1307 * Convenience function to spawn an input box.
1308 *
1309 * @param title window title, will be centered along the top border
1310 * @param caption message to display. Use embedded newlines to get a
1311 * multi-line box.
1312 * @return the new input box
1313 */
1314 public final TInputBox inputBox(final String title, final String caption) {
1315
1316 return getApplication().inputBox(title, caption);
1317 }
1318
1319 /**
1320 * Convenience function to spawn an input box.
1321 *
1322 * @param title window title, will be centered along the top border
1323 * @param caption message to display. Use embedded newlines to get a
1324 * multi-line box.
1325 * @param text initial text to seed the field with
1326 * @return the new input box
1327 */
1328 public final TInputBox inputBox(final String title, final String caption,
1329 final String text) {
1330
1331 return getApplication().inputBox(title, caption, text);
1332 }
1333
1334 /**
1335 * Convenience function to add a password text field to this
1336 * container/window.
1337 *
1338 * @param x column relative to parent
1339 * @param y row relative to parent
1340 * @param width visible text width
1341 * @param fixed if true, the text cannot exceed the display width
1342 * @return the new text field
1343 */
1344 public final TPasswordField addPasswordField(final int x, final int y,
1345 final int width, final boolean fixed) {
1346
1347 return new TPasswordField(this, x, y, width, fixed);
1348 }
1349
1350 /**
1351 * Convenience function to add a password text field to this
1352 * container/window.
1353 *
1354 * @param x column relative to parent
1355 * @param y row relative to parent
1356 * @param width visible text width
1357 * @param fixed if true, the text cannot exceed the display width
1358 * @param text initial text, default is empty string
1359 * @return the new text field
1360 */
1361 public final TPasswordField addPasswordField(final int x, final int y,
1362 final int width, final boolean fixed, final String text) {
1363
1364 return new TPasswordField(this, x, y, width, fixed, text);
1365 }
1366
1367 /**
1368 * Convenience function to add a password text field to this
1369 * container/window.
1370 *
1371 * @param x column relative to parent
1372 * @param y row relative to parent
1373 * @param width visible text width
1374 * @param fixed if true, the text cannot exceed the display width
1375 * @param text initial text, default is empty string
1376 * @param enterAction function to call when enter key is pressed
1377 * @param updateAction function to call when the text is updated
1378 * @return the new text field
1379 */
1380 public final TPasswordField addPasswordField(final int x, final int y,
1381 final int width, final boolean fixed, final String text,
1382 final TAction enterAction, final TAction updateAction) {
1383
1384 return new TPasswordField(this, x, y, width, fixed, text, enterAction,
1385 updateAction);
1386 }
1387
1388 /**
1389 * Convenience function to add a tree view to this container/window.
1390 *
1391 * @param x column relative to parent
1392 * @param y row relative to parent
1393 * @param width width of tree view
1394 * @param height height of tree view
1395 * @return the new tree view
1396 */
1397 public final TTreeView addTreeView(final int x, final int y,
1398 final int width, final int height) {
1399
1400 return new TTreeView(this, x, y, width, height);
1401 }
1402
1403 /**
1404 * Convenience function to add a tree view to this container/window.
1405 *
1406 * @param x column relative to parent
1407 * @param y row relative to parent
1408 * @param width width of tree view
1409 * @param height height of tree view
1410 * @param action action to perform when an item is selected
1411 * @return the new tree view
1412 */
1413 public final TTreeView addTreeView(final int x, final int y,
1414 final int width, final int height, final TAction action) {
1415
1416 return new TTreeView(this, x, y, width, height, action);
1417 }
1418
1419 /**
1420 * Convenience function to spawn a file open box.
1421 *
1422 * @param path path of selected file
1423 * @return the result of the new file open box
1424 * @throws IOException if a java.io operation throws
1425 */
1426 public final String fileOpenBox(final String path) throws IOException {
1427 return getApplication().fileOpenBox(path);
1428 }
1429
1430 /**
1431 * Convenience function to spawn a file open box.
1432 *
1433 * @param path path of selected file
1434 * @param type one of the Type constants
1435 * @return the result of the new file open box
1436 * @throws IOException if a java.io operation throws
1437 */
1438 public final String fileOpenBox(final String path,
1439 final TFileOpenBox.Type type) throws IOException {
1440
1441 return getApplication().fileOpenBox(path, type);
1442 }
1443 /**
1444 * Convenience function to add a directory list to this container/window.
1445 *
1446 * @param path directory path, must be a directory
1447 * @param x column relative to parent
1448 * @param y row relative to parent
1449 * @param width width of text area
1450 * @param height height of text area
1451 * @return the new directory list
1452 */
1453 public final TDirectoryList addDirectoryList(final String path, final int x,
1454 final int y, final int width, final int height) {
1455
1456 return new TDirectoryList(this, path, x, y, width, height, null);
1457 }
1458
1459 /**
1460 * Convenience function to add a directory list to this container/window.
1461 *
1462 * @param path directory path, must be a directory
1463 * @param x column relative to parent
1464 * @param y row relative to parent
1465 * @param width width of text area
1466 * @param height height of text area
1467 * @param action action to perform when an item is selected
1468 * @return the new directory list
1469 */
1470 public final TDirectoryList addDirectoryList(final String path, final int x,
1471 final int y, final int width, final int height, final TAction action) {
1472
1473 return new TDirectoryList(this, path, x, y, width, height, action);
1474 }
1475
1476 /**
1477 * Convenience function to add a directory list to this container/window.
1478 *
1479 * @param strings list of strings to show
1480 * @param x column relative to parent
1481 * @param y row relative to parent
1482 * @param width width of text area
1483 * @param height height of text area
1484 * @return the new directory list
1485 */
1486 public final TList addList(final List<String> strings, final int x,
1487 final int y, final int width, final int height) {
1488
1489 return new TList(this, strings, x, y, width, height, null);
1490 }
1491
1492 /**
1493 * Convenience function to add a directory list to this container/window.
1494 *
1495 * @param strings list of strings to show
1496 * @param x column relative to parent
1497 * @param y row relative to parent
1498 * @param width width of text area
1499 * @param height height of text area
1500 * @param enterAction action to perform when an item is selected
1501 * @return the new directory list
1502 */
1503 public final TList addList(final List<String> strings, final int x,
1504 final int y, final int width, final int height,
1505 final TAction enterAction) {
1506
1507 return new TList(this, strings, x, y, width, height, enterAction);
1508 }
1509
1510 /**
1511 * Convenience function to add a directory list to this container/window.
1512 *
1513 * @param strings list of strings to show
1514 * @param x column relative to parent
1515 * @param y row relative to parent
1516 * @param width width of text area
1517 * @param height height of text area
1518 * @param enterAction action to perform when an item is selected
1519 * @param moveAction action to perform when the user navigates to a new
1520 * item with arrow/page keys
1521 * @return the new directory list
1522 */
1523 public final TList addList(final List<String> strings, final int x,
1524 final int y, final int width, final int height,
1525 final TAction enterAction, final TAction moveAction) {
1526
1527 return new TList(this, strings, x, y, width, height, enterAction,
1528 moveAction);
1529 }
1530
1531 }