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