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