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