Fix set value
[fanfix.git] / src / jexer / TWindow.java
CommitLineData
daa4106c 1/*
48e27807
KL
2 * Jexer - Java Text User Interface
3 *
e16dda65 4 * The MIT License (MIT)
48e27807 5 *
a69ed767 6 * Copyright (C) 2019 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
5dfd1c11 31import java.util.HashSet;
d36057df 32import java.util.Set;
5dfd1c11 33
42873e30 34import jexer.backend.Screen;
48e27807
KL
35import jexer.bits.Cell;
36import jexer.bits.CellAttributes;
37import jexer.bits.GraphicsChars;
38import jexer.event.TCommandEvent;
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.TCommand.*;
45import static jexer.TKeypress.*;
46
47/**
48 * TWindow is the top-level container and drawing surface for other widgets.
49 */
2b9c27db 50public class TWindow extends TWidget {
48e27807 51
2ce6dab2 52 // ------------------------------------------------------------------------
d36057df 53 // Constants --------------------------------------------------------------
2ce6dab2
KL
54 // ------------------------------------------------------------------------
55
48e27807 56 /**
2ce6dab2 57 * Window is resizable (default yes).
48e27807 58 */
2ce6dab2 59 public static final int RESIZABLE = 0x01;
48e27807
KL
60
61 /**
2ce6dab2 62 * Window is modal (default no).
48e27807 63 */
2ce6dab2 64 public static final int MODAL = 0x02;
48e27807
KL
65
66 /**
2ce6dab2 67 * Window is centered (default no).
48e27807 68 */
2ce6dab2
KL
69 public static final int CENTERED = 0x04;
70
78a56d5d
KL
71 /**
72 * Window has no close box (default no). Window can still be closed via
73 * TApplication.closeWindow() and TWindow.close().
74 */
75 public static final int NOCLOSEBOX = 0x08;
76
68c5cd6b
KL
77 /**
78 * Window has no maximize box (default no).
79 */
80 public static final int NOZOOMBOX = 0x10;
81
48e27807 82 /**
d36057df
KL
83 * Window is placed at absolute position (no smart placement) (default
84 * no).
fca67db0 85 */
d36057df 86 public static final int ABSOLUTEXY = 0x20;
fca67db0
KL
87
88 /**
d36057df
KL
89 * Hitting the closebox with the mouse calls TApplication.hideWindow()
90 * rather than TApplication.closeWindow() (default no).
fca67db0 91 */
d36057df 92 public static final int HIDEONCLOSE = 0x40;
48e27807 93
2ce6dab2 94 // ------------------------------------------------------------------------
d36057df 95 // Variables --------------------------------------------------------------
2ce6dab2 96 // ------------------------------------------------------------------------
48e27807
KL
97
98 /**
d36057df 99 * Window flags. Note package private access.
48e27807 100 */
d36057df 101 int flags = RESIZABLE;
48e27807
KL
102
103 /**
d36057df 104 * Window title.
48e27807 105 */
d36057df 106 private String title = "";
48e27807
KL
107
108 /**
d36057df 109 * Window's parent TApplication.
48e27807 110 */
d36057df 111 private TApplication application;
48e27807
KL
112
113 /**
114 * Z order. Lower number means more in-front.
115 */
116 private int z = 0;
117
5dfd1c11
KL
118 /**
119 * Window's keyboard shortcuts. Any key in this set will be passed to
120 * the window directly rather than processed through the menu
121 * accelerators.
122 */
d36057df 123 private Set<TKeypress> keyboardShortcuts = new HashSet<TKeypress>();
2ce6dab2 124
48e27807
KL
125 /**
126 * If true, then the user clicked on the title bar and is moving the
127 * window.
128 */
bd8d51fa 129 protected boolean inWindowMove = false;
48e27807
KL
130
131 /**
132 * If true, then the user clicked on the bottom right corner and is
133 * resizing the window.
134 */
bd8d51fa 135 protected boolean inWindowResize = false;
48e27807
KL
136
137 /**
138 * If true, then the user selected "Size/Move" (or hit Ctrl-F5) and is
139 * resizing/moving the window via the keyboard.
140 */
d36057df 141 protected boolean inKeyboardResize = false;
48e27807
KL
142
143 /**
144 * If true, this window is maximized.
145 */
146 private boolean maximized = false;
147
148 /**
149 * Remember mouse state.
150 */
928811d8 151 protected TMouseEvent mouse;
48e27807
KL
152
153 // For moving the window. resizing also uses moveWindowMouseX/Y
154 private int moveWindowMouseX;
155 private int moveWindowMouseY;
156 private int oldWindowX;
157 private int oldWindowY;
158
159 // Resizing
160 private int resizeWindowWidth;
161 private int resizeWindowHeight;
162 private int minimumWindowWidth = 10;
163 private int minimumWindowHeight = 2;
164 private int maximumWindowWidth = -1;
165 private int maximumWindowHeight = -1;
166
167 // For maximize/restore
168 private int restoreWindowWidth;
169 private int restoreWindowHeight;
170 private int restoreWindowX;
171 private int restoreWindowY;
172
34a42e78 173 /**
d36057df
KL
174 * Hidden flag. A hidden window will still have its onIdle() called, and
175 * will also have onClose() called at application exit. Note package
176 * private access: TApplication will force hidden false if a modal window
177 * is active.
34a42e78 178 */
d36057df 179 boolean hidden = false;
34a42e78 180
71a389c9 181 /**
d36057df
KL
182 * A window may have a status bar associated with it. TApplication will
183 * draw this status bar last, and will also route events to it first
184 * before the window.
71a389c9 185 */
d36057df
KL
186 protected TStatusBar statusBar = null;
187
188 // ------------------------------------------------------------------------
189 // Constructors -----------------------------------------------------------
190 // ------------------------------------------------------------------------
71a389c9
KL
191
192 /**
d36057df 193 * Public constructor. Window will be located at (0, 0).
71a389c9 194 *
d36057df
KL
195 * @param application TApplication that manages this window
196 * @param title window title, will be centered along the top border
197 * @param width width of window
198 * @param height height of window
71a389c9 199 */
d36057df
KL
200 public TWindow(final TApplication application, final String title,
201 final int width, final int height) {
202
203 this(application, title, 0, 0, width, height, RESIZABLE);
71a389c9
KL
204 }
205
206 /**
d36057df 207 * Public constructor. Window will be located at (0, 0).
71a389c9 208 *
d36057df
KL
209 * @param application TApplication that manages this window
210 * @param title window title, will be centered along the top border
211 * @param width width of window
212 * @param height height of window
213 * @param flags bitmask of RESIZABLE, CENTERED, or MODAL
71a389c9 214 */
d36057df
KL
215 public TWindow(final TApplication application, final String title,
216 final int width, final int height, final int flags) {
71a389c9 217
d36057df 218 this(application, title, 0, 0, width, height, flags);
2ce6dab2
KL
219 }
220
221 /**
d36057df
KL
222 * Public constructor.
223 *
224 * @param application TApplication that manages this window
225 * @param title window title, will be centered along the top border
226 * @param x column relative to parent
227 * @param y row relative to parent
228 * @param width width of window
229 * @param height height of window
2ce6dab2 230 */
d36057df
KL
231 public TWindow(final TApplication application, final String title,
232 final int x, final int y, final int width, final int height) {
92453213 233
d36057df 234 this(application, title, x, y, width, height, RESIZABLE);
48e27807
KL
235 }
236
237 /**
238 * Public constructor.
239 *
240 * @param application TApplication that manages this window
241 * @param title window title, will be centered along the top border
242 * @param x column relative to parent
243 * @param y row relative to parent
244 * @param width width of window
245 * @param height height of window
246 * @param flags mask of RESIZABLE, CENTERED, or MODAL
247 */
248 public TWindow(final TApplication application, final String title,
249 final int x, final int y, final int width, final int height,
250 final int flags) {
251
fca67db0
KL
252 super();
253
48e27807 254 // I am my own window and parent
fca67db0
KL
255 setupForTWindow(this, x, y + application.getDesktopTop(),
256 width, height);
48e27807
KL
257
258 // Save fields
259 this.title = title;
260 this.application = application;
48e27807
KL
261 this.flags = flags;
262
263 // Minimum width/height are 10 and 2
264 assert (width >= 10);
fca67db0 265 assert (getHeight() >= 2);
48e27807
KL
266
267 // MODAL implies CENTERED
268 if (isModal()) {
269 this.flags |= CENTERED;
270 }
271
272 // Center window if specified
273 center();
274
275 // Add me to the application
d36057df 276 application.addWindowToApplication(this);
48e27807
KL
277 }
278
2ce6dab2 279 // ------------------------------------------------------------------------
d36057df 280 // Event handlers ---------------------------------------------------------
2ce6dab2 281 // ------------------------------------------------------------------------
48e27807 282
fe0770f9 283 /**
d36057df 284 * Returns true if the mouse is currently on the close button.
fe0770f9 285 *
d36057df 286 * @return true if mouse is currently on the close button
fe0770f9 287 */
d36057df
KL
288 protected boolean mouseOnClose() {
289 if ((flags & NOCLOSEBOX) != 0) {
290 return false;
291 }
292 if ((mouse != null)
293 && (mouse.getAbsoluteY() == getY())
294 && (mouse.getAbsoluteX() == getX() + 3)
295 ) {
fe0770f9
KL
296 return true;
297 }
298 return false;
299 }
300
301 /**
d36057df 302 * Returns true if the mouse is currently on the maximize/restore button.
48e27807 303 *
d36057df 304 * @return true if the mouse is currently on the maximize/restore button
48e27807 305 */
d36057df
KL
306 protected boolean mouseOnMaximize() {
307 if ((flags & NOZOOMBOX) != 0) {
48e27807
KL
308 return false;
309 }
d36057df
KL
310 if ((mouse != null)
311 && !isModal()
312 && (mouse.getAbsoluteY() == getY())
313 && (mouse.getAbsoluteX() == getX() + getWidth() - 4)
314 ) {
315 return true;
316 }
317 return false;
48e27807
KL
318 }
319
78a56d5d 320 /**
d36057df
KL
321 * Returns true if the mouse is currently on the resizable lower right
322 * corner.
78a56d5d 323 *
d36057df
KL
324 * @return true if the mouse is currently on the resizable lower right
325 * corner
78a56d5d 326 */
d36057df
KL
327 protected boolean mouseOnResize() {
328 if (((flags & RESIZABLE) != 0)
329 && !isModal()
330 && (mouse != null)
331 && (mouse.getAbsoluteY() == getY() + getHeight() - 1)
332 && ((mouse.getAbsoluteX() == getX() + getWidth() - 1)
333 || (mouse.getAbsoluteX() == getX() + getWidth() - 2))
334 ) {
78a56d5d
KL
335 return true;
336 }
337 return false;
338 }
339
a69ed767
KL
340 /**
341 * Subclasses should override this method to perform any user prompting
342 * before they are offscreen. Note that unlike other windowing toolkits,
343 * windows can NOT use this function in some manner to avoid being
344 * closed. This is called by application.closeWindow().
345 */
346 protected void onPreClose() {
347 // Default: do nothing.
348 }
349
68c5cd6b 350 /**
d36057df
KL
351 * Subclasses should override this method to cleanup resources. This is
352 * called by application.closeWindow().
68c5cd6b 353 */
a69ed767
KL
354 protected void onClose() {
355 // Default: perform widget-specific cleanup.
356 for (TWidget w: getChildren()) {
357 w.close();
358 }
68c5cd6b
KL
359 }
360
48e27807 361 /**
d36057df
KL
362 * Called by application.switchWindow() when this window gets the
363 * focus, and also by application.addWindow().
48e27807 364 */
a69ed767 365 protected void onFocus() {
d36057df 366 // Default: do nothing
48e27807
KL
367 }
368
369 /**
d36057df
KL
370 * Called by application.switchWindow() when another window gets the
371 * focus.
48e27807 372 */
a69ed767 373 protected void onUnfocus() {
d36057df 374 // Default: do nothing
48e27807
KL
375 }
376
377 /**
d36057df 378 * Called by application.hideWindow().
92453213 379 */
a69ed767 380 protected void onHide() {
92453213
KL
381 // Default: do nothing
382 }
383
384 /**
385 * Called by application.showWindow().
386 */
a69ed767 387 protected void onShow() {
92453213
KL
388 // Default: do nothing
389 }
390
48e27807
KL
391 /**
392 * Handle mouse button presses.
393 *
394 * @param mouse mouse button event
395 */
396 @Override
397 public void onMouseDown(final TMouseEvent mouse) {
398 this.mouse = mouse;
48e27807
KL
399
400 inKeyboardResize = false;
a69ed767
KL
401 inWindowMove = false;
402 inWindowResize = false;
48e27807 403
fca67db0 404 if ((mouse.getAbsoluteY() == getY())
7c870d89 405 && mouse.isMouse1()
fca67db0
KL
406 && (getX() <= mouse.getAbsoluteX())
407 && (mouse.getAbsoluteX() < getX() + getWidth())
48e27807
KL
408 && !mouseOnClose()
409 && !mouseOnMaximize()
410 ) {
411 // Begin moving window
412 inWindowMove = true;
413 moveWindowMouseX = mouse.getAbsoluteX();
414 moveWindowMouseY = mouse.getAbsoluteY();
fca67db0
KL
415 oldWindowX = getX();
416 oldWindowY = getY();
48e27807
KL
417 if (maximized) {
418 maximized = false;
419 }
420 return;
421 }
422 if (mouseOnResize()) {
423 // Begin window resize
424 inWindowResize = true;
425 moveWindowMouseX = mouse.getAbsoluteX();
426 moveWindowMouseY = mouse.getAbsoluteY();
fca67db0
KL
427 resizeWindowWidth = getWidth();
428 resizeWindowHeight = getHeight();
48e27807
KL
429 if (maximized) {
430 maximized = false;
431 }
432 return;
433 }
434
2ce6dab2
KL
435 // Give the shortcut bar a shot at this.
436 if (statusBar != null) {
437 if (statusBar.statusBarMouseDown(mouse)) {
438 return;
439 }
440 }
441
48e27807
KL
442 // I didn't take it, pass it on to my children
443 super.onMouseDown(mouse);
444 }
445
48e27807
KL
446 /**
447 * Handle mouse button releases.
448 *
449 * @param mouse mouse button release event
450 */
451 @Override
452 public void onMouseUp(final TMouseEvent mouse) {
453 this.mouse = mouse;
48e27807 454
7c870d89 455 if ((inWindowMove) && (mouse.isMouse1())) {
48e27807
KL
456 // Stop moving window
457 inWindowMove = false;
458 return;
459 }
460
7c870d89 461 if ((inWindowResize) && (mouse.isMouse1())) {
48e27807
KL
462 // Stop resizing window
463 inWindowResize = false;
464 return;
465 }
466
7c870d89 467 if (mouse.isMouse1() && mouseOnClose()) {
d36057df
KL
468 if ((flags & HIDEONCLOSE) == 0) {
469 // Close window
470 application.closeWindow(this);
471 } else {
472 // Hide window
473 application.hideWindow(this);
474 }
48e27807
KL
475 return;
476 }
477
fca67db0 478 if ((mouse.getAbsoluteY() == getY())
7c870d89 479 && mouse.isMouse1()
48e27807
KL
480 && mouseOnMaximize()) {
481 if (maximized) {
482 // Restore
483 restore();
484 } else {
485 // Maximize
486 maximize();
487 }
488 // Pass a resize event to my children
fca67db0
KL
489 onResize(new TResizeEvent(TResizeEvent.Type.WIDGET,
490 getWidth(), getHeight()));
48e27807
KL
491 return;
492 }
493
2ce6dab2
KL
494 // Give the shortcut bar a shot at this.
495 if (statusBar != null) {
496 if (statusBar.statusBarMouseUp(mouse)) {
497 return;
498 }
499 }
500
48e27807
KL
501 // I didn't take it, pass it on to my children
502 super.onMouseUp(mouse);
503 }
504
505 /**
506 * Handle mouse movements.
507 *
508 * @param mouse mouse motion event
509 */
510 @Override
511 public void onMouseMotion(final TMouseEvent mouse) {
512 this.mouse = mouse;
48e27807
KL
513
514 if (inWindowMove) {
515 // Move window over
fca67db0
KL
516 setX(oldWindowX + (mouse.getAbsoluteX() - moveWindowMouseX));
517 setY(oldWindowY + (mouse.getAbsoluteY() - moveWindowMouseY));
48e27807 518 // Don't cover up the menu bar
fca67db0
KL
519 if (getY() < application.getDesktopTop()) {
520 setY(application.getDesktopTop());
48e27807 521 }
2ce6dab2
KL
522 // Don't go below the status bar
523 if (getY() >= application.getDesktopBottom()) {
524 setY(application.getDesktopBottom() - 1);
525 }
48e27807
KL
526 return;
527 }
528
529 if (inWindowResize) {
12b55d76
KL
530 // Do not permit resizing below the status line
531 if (mouse.getAbsoluteY() == application.getDesktopBottom()) {
532 inWindowResize = false;
533 return;
534 }
535
48e27807 536 // Move window over
fca67db0
KL
537 setWidth(resizeWindowWidth + (mouse.getAbsoluteX()
538 - moveWindowMouseX));
539 setHeight(resizeWindowHeight + (mouse.getAbsoluteY()
540 - moveWindowMouseY));
541 if (getX() + getWidth() > getScreen().getWidth()) {
542 setWidth(getScreen().getWidth() - getX());
48e27807 543 }
fca67db0
KL
544 if (getY() + getHeight() > application.getDesktopBottom()) {
545 setY(application.getDesktopBottom() - getHeight() + 1);
48e27807
KL
546 }
547 // Don't cover up the menu bar
fca67db0
KL
548 if (getY() < application.getDesktopTop()) {
549 setY(application.getDesktopTop());
48e27807
KL
550 }
551
552 // Keep within min/max bounds
fca67db0
KL
553 if (getWidth() < minimumWindowWidth) {
554 setWidth(minimumWindowWidth);
48e27807
KL
555 inWindowResize = false;
556 }
fca67db0
KL
557 if (getHeight() < minimumWindowHeight) {
558 setHeight(minimumWindowHeight);
48e27807
KL
559 inWindowResize = false;
560 }
fca67db0
KL
561 if ((maximumWindowWidth > 0)
562 && (getWidth() > maximumWindowWidth)
563 ) {
564 setWidth(maximumWindowWidth);
48e27807
KL
565 inWindowResize = false;
566 }
fca67db0
KL
567 if ((maximumWindowHeight > 0)
568 && (getHeight() > maximumWindowHeight)
569 ) {
570 setHeight(maximumWindowHeight);
48e27807
KL
571 inWindowResize = false;
572 }
573
574 // Pass a resize event to my children
fca67db0
KL
575 onResize(new TResizeEvent(TResizeEvent.Type.WIDGET,
576 getWidth(), getHeight()));
48e27807
KL
577 return;
578 }
579
2ce6dab2
KL
580 // Give the shortcut bar a shot at this.
581 if (statusBar != null) {
582 statusBar.statusBarMouseMotion(mouse);
583 }
584
48e27807
KL
585 // I didn't take it, pass it on to my children
586 super.onMouseMotion(mouse);
587 }
588
589 /**
590 * Handle keystrokes.
591 *
592 * @param keypress keystroke event
593 */
594 @Override
595 public void onKeypress(final TKeypressEvent keypress) {
596
597 if (inKeyboardResize) {
598
32437017
KL
599 // ESC or ENTER - Exit size/move
600 if (keypress.equals(kbEsc) || keypress.equals(kbEnter)) {
48e27807
KL
601 inKeyboardResize = false;
602 }
603
604 if (keypress.equals(kbLeft)) {
fca67db0
KL
605 if (getX() > 0) {
606 setX(getX() - 1);
48e27807
KL
607 }
608 }
609 if (keypress.equals(kbRight)) {
fca67db0
KL
610 if (getX() < getScreen().getWidth() - 1) {
611 setX(getX() + 1);
48e27807
KL
612 }
613 }
614 if (keypress.equals(kbDown)) {
fca67db0
KL
615 if (getY() < application.getDesktopBottom() - 1) {
616 setY(getY() + 1);
48e27807
KL
617 }
618 }
619 if (keypress.equals(kbUp)) {
fca67db0
KL
620 if (getY() > 1) {
621 setY(getY() - 1);
48e27807
KL
622 }
623 }
3b0a5f8b
KL
624
625 /*
626 * Only permit keyboard resizing if the window was RESIZABLE.
627 */
628 if ((flags & RESIZABLE) != 0) {
629
630 if (keypress.equals(kbShiftLeft)) {
631 if ((getWidth() > minimumWindowWidth)
632 || (minimumWindowWidth <= 0)
633 ) {
634 setWidth(getWidth() - 1);
635 }
48e27807 636 }
3b0a5f8b
KL
637 if (keypress.equals(kbShiftRight)) {
638 if ((getWidth() < maximumWindowWidth)
639 || (maximumWindowWidth <= 0)
640 ) {
641 setWidth(getWidth() + 1);
642 }
48e27807 643 }
3b0a5f8b
KL
644 if (keypress.equals(kbShiftUp)) {
645 if ((getHeight() > minimumWindowHeight)
646 || (minimumWindowHeight <= 0)
647 ) {
648 setHeight(getHeight() - 1);
649 }
48e27807 650 }
3b0a5f8b
KL
651 if (keypress.equals(kbShiftDown)) {
652 if ((getHeight() < maximumWindowHeight)
653 || (maximumWindowHeight <= 0)
654 ) {
655 setHeight(getHeight() + 1);
656 }
48e27807 657 }
48e27807 658
3b0a5f8b
KL
659 // Pass a resize event to my children
660 onResize(new TResizeEvent(TResizeEvent.Type.WIDGET,
661 getWidth(), getHeight()));
662
663 } // if ((flags & RESIZABLE) != 0)
0d47c546 664
48e27807
KL
665 return;
666 }
667
2ce6dab2
KL
668 // Give the shortcut bar a shot at this.
669 if (statusBar != null) {
670 if (statusBar.statusBarKeypress(keypress)) {
671 return;
672 }
673 }
674
48e27807
KL
675 // These keystrokes will typically not be seen unless a subclass
676 // overrides onMenu() due to how TApplication dispatches
677 // accelerators.
678
92453213 679 if (!(this instanceof TDesktop)) {
48e27807 680
92453213
KL
681 // Ctrl-W - close window
682 if (keypress.equals(kbCtrlW)) {
78a56d5d 683 if ((flags & NOCLOSEBOX) == 0) {
d36057df
KL
684 if ((flags & HIDEONCLOSE) == 0) {
685 // Close window
686 application.closeWindow(this);
687 } else {
688 // Hide window
689 application.hideWindow(this);
690 }
78a56d5d 691 }
92453213
KL
692 return;
693 }
48e27807 694
92453213
KL
695 // F6 - behave like Alt-TAB
696 if (keypress.equals(kbF6)) {
697 application.switchWindow(true);
698 return;
699 }
48e27807 700
92453213
KL
701 // Shift-F6 - behave like Shift-Alt-TAB
702 if (keypress.equals(kbShiftF6)) {
703 application.switchWindow(false);
704 return;
48e27807 705 }
48e27807 706
92453213 707 // F5 - zoom
68c5cd6b 708 if (keypress.equals(kbF5) && ((flags & NOZOOMBOX) == 0)) {
92453213
KL
709 if (maximized) {
710 restore();
711 } else {
712 maximize();
713 }
714 }
715
716 // Ctrl-F5 - size/move
717 if (keypress.equals(kbCtrlF5)) {
718 inKeyboardResize = !inKeyboardResize;
719 }
720
721 } // if (!(this instanceof TDesktop))
48e27807
KL
722
723 // I didn't take it, pass it on to my children
724 super.onKeypress(keypress);
725 }
726
727 /**
728 * Handle posted command events.
729 *
730 * @param command command event
731 */
732 @Override
733 public void onCommand(final TCommandEvent command) {
734
735 // These commands will typically not be seen unless a subclass
736 // overrides onMenu() due to how TApplication dispatches
737 // accelerators.
738
92453213 739 if (!(this instanceof TDesktop)) {
48e27807 740
92453213 741 if (command.equals(cmWindowClose)) {
78a56d5d 742 if ((flags & NOCLOSEBOX) == 0) {
d36057df
KL
743 if ((flags & HIDEONCLOSE) == 0) {
744 // Close window
745 application.closeWindow(this);
746 } else {
747 // Hide window
748 application.hideWindow(this);
749 }
78a56d5d 750 }
92453213
KL
751 return;
752 }
48e27807 753
92453213
KL
754 if (command.equals(cmWindowNext)) {
755 application.switchWindow(true);
756 return;
757 }
48e27807 758
92453213
KL
759 if (command.equals(cmWindowPrevious)) {
760 application.switchWindow(false);
761 return;
762 }
48e27807 763
92453213
KL
764 if (command.equals(cmWindowMove)) {
765 inKeyboardResize = true;
766 return;
767 }
768
68c5cd6b 769 if (command.equals(cmWindowZoom) && ((flags & NOZOOMBOX) == 0)) {
92453213
KL
770 if (maximized) {
771 restore();
772 } else {
773 maximize();
774 }
48e27807 775 }
92453213
KL
776
777 } // if (!(this instanceof TDesktop))
48e27807
KL
778
779 // I didn't take it, pass it on to my children
780 super.onCommand(command);
781 }
782
783 /**
784 * Handle posted menu events.
785 *
786 * @param menu menu event
787 */
788 @Override
789 public void onMenu(final TMenuEvent menu) {
48e27807 790
92453213 791 if (!(this instanceof TDesktop)) {
48e27807 792
92453213 793 if (menu.getId() == TMenu.MID_WINDOW_CLOSE) {
78a56d5d 794 if ((flags & NOCLOSEBOX) == 0) {
d36057df
KL
795 if ((flags & HIDEONCLOSE) == 0) {
796 // Close window
797 application.closeWindow(this);
798 } else {
799 // Hide window
800 application.hideWindow(this);
801 }
78a56d5d 802 }
92453213
KL
803 return;
804 }
48e27807 805
92453213
KL
806 if (menu.getId() == TMenu.MID_WINDOW_NEXT) {
807 application.switchWindow(true);
808 return;
809 }
48e27807 810
92453213
KL
811 if (menu.getId() == TMenu.MID_WINDOW_PREVIOUS) {
812 application.switchWindow(false);
813 return;
48e27807 814 }
92453213
KL
815
816 if (menu.getId() == TMenu.MID_WINDOW_MOVE) {
817 inKeyboardResize = true;
818 return;
819 }
820
68c5cd6b
KL
821 if ((menu.getId() == TMenu.MID_WINDOW_ZOOM)
822 && ((flags & NOZOOMBOX) == 0)
823 ) {
92453213
KL
824 if (maximized) {
825 restore();
826 } else {
827 maximize();
828 }
829 return;
830 }
831
832 } // if (!(this instanceof TDesktop))
48e27807
KL
833
834 // I didn't take it, pass it on to my children
835 super.onMenu(menu);
836 }
837
d36057df
KL
838 // ------------------------------------------------------------------------
839 // TWidget ----------------------------------------------------------------
840 // ------------------------------------------------------------------------
841
842 /**
843 * Get this TWindow's parent TApplication.
844 *
845 * @return this TWindow's parent TApplication
846 */
847 @Override
848 public final TApplication getApplication() {
849 return application;
850 }
851
852 /**
853 * Get the Screen.
854 *
855 * @return the Screen
856 */
857 @Override
858 public final Screen getScreen() {
859 return application.getScreen();
860 }
861
862 /**
863 * Called by TApplication.drawChildren() to render on screen.
864 */
865 @Override
866 public void draw() {
867 // Draw the box and background first.
868 CellAttributes border = getBorder();
869 CellAttributes background = getBackground();
870 int borderType = getBorderType();
871
a69ed767
KL
872 drawBox(0, 0, getWidth(), getHeight(), border, background, borderType,
873 true);
d36057df
KL
874
875 // Draw the title
876 int titleLeft = (getWidth() - title.length() - 2) / 2;
877 putCharXY(titleLeft, 0, ' ', border);
878 putStringXY(titleLeft + 1, 0, title);
879 putCharXY(titleLeft + title.length() + 1, 0, ' ', border);
880
881 if (isActive()) {
882
883 // Draw the close button
884 if ((flags & NOCLOSEBOX) == 0) {
885 putCharXY(2, 0, '[', border);
886 putCharXY(4, 0, ']', border);
887 if (mouseOnClose() && mouse.isMouse1()) {
888 putCharXY(3, 0, GraphicsChars.CP437[0x0F],
889 getBorderControls());
890 } else {
891 putCharXY(3, 0, GraphicsChars.CP437[0xFE],
892 getBorderControls());
893 }
894 }
895
896 // Draw the maximize button
897 if (!isModal() && ((flags & NOZOOMBOX) == 0)) {
898
899 putCharXY(getWidth() - 5, 0, '[', border);
900 putCharXY(getWidth() - 3, 0, ']', border);
901 if (mouseOnMaximize() && mouse.isMouse1()) {
902 putCharXY(getWidth() - 4, 0, GraphicsChars.CP437[0x0F],
903 getBorderControls());
904 } else {
905 if (maximized) {
906 putCharXY(getWidth() - 4, 0, GraphicsChars.CP437[0x12],
907 getBorderControls());
908 } else {
909 putCharXY(getWidth() - 4, 0, GraphicsChars.UPARROW,
910 getBorderControls());
911 }
912 }
913
914 // Draw the resize corner
915 if ((flags & RESIZABLE) != 0) {
916 putCharXY(getWidth() - 2, getHeight() - 1,
917 GraphicsChars.SINGLE_BAR, getBorderControls());
918 putCharXY(getWidth() - 1, getHeight() - 1,
919 GraphicsChars.LRCORNER, getBorderControls());
920 }
921 }
922 }
923 }
924
925 // ------------------------------------------------------------------------
926 // TWindow ----------------------------------------------------------------
927 // ------------------------------------------------------------------------
928
929 /**
930 * Get window title.
931 *
932 * @return window title
933 */
934 public final String getTitle() {
935 return title;
936 }
937
938 /**
939 * Set window title.
940 *
941 * @param title new window title
942 */
943 public final void setTitle(final String title) {
944 this.title = title;
945 }
946
947 /**
948 * Get Z order. Lower number means more in-front.
949 *
950 * @return Z value. Lower number means more in-front.
951 */
952 public final int getZ() {
953 return z;
954 }
955
956 /**
957 * Set Z order. Lower number means more in-front.
958 *
959 * @param z the new Z value. Lower number means more in-front.
960 */
961 public final void setZ(final int z) {
962 this.z = z;
963 }
964
965 /**
966 * Add a keypress to be overridden for this window.
967 *
968 * @param key the key to start taking control of
969 */
970 protected void addShortcutKeypress(final TKeypress key) {
971 keyboardShortcuts.add(key);
972 }
973
974 /**
975 * Remove a keypress to be overridden for this window.
976 *
977 * @param key the key to stop taking control of
978 */
979 protected void removeShortcutKeypress(final TKeypress key) {
980 keyboardShortcuts.remove(key);
981 }
982
983 /**
984 * Remove all keypresses to be overridden for this window.
985 */
986 protected void clearShortcutKeypresses() {
987 keyboardShortcuts.clear();
988 }
989
990 /**
991 * Determine if a keypress is overridden for this window.
992 *
993 * @param key the key to check
994 * @return true if this window wants to process this key on its own
995 */
996 public boolean isShortcutKeypress(final TKeypress key) {
997 return keyboardShortcuts.contains(key);
998 }
999
1000 /**
1001 * Get the window's status bar, or null if it does not have one.
1002 *
1003 * @return the status bar, or null
1004 */
1005 public TStatusBar getStatusBar() {
1006 return statusBar;
1007 }
1008
1009 /**
1010 * Set the window's status bar to a new one.
1011 *
1012 * @param text the status bar text
1013 * @return the status bar
1014 */
1015 public TStatusBar newStatusBar(final String text) {
1016 statusBar = new TStatusBar(this, text);
1017 return statusBar;
1018 }
1019
1020 /**
1021 * Set the maximum width for this window.
1022 *
1023 * @param maximumWindowWidth new maximum width
1024 */
1025 public final void setMaximumWindowWidth(final int maximumWindowWidth) {
1026 if ((maximumWindowWidth != -1)
1027 && (maximumWindowWidth < minimumWindowWidth + 1)
1028 ) {
1029 throw new IllegalArgumentException("Maximum window width cannot " +
1030 "be smaller than minimum window width + 1");
1031 }
1032 this.maximumWindowWidth = maximumWindowWidth;
1033 }
1034
1035 /**
1036 * Set the minimum width for this window.
1037 *
1038 * @param minimumWindowWidth new minimum width
1039 */
1040 public final void setMinimumWindowWidth(final int minimumWindowWidth) {
1041 if ((maximumWindowWidth != -1)
1042 && (minimumWindowWidth > maximumWindowWidth - 1)
1043 ) {
1044 throw new IllegalArgumentException("Minimum window width cannot " +
1045 "be larger than maximum window width - 1");
1046 }
1047 this.minimumWindowWidth = minimumWindowWidth;
1048 }
1049
1050 /**
1051 * Set the maximum height for this window.
1052 *
1053 * @param maximumWindowHeight new maximum height
1054 */
1055 public final void setMaximumWindowHeight(final int maximumWindowHeight) {
1056 if ((maximumWindowHeight != -1)
1057 && (maximumWindowHeight < minimumWindowHeight + 1)
1058 ) {
1059 throw new IllegalArgumentException("Maximum window height cannot " +
1060 "be smaller than minimum window height + 1");
1061 }
1062 this.maximumWindowHeight = maximumWindowHeight;
1063 }
1064
1065 /**
1066 * Set the minimum height for this window.
1067 *
1068 * @param minimumWindowHeight new minimum height
1069 */
1070 public final void setMinimumWindowHeight(final int minimumWindowHeight) {
1071 if ((maximumWindowHeight != -1)
1072 && (minimumWindowHeight > maximumWindowHeight - 1)
1073 ) {
1074 throw new IllegalArgumentException("Minimum window height cannot " +
1075 "be larger than maximum window height - 1");
1076 }
1077 this.minimumWindowHeight = minimumWindowHeight;
1078 }
1079
1080 /**
1081 * Recenter the window on-screen.
1082 */
1083 public final void center() {
1084 if ((flags & CENTERED) != 0) {
1085 if (getWidth() < getScreen().getWidth()) {
1086 setX((getScreen().getWidth() - getWidth()) / 2);
1087 } else {
1088 setX(0);
1089 }
1090 setY(((application.getDesktopBottom()
1091 - application.getDesktopTop()) - getHeight()) / 2);
1092 if (getY() < 0) {
1093 setY(0);
1094 }
1095 setY(getY() + application.getDesktopTop());
1096 }
1097 }
1098
1099 /**
1100 * Maximize window.
1101 */
1102 public void maximize() {
1103 if (maximized) {
1104 return;
1105 }
1106
1107 restoreWindowWidth = getWidth();
1108 restoreWindowHeight = getHeight();
1109 restoreWindowX = getX();
1110 restoreWindowY = getY();
1111 setWidth(getScreen().getWidth());
1112 setHeight(application.getDesktopBottom() - 1);
1113 setX(0);
1114 setY(1);
1115 maximized = true;
1116
1117 onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, getWidth(),
1118 getHeight()));
1119 }
1120
1121 /**
1122 * Restore (unmaximize) window.
1123 */
1124 public void restore() {
1125 if (!maximized) {
1126 return;
1127 }
1128
1129 setWidth(restoreWindowWidth);
1130 setHeight(restoreWindowHeight);
1131 setX(restoreWindowX);
1132 setY(restoreWindowY);
1133 maximized = false;
1134
1135 onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, getWidth(),
1136 getHeight()));
1137 }
1138
1139 /**
1140 * Returns true if this window is hidden.
1141 *
1142 * @return true if this window is hidden, false if the window is shown
1143 */
1144 public final boolean isHidden() {
1145 return hidden;
1146 }
1147
1148 /**
1149 * Returns true if this window is shown.
1150 *
1151 * @return true if this window is shown, false if the window is hidden
1152 */
1153 public final boolean isShown() {
1154 return !hidden;
1155 }
1156
1157 /**
1158 * Hide window. A hidden window will still have its onIdle() called, and
1159 * will also have onClose() called at application exit. Hidden windows
1160 * will not receive any other events.
1161 */
1162 public void hide() {
1163 application.hideWindow(this);
1164 }
1165
1166 /**
1167 * Show window.
1168 */
1169 public void show() {
1170 application.showWindow(this);
1171 }
1172
1173 /**
1174 * Activate window (bring to top and receive events).
1175 */
1176 public void activate() {
1177 application.activateWindow(this);
1178 }
1179
1180 /**
1181 * Close window. Note that windows without a close box can still be
1182 * closed by calling the close() method.
1183 */
1184 public void close() {
1185 application.closeWindow(this);
1186 }
1187
1188 /**
1189 * See if this window is undergoing any movement/resize/etc.
1190 *
1191 * @return true if the window is moving
1192 */
1193 public boolean inMovements() {
1194 if (inWindowResize || inWindowMove || inKeyboardResize) {
1195 return true;
1196 }
1197 return false;
1198 }
1199
1200 /**
1201 * Stop any pending movement/resize/etc.
1202 */
1203 public void stopMovements() {
1204 inWindowResize = false;
1205 inWindowMove = false;
1206 inKeyboardResize = false;
1207 }
1208
1209 /**
1210 * Returns true if this window is modal.
1211 *
1212 * @return true if this window is modal
1213 */
1214 public final boolean isModal() {
1215 if ((flags & MODAL) == 0) {
1216 return false;
1217 }
1218 return true;
1219 }
1220
1221 /**
1222 * Returns true if this window has a close box.
1223 *
1224 * @return true if this window has a close box
1225 */
1226 public final boolean hasCloseBox() {
1227 if ((flags & NOCLOSEBOX) != 0) {
1228 return true;
1229 }
1230 return false;
1231 }
1232
1233 /**
1234 * Returns true if this window has a maximize/zoom box.
1235 *
1236 * @return true if this window has a maximize/zoom box
1237 */
1238 public final boolean hasZoomBox() {
1239 if ((flags & NOZOOMBOX) != 0) {
1240 return true;
1241 }
1242 return false;
1243 }
1244
1245 /**
1246 * Retrieve the background color.
1247 *
1248 * @return the background color
1249 */
1250 public CellAttributes getBackground() {
1251 if (!isModal()
1252 && (inWindowMove || inWindowResize || inKeyboardResize)
1253 ) {
1254 assert (isActive());
1255 return getTheme().getColor("twindow.background.windowmove");
1256 } else if (isModal() && inWindowMove) {
1257 assert (isActive());
1258 return getTheme().getColor("twindow.background.modal");
1259 } else if (isModal()) {
1260 if (isActive()) {
1261 return getTheme().getColor("twindow.background.modal");
1262 }
1263 return getTheme().getColor("twindow.background.modal.inactive");
1264 } else if (isActive()) {
1265 assert (!isModal());
1266 return getTheme().getColor("twindow.background");
1267 } else {
1268 assert (!isModal());
1269 return getTheme().getColor("twindow.background.inactive");
1270 }
1271 }
1272
1273 /**
1274 * Retrieve the border color.
1275 *
1276 * @return the border color
1277 */
1278 public CellAttributes getBorder() {
1279 if (!isModal()
1280 && (inWindowMove || inWindowResize || inKeyboardResize)
1281 ) {
a69ed767
KL
1282 if (!isActive()) {
1283 // The user's terminal never passed a mouse up event, and now
1284 // another window is active but we never finished a drag.
1285 inWindowMove = false;
1286 inWindowResize = false;
1287 inKeyboardResize = false;
1288 return getTheme().getColor("twindow.border.inactive");
1289 }
1290
d36057df
KL
1291 return getTheme().getColor("twindow.border.windowmove");
1292 } else if (isModal() && inWindowMove) {
1293 assert (isActive());
1294 return getTheme().getColor("twindow.border.modal.windowmove");
1295 } else if (isModal()) {
1296 if (isActive()) {
1297 return getTheme().getColor("twindow.border.modal");
1298 } else {
1299 return getTheme().getColor("twindow.border.modal.inactive");
1300 }
1301 } else if (isActive()) {
1302 assert (!isModal());
1303 return getTheme().getColor("twindow.border");
1304 } else {
1305 assert (!isModal());
1306 return getTheme().getColor("twindow.border.inactive");
1307 }
1308 }
1309
1310 /**
1311 * Retrieve the color used by the window movement/sizing controls.
1312 *
1313 * @return the color used by the zoom box, resize bar, and close box
1314 */
1315 public CellAttributes getBorderControls() {
1316 if (isModal()) {
1317 return getTheme().getColor("twindow.border.modal.windowmove");
1318 }
1319 return getTheme().getColor("twindow.border.windowmove");
1320 }
1321
1322 /**
1323 * Retrieve the border line type.
1324 *
1325 * @return the border line type
1326 */
1327 private int getBorderType() {
1328 if (!isModal()
1329 && (inWindowMove || inWindowResize || inKeyboardResize)
1330 ) {
1331 assert (isActive());
1332 return 1;
1333 } else if (isModal() && inWindowMove) {
1334 assert (isActive());
1335 return 1;
1336 } else if (isModal()) {
1337 if (isActive()) {
1338 return 2;
1339 } else {
1340 return 1;
1341 }
1342 } else if (isActive()) {
1343 return 2;
1344 } else {
1345 return 1;
1346 }
1347 }
1348
48e27807 1349}