2 * Jexer - Java Text User Interface
4 * License: LGPLv3 or later
6 * This module is licensed under the GNU Lesser General Public License
7 * Version 3. Please see the file "COPYING" in this directory for more
8 * information about the GNU Lesser General Public License Version 3.
10 * Copyright (C) 2015 Kevin Lamonte
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Lesser General Public License
14 * as published by the Free Software Foundation; either version 3 of
15 * the License, or (at your option) any later version.
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with this program; if not, see
24 * http://www.gnu.org/licenses/, or write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
28 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
33 import jexer
.bits
.Cell
;
34 import jexer
.bits
.CellAttributes
;
35 import jexer
.bits
.GraphicsChars
;
36 import jexer
.event
.TCommandEvent
;
37 import jexer
.event
.TKeypressEvent
;
38 import jexer
.event
.TMenuEvent
;
39 import jexer
.event
.TMouseEvent
;
40 import jexer
.event
.TResizeEvent
;
41 import jexer
.io
.Screen
;
42 import static jexer
.TCommand
.*;
43 import static jexer
.TKeypress
.*;
46 * TWindow is the top-level container and drawing surface for other widgets.
48 public class TWindow
extends TWidget
{
51 * Window's parent TApplication.
53 protected TApplication application
;
56 * Get this TWindow's parent TApplication.
58 * @return this TWindow's parent TApplication
60 public final TApplication
getApplication() {
69 public final Screen
getScreen() {
70 return application
.getScreen();
76 protected String title
= "";
79 * Window is resizable (default yes).
81 public static final int RESIZABLE
= 0x01;
84 * Window is modal (default no).
86 public static final int MODAL
= 0x02;
89 * Window is centered (default no).
91 public static final int CENTERED
= 0x04;
96 private int flags
= RESIZABLE
;
99 * Z order. Lower number means more in-front.
104 * If true, then the user clicked on the title bar and is moving the
107 private boolean inWindowMove
= false;
110 * If true, then the user clicked on the bottom right corner and is
111 * resizing the window.
113 private boolean inWindowResize
= false;
116 * If true, then the user selected "Size/Move" (or hit Ctrl-F5) and is
117 * resizing/moving the window via the keyboard.
119 private boolean inKeyboardResize
= false;
122 * If true, this window is maximized.
124 private boolean maximized
= false;
127 * Remember mouse state.
129 protected TMouseEvent mouse
;
131 // For moving the window. resizing also uses moveWindowMouseX/Y
132 private int moveWindowMouseX
;
133 private int moveWindowMouseY
;
134 private int oldWindowX
;
135 private int oldWindowY
;
138 private int resizeWindowWidth
;
139 private int resizeWindowHeight
;
140 private int minimumWindowWidth
= 10;
141 private int minimumWindowHeight
= 2;
142 private int maximumWindowWidth
= -1;
143 private int maximumWindowHeight
= -1;
145 // For maximize/restore
146 private int restoreWindowWidth
;
147 private int restoreWindowHeight
;
148 private int restoreWindowX
;
149 private int restoreWindowY
;
152 * Public constructor. Window will be located at (0, 0).
154 * @param application TApplication that manages this window
155 * @param title window title, will be centered along the top border
156 * @param width width of window
157 * @param height height of window
159 public TWindow(final TApplication application
, final String title
,
160 final int width
, final int height
) {
162 this(application
, title
, 0, 0, width
, height
, RESIZABLE
);
166 * Public constructor. Window will be located at (0, 0).
168 * @param application TApplication that manages this window
169 * @param title window title, will be centered along the top border
170 * @param width width of window
171 * @param height height of window
172 * @param flags bitmask of RESIZABLE, CENTERED, or MODAL
174 public TWindow(final TApplication application
, final String title
,
175 final int width
, final int height
, final int flags
) {
177 this(application
, title
, 0, 0, width
, height
, flags
);
181 * Public constructor.
183 * @param application TApplication that manages this window
184 * @param title window title, will be centered along the top border
185 * @param x column relative to parent
186 * @param y row relative to parent
187 * @param width width of window
188 * @param height height of window
190 public TWindow(final TApplication application
, final String title
,
191 final int x
, final int y
, final int width
, final int height
) {
193 this(application
, title
, x
, y
, width
, height
, RESIZABLE
);
197 * Public constructor.
199 * @param application TApplication that manages this window
200 * @param title window title, will be centered along the top border
201 * @param x column relative to parent
202 * @param y row relative to parent
203 * @param width width of window
204 * @param height height of window
205 * @param flags mask of RESIZABLE, CENTERED, or MODAL
207 public TWindow(final TApplication application
, final String title
,
208 final int x
, final int y
, final int width
, final int height
,
211 // I am my own window and parent
217 this.application
= application
;
219 this.y
= y
+ application
.getDesktopTop();
221 this.height
= height
;
224 // Minimum width/height are 10 and 2
225 assert (width
>= 10);
226 assert (height
>= 2);
228 // MODAL implies CENTERED
230 this.flags
|= CENTERED
;
233 // Center window if specified
236 // Add me to the application
237 application
.addWindow(this);
241 * Recenter the window on-screen.
243 public final void center() {
244 if ((flags
& CENTERED
) != 0) {
245 if (width
< getScreen().getWidth()) {
246 x
= (getScreen().getWidth() - width
) / 2;
250 y
= (application
.getDesktopBottom() - application
.getDesktopTop());
256 y
+= application
.getDesktopTop();
261 * Returns true if this window is modal.
263 * @return true if this window is modal
265 public final boolean isModal() {
266 if ((flags
& MODAL
) == 0) {
273 * Comparison operator sorts on z.
275 * @param that another TWindow instance
276 * @return difference between this.z and that.z
278 public final int compare(final TWindow that
) {
283 * Returns true if the mouse is currently on the close button.
285 * @return true if mouse is currently on the close button
287 private boolean mouseOnClose() {
289 && (mouse
.getAbsoluteY() == y
)
290 && (mouse
.getAbsoluteX() == x
+ 3)
298 * Returns true if the mouse is currently on the maximize/restore button.
300 * @return true if the mouse is currently on the maximize/restore button
302 private boolean mouseOnMaximize() {
305 && (mouse
.getAbsoluteY() == y
)
306 && (mouse
.getAbsoluteX() == x
+ width
- 4)
314 * Returns true if the mouse is currently on the resizable lower right
317 * @return true if the mouse is currently on the resizable lower right
320 private boolean mouseOnResize() {
321 if (((flags
& RESIZABLE
) != 0)
324 && (mouse
.getAbsoluteY() == y
+ height
- 1)
325 && ((mouse
.getAbsoluteX() == x
+ width
- 1)
326 || (mouse
.getAbsoluteX() == x
+ width
- 2))
334 * Retrieve the background color.
336 * @return the background color
338 protected final CellAttributes
getBackground() {
340 && (inWindowMove
|| inWindowResize
|| inKeyboardResize
)
343 return application
.getTheme().getColor("twindow.background.windowmove");
344 } else if (isModal() && inWindowMove
) {
346 return application
.getTheme().getColor("twindow.background.modal");
347 } else if (isModal()) {
349 return application
.getTheme().getColor("twindow.background.modal");
351 return application
.getTheme().getColor("twindow.background.modal.inactive");
354 return application
.getTheme().getColor("twindow.background");
357 return application
.getTheme().getColor("twindow.background.inactive");
362 * Retrieve the border color.
364 * @return the border color
366 protected final CellAttributes
getBorder() {
368 && (inWindowMove
|| inWindowResize
|| inKeyboardResize
)
371 return application
.getTheme().getColor("twindow.border.windowmove");
372 } else if (isModal() && inWindowMove
) {
374 return application
.getTheme().getColor("twindow.border.modal.windowmove");
375 } else if (isModal()) {
377 return application
.getTheme().getColor("twindow.border.modal");
379 return application
.getTheme().getColor("twindow.border.modal.inactive");
383 return application
.getTheme().getColor("twindow.border");
386 return application
.getTheme().getColor("twindow.border.inactive");
391 * Retrieve the border line type.
393 * @return the border line type
395 protected final int getBorderType() {
397 && (inWindowMove
|| inWindowResize
|| inKeyboardResize
)
401 } else if (isModal() && inWindowMove
) {
404 } else if (isModal()) {
418 * Subclasses should override this method to cleanup resources. This is
419 * called by application.closeWindow().
421 public void onClose() {
422 // Default: do nothing
426 * Called by TApplication.drawChildren() to render on screen.
430 // Draw the box and background first.
431 CellAttributes border
= getBorder();
432 CellAttributes background
= getBackground();
433 int borderType
= getBorderType();
435 getScreen().drawBox(0, 0, width
, height
, border
,
436 background
, borderType
, true);
439 int titleLeft
= (width
- title
.length() - 2) / 2;
440 putCharXY(titleLeft
, 0, ' ', border
);
441 putStrXY(titleLeft
+ 1, 0, title
);
442 putCharXY(titleLeft
+ title
.length() + 1, 0, ' ', border
);
446 // Draw the close button
447 putCharXY(2, 0, '[', border
);
448 putCharXY(4, 0, ']', border
);
449 if (mouseOnClose() && mouse
.getMouse1()) {
450 putCharXY(3, 0, GraphicsChars
.CP437
[0x0F],
452 ? application
.getTheme().getColor("twindow.border.windowmove")
453 : application
.getTheme().getColor("twindow.border.modal.windowmove"));
455 putCharXY(3, 0, GraphicsChars
.CP437
[0xFE],
457 ? application
.getTheme().getColor("twindow.border.windowmove")
458 : application
.getTheme().getColor("twindow.border.modal.windowmove"));
461 // Draw the maximize button
464 putCharXY(width
- 5, 0, '[', border
);
465 putCharXY(width
- 3, 0, ']', border
);
466 if (mouseOnMaximize() && mouse
.getMouse1()) {
467 putCharXY(width
- 4, 0, GraphicsChars
.CP437
[0x0F],
468 application
.getTheme().getColor("twindow.border.windowmove"));
471 putCharXY(width
- 4, 0, GraphicsChars
.CP437
[0x12],
472 application
.getTheme().getColor("twindow.border.windowmove"));
474 putCharXY(width
- 4, 0, GraphicsChars
.UPARROW
,
475 application
.getTheme().getColor("twindow.border.windowmove"));
479 // Draw the resize corner
480 if ((flags
& RESIZABLE
) != 0) {
481 putCharXY(width
- 2, height
- 1, GraphicsChars
.SINGLE_BAR
,
482 application
.getTheme().getColor("twindow.border.windowmove"));
483 putCharXY(width
- 1, height
- 1, GraphicsChars
.LRCORNER
,
484 application
.getTheme().getColor("twindow.border.windowmove"));
491 * Handle mouse button presses.
493 * @param mouse mouse button event
496 public void onMouseDown(final TMouseEvent mouse
) {
498 application
.setRepaint();
500 inKeyboardResize
= false;
502 if ((mouse
.getAbsoluteY() == y
)
504 && (x
<= mouse
.getAbsoluteX())
505 && (mouse
.getAbsoluteX() < x
+ width
)
507 && !mouseOnMaximize()
509 // Begin moving window
511 moveWindowMouseX
= mouse
.getAbsoluteX();
512 moveWindowMouseY
= mouse
.getAbsoluteY();
520 if (mouseOnResize()) {
521 // Begin window resize
522 inWindowResize
= true;
523 moveWindowMouseX
= mouse
.getAbsoluteX();
524 moveWindowMouseY
= mouse
.getAbsoluteY();
525 resizeWindowWidth
= width
;
526 resizeWindowHeight
= height
;
533 // I didn't take it, pass it on to my children
534 super.onMouseDown(mouse
);
540 private void maximize() {
541 restoreWindowWidth
= width
;
542 restoreWindowHeight
= height
;
545 width
= getScreen().getWidth();
546 height
= application
.getDesktopBottom() - 1;
553 * Restote (unmaximize) window.
555 private void restore() {
556 width
= restoreWindowWidth
;
557 height
= restoreWindowHeight
;
564 * Handle mouse button releases.
566 * @param mouse mouse button release event
569 public void onMouseUp(final TMouseEvent mouse
) {
571 application
.setRepaint();
573 if ((inWindowMove
) && (mouse
.getMouse1())) {
574 // Stop moving window
575 inWindowMove
= false;
579 if ((inWindowResize
) && (mouse
.getMouse1())) {
580 // Stop resizing window
581 inWindowResize
= false;
585 if (mouse
.getMouse1() && mouseOnClose()) {
587 application
.closeWindow(this);
591 if ((mouse
.getAbsoluteY() == y
) && mouse
.getMouse1()
592 && mouseOnMaximize()) {
600 // Pass a resize event to my children
601 onResize(new TResizeEvent(TResizeEvent
.Type
.WIDGET
, width
, height
));
605 // I didn't take it, pass it on to my children
606 super.onMouseUp(mouse
);
610 * Handle mouse movements.
612 * @param mouse mouse motion event
615 public void onMouseMotion(final TMouseEvent mouse
) {
617 application
.setRepaint();
621 x
= oldWindowX
+ (mouse
.getAbsoluteX() - moveWindowMouseX
);
622 y
= oldWindowY
+ (mouse
.getAbsoluteY() - moveWindowMouseY
);
623 // Don't cover up the menu bar
624 if (y
< application
.getDesktopTop()) {
625 y
= application
.getDesktopTop();
630 if (inWindowResize
) {
632 width
= resizeWindowWidth
+ (mouse
.getAbsoluteX() - moveWindowMouseX
);
633 height
= resizeWindowHeight
+ (mouse
.getAbsoluteY() - moveWindowMouseY
);
634 if (x
+ width
> getScreen().getWidth()) {
635 width
= getScreen().getWidth() - x
;
637 if (y
+ height
> application
.getDesktopBottom()) {
638 y
= application
.getDesktopBottom() - height
+ 1;
640 // Don't cover up the menu bar
641 if (y
< application
.getDesktopTop()) {
642 y
= application
.getDesktopTop();
645 // Keep within min/max bounds
646 if (width
< minimumWindowWidth
) {
647 width
= minimumWindowWidth
;
648 inWindowResize
= false;
650 if (height
< minimumWindowHeight
) {
651 height
= minimumWindowHeight
;
652 inWindowResize
= false;
654 if ((maximumWindowWidth
> 0) && (width
> maximumWindowWidth
)) {
655 width
= maximumWindowWidth
;
656 inWindowResize
= false;
658 if ((maximumWindowHeight
> 0) && (height
> maximumWindowHeight
)) {
659 height
= maximumWindowHeight
;
660 inWindowResize
= false;
663 // Pass a resize event to my children
664 onResize(new TResizeEvent(TResizeEvent
.Type
.WIDGET
, width
, height
));
668 // I didn't take it, pass it on to my children
669 super.onMouseMotion(mouse
);
675 * @param keypress keystroke event
678 public void onKeypress(final TKeypressEvent keypress
) {
680 if (inKeyboardResize
) {
682 // ESC - Exit size/move
683 if (keypress
.equals(kbEsc
)) {
684 inKeyboardResize
= false;
687 if (keypress
.equals(kbLeft
)) {
692 if (keypress
.equals(kbRight
)) {
693 if (x
< getScreen().getWidth() - 1) {
697 if (keypress
.equals(kbDown
)) {
698 if (y
< application
.getDesktopBottom() - 1) {
702 if (keypress
.equals(kbUp
)) {
707 if (keypress
.equals(kbShiftLeft
)) {
708 if (width
> minimumWindowWidth
) {
712 if (keypress
.equals(kbShiftRight
)) {
713 if (width
< maximumWindowWidth
) {
717 if (keypress
.equals(kbShiftUp
)) {
718 if (height
> minimumWindowHeight
) {
722 if (keypress
.equals(kbShiftDown
)) {
723 if (height
< maximumWindowHeight
) {
731 // These keystrokes will typically not be seen unless a subclass
732 // overrides onMenu() due to how TApplication dispatches
735 // Ctrl-W - close window
736 if (keypress
.equals(kbCtrlW
)) {
737 application
.closeWindow(this);
741 // F6 - behave like Alt-TAB
742 if (keypress
.equals(kbF6
)) {
743 application
.switchWindow(true);
747 // Shift-F6 - behave like Shift-Alt-TAB
748 if (keypress
.equals(kbShiftF6
)) {
749 application
.switchWindow(false);
754 if (keypress
.equals(kbF5
)) {
762 // Ctrl-F5 - size/move
763 if (keypress
.equals(kbCtrlF5
)) {
764 inKeyboardResize
= !inKeyboardResize
;
767 // I didn't take it, pass it on to my children
768 super.onKeypress(keypress
);
772 * Handle posted command events.
774 * @param command command event
777 public void onCommand(final TCommandEvent command
) {
779 // These commands will typically not be seen unless a subclass
780 // overrides onMenu() due to how TApplication dispatches
783 if (command
.equals(cmWindowClose
)) {
784 application
.closeWindow(this);
788 if (command
.equals(cmWindowNext
)) {
789 application
.switchWindow(true);
793 if (command
.equals(cmWindowPrevious
)) {
794 application
.switchWindow(false);
798 if (command
.equals(cmWindowMove
)) {
799 inKeyboardResize
= true;
803 if (command
.equals(cmWindowZoom
)) {
811 // I didn't take it, pass it on to my children
812 super.onCommand(command
);
816 * Handle posted menu events.
818 * @param menu menu event
821 public void onMenu(final TMenuEvent menu
) {
822 if (menu
.getId() == TMenu
.MID_WINDOW_CLOSE
) {
823 application
.closeWindow(this);
827 if (menu
.getId() == TMenu
.MID_WINDOW_NEXT
) {
828 application
.switchWindow(true);
832 if (menu
.getId() == TMenu
.MID_WINDOW_PREVIOUS
) {
833 application
.switchWindow(false);
837 if (menu
.getId() == TMenu
.MID_WINDOW_MOVE
) {
838 inKeyboardResize
= true;
842 if (menu
.getId() == TMenu
.MID_WINDOW_ZOOM
) {
851 // I didn't take it, pass it on to my children
855 // ------------------------------------------------------------------------
856 // Passthru for Screen functions ------------------------------------------
857 // ------------------------------------------------------------------------
860 * Get the attributes at one location.
862 * @param x column coordinate. 0 is the left-most column.
863 * @param y row coordinate. 0 is the top-most row.
864 * @return attributes at (x, y)
866 public final CellAttributes
getAttrXY(final int x
, final int y
) {
867 return getScreen().getAttrXY(x
, y
);
871 * Set the attributes at one location.
873 * @param x column coordinate. 0 is the left-most column.
874 * @param y row coordinate. 0 is the top-most row.
875 * @param attr attributes to use (bold, foreColor, backColor)
877 public final void putAttrXY(final int x
, final int y
,
878 final CellAttributes attr
) {
880 getScreen().putAttrXY(x
, y
, attr
);
884 * Set the attributes at one location.
886 * @param x column coordinate. 0 is the left-most column.
887 * @param y row coordinate. 0 is the top-most row.
888 * @param attr attributes to use (bold, foreColor, backColor)
889 * @param clip if true, honor clipping/offset
891 public final void putAttrXY(final int x
, final int y
,
892 final CellAttributes attr
, final boolean clip
) {
894 getScreen().putAttrXY(x
, y
, attr
, clip
);
898 * Fill the entire screen with one character with attributes.
900 * @param ch character to draw
901 * @param attr attributes to use (bold, foreColor, backColor)
903 public final void putAll(final char ch
, final CellAttributes attr
) {
904 getScreen().putAll(ch
, attr
);
908 * Render one character with attributes.
910 * @param x column coordinate. 0 is the left-most column.
911 * @param y row coordinate. 0 is the top-most row.
912 * @param ch character + attributes to draw
914 public final void putCharXY(final int x
, final int y
, final Cell ch
) {
915 getScreen().putCharXY(x
, y
, ch
);
919 * Render one character with attributes.
921 * @param x column coordinate. 0 is the left-most column.
922 * @param y row coordinate. 0 is the top-most row.
923 * @param ch character to draw
924 * @param attr attributes to use (bold, foreColor, backColor)
926 public final void putCharXY(final int x
, final int y
, final char ch
,
927 final CellAttributes attr
) {
929 getScreen().putCharXY(x
, y
, ch
, attr
);
933 * Render one character without changing the underlying attributes.
935 * @param x column coordinate. 0 is the left-most column.
936 * @param y row coordinate. 0 is the top-most row.
937 * @param ch character to draw
939 public final void putCharXY(final int x
, final int y
, final char ch
) {
940 getScreen().putCharXY(x
, y
, ch
);
944 * Render a string. Does not wrap if the string exceeds the line.
946 * @param x column coordinate. 0 is the left-most column.
947 * @param y row coordinate. 0 is the top-most row.
948 * @param str string to draw
949 * @param attr attributes to use (bold, foreColor, backColor)
951 public final void putStrXY(final int x
, final int y
, final String str
,
952 final CellAttributes attr
) {
954 getScreen().putStrXY(x
, y
, str
, attr
);
958 * Render a string without changing the underlying attribute. Does not
959 * wrap if the string exceeds the line.
961 * @param x column coordinate. 0 is the left-most column.
962 * @param y row coordinate. 0 is the top-most row.
963 * @param str string to draw
965 public final void putStrXY(final int x
, final int y
, final String str
) {
966 getScreen().putStrXY(x
, y
, str
);
970 * Draw a vertical line from (x, y) to (x, y + n).
972 * @param x column coordinate. 0 is the left-most column.
973 * @param y row coordinate. 0 is the top-most row.
974 * @param n number of characters to draw
975 * @param ch character to draw
976 * @param attr attributes to use (bold, foreColor, backColor)
978 public final void vLineXY(final int x
, final int y
, final int n
,
979 final char ch
, final CellAttributes attr
) {
981 getScreen().vLineXY(x
, y
, n
, ch
, attr
);
985 * Draw a horizontal line from (x, y) to (x + n, y).
987 * @param x column coordinate. 0 is the left-most column.
988 * @param y row coordinate. 0 is the top-most row.
989 * @param n number of characters to draw
990 * @param ch character to draw
991 * @param attr attributes to use (bold, foreColor, backColor)
993 public final void hLineXY(final int x
, final int y
, final int n
,
994 final char ch
, final CellAttributes attr
) {
996 getScreen().hLineXY(x
, y
, n
, ch
, attr
);