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