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