PMD code sweep, #6 don't add MyWindow twice to MyApplication
[fanfix.git] / src / jexer / TWindow.java
index 19c96fd141d2550209a9ca8fb14ae53bcbfbc378..140a38aa263f1c2138aed7ff558ada47df9507e1 100644 (file)
@@ -29,6 +29,7 @@
 package jexer;
 
 import java.util.HashSet;
+import java.util.Set;
 
 import jexer.backend.Screen;
 import jexer.bits.Cell;
@@ -49,7 +50,7 @@ import static jexer.TKeypress.*;
 public class TWindow extends TWidget {
 
     // ------------------------------------------------------------------------
-    // Public constants -------------------------------------------------------
+    // Constants --------------------------------------------------------------
     // ------------------------------------------------------------------------
 
     /**
@@ -78,162 +79,48 @@ public class TWindow extends TWidget {
      */
     public static final int NOZOOMBOX   = 0x10;
 
-    // ------------------------------------------------------------------------
-    // Common window attributes -----------------------------------------------
-    // ------------------------------------------------------------------------
-
-    /**
-     * Window flags.  Note package private access.
-     */
-    int flags = RESIZABLE;
-
     /**
-     * Window title.
-     */
-    private String title = "";
-
-    /**
-     * Get window title.
-     *
-     * @return window title
+     * Window is placed at absolute position (no smart placement) (default
+     * no).
      */
-    public final String getTitle() {
-        return title;
-    }
+    public static final int ABSOLUTEXY  = 0x20;
 
     /**
-     * Set window title.
-     *
-     * @param title new window title
+     * Hitting the closebox with the mouse calls TApplication.hideWindow()
+     * rather than TApplication.closeWindow() (default no).
      */
-    public final void setTitle(final String title) {
-        this.title = title;
-    }
+    public static final int HIDEONCLOSE = 0x40;
 
     // ------------------------------------------------------------------------
-    // TApplication integration -----------------------------------------------
+    // Variables --------------------------------------------------------------
     // ------------------------------------------------------------------------
 
     /**
-     * Window's parent TApplication.
+     * Window flags.  Note package private access.
      */
-    private TApplication application;
+    int flags = RESIZABLE;
 
     /**
-     * Get this TWindow's parent TApplication.
-     *
-     * @return this TWindow's parent TApplication
+     * Window title.
      */
-    @Override
-    public final TApplication getApplication() {
-        return application;
-    }
+    private String title = "";
 
     /**
-     * Get the Screen.
-     *
-     * @return the Screen
+     * Window's parent TApplication.
      */
-    @Override
-    public final Screen getScreen() {
-        return application.getScreen();
-    }
+    private TApplication application;
 
     /**
      * Z order.  Lower number means more in-front.
      */
     private int z = 0;
 
-    /**
-     * Get Z order.  Lower number means more in-front.
-     *
-     * @return Z value.  Lower number means more in-front.
-     */
-    public final int getZ() {
-        return z;
-    }
-
-    /**
-     * Set Z order.  Lower number means more in-front.
-     *
-     * @param z the new Z value.  Lower number means more in-front.
-     */
-    public final void setZ(final int z) {
-        this.z = z;
-    }
-
     /**
      * Window's keyboard shortcuts.  Any key in this set will be passed to
      * the window directly rather than processed through the menu
      * accelerators.
      */
-    private HashSet<TKeypress> keyboardShortcuts = new HashSet<TKeypress>();
-
-    /**
-     * Add a keypress to be overridden for this window.
-     *
-     * @param key the key to start taking control of
-     */
-    protected void addShortcutKeypress(final TKeypress key) {
-        keyboardShortcuts.add(key);
-    }
-
-    /**
-     * Remove a keypress to be overridden for this window.
-     *
-     * @param key the key to stop taking control of
-     */
-    protected void removeShortcutKeypress(final TKeypress key) {
-        keyboardShortcuts.remove(key);
-    }
-
-    /**
-     * Remove all keypresses to be overridden for this window.
-     */
-    protected void clearShortcutKeypresses() {
-        keyboardShortcuts.clear();
-    }
-
-    /**
-     * Determine if a keypress is overridden for this window.
-     *
-     * @param key the key to check
-     * @return true if this window wants to process this key on its own
-     */
-    public boolean isShortcutKeypress(final TKeypress key) {
-        return keyboardShortcuts.contains(key);
-    }
-
-    /**
-     * A window may have a status bar associated with it.  TApplication will
-     * draw this status bar last, and will also route events to it first
-     * before the window.
-     */
-    protected TStatusBar statusBar = null;
-
-    /**
-     * Get the window's status bar, or null if it does not have one.
-     *
-     * @return the status bar, or null
-     */
-    public TStatusBar getStatusBar() {
-        return statusBar;
-    }
-
-    /**
-     * Set the window's status bar to a new one.
-     *
-     * @param text the status bar text
-     * @return the status bar
-     */
-    public TStatusBar newStatusBar(final String text) {
-        statusBar = new TStatusBar(this, text);
-        return statusBar;
-    }
-
-    // ------------------------------------------------------------------------
-    // Window movement/resizing support ---------------------------------------
-    // ------------------------------------------------------------------------
+    private Set<TKeypress> keyboardShortcuts = new HashSet<TKeypress>();
 
     /**
      * If true, then the user clicked on the title bar and is moving the
@@ -251,7 +138,7 @@ public class TWindow extends TWidget {
      * If true, then the user selected "Size/Move" (or hit Ctrl-F5) and is
      * resizing/moving the window via the keyboard.
      */
-    private boolean inKeyboardResize = false;
+    protected boolean inKeyboardResize = false;
 
     /**
      * If true, this window is maximized.
@@ -284,226 +171,67 @@ public class TWindow extends TWidget {
     private int restoreWindowY;
 
     /**
-     * Set the maximum width for this window.
-     *
-     * @param maximumWindowWidth new maximum width
+     * Hidden flag.  A hidden window will still have its onIdle() called, and
+     * will also have onClose() called at application exit.  Note package
+     * private access: TApplication will force hidden false if a modal window
+     * is active.
      */
-    public final void setMaximumWindowWidth(final int maximumWindowWidth) {
-        if ((maximumWindowWidth != -1)
-            && (maximumWindowWidth < minimumWindowWidth + 1)
-        ) {
-            throw new IllegalArgumentException("Maximum window width cannot " +
-                "be smaller than minimum window width + 1");
-        }
-        this.maximumWindowWidth = maximumWindowWidth;
-    }
+    boolean hidden = false;
 
     /**
-     * Set the minimum width for this window.
-     *
-     * @param minimumWindowWidth new minimum width
+     * A window may have a status bar associated with it.  TApplication will
+     * draw this status bar last, and will also route events to it first
+     * before the window.
      */
-    public final void setMinimumWindowWidth(final int minimumWindowWidth) {
-        if ((maximumWindowWidth != -1)
-            && (minimumWindowWidth > maximumWindowWidth - 1)
-        ) {
-            throw new IllegalArgumentException("Minimum window width cannot " +
-                "be larger than maximum window width - 1");
-        }
-        this.minimumWindowWidth = minimumWindowWidth;
-    }
+    protected TStatusBar statusBar = null;
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
-     * Set the maximum height for this window.
+     * Public constructor.  Window will be located at (0, 0).
      *
-     * @param maximumWindowHeight new maximum height
+     * @param application TApplication that manages this window
+     * @param title window title, will be centered along the top border
+     * @param width width of window
+     * @param height height of window
      */
-    public final void setMaximumWindowHeight(final int maximumWindowHeight) {
-        if ((maximumWindowHeight != -1)
-            && (maximumWindowHeight < minimumWindowHeight + 1)
-        ) {
-            throw new IllegalArgumentException("Maximum window height cannot " +
-                "be smaller than minimum window height + 1");
-        }
-        this.maximumWindowHeight = maximumWindowHeight;
+    public TWindow(final TApplication application, final String title,
+        final int width, final int height) {
+
+        this(application, title, 0, 0, width, height, RESIZABLE);
     }
 
     /**
-     * Set the minimum height for this window.
+     * Public constructor.  Window will be located at (0, 0).
      *
-     * @param minimumWindowHeight new minimum height
+     * @param application TApplication that manages this window
+     * @param title window title, will be centered along the top border
+     * @param width width of window
+     * @param height height of window
+     * @param flags bitmask of RESIZABLE, CENTERED, or MODAL
      */
-    public final void setMinimumWindowHeight(final int minimumWindowHeight) {
-        if ((maximumWindowHeight != -1)
-            && (minimumWindowHeight > maximumWindowHeight - 1)
-        ) {
-            throw new IllegalArgumentException("Minimum window height cannot " +
-                "be larger than maximum window height - 1");
-        }
-        this.minimumWindowHeight = minimumWindowHeight;
-    }
+    public TWindow(final TApplication application, final String title,
+        final int width, final int height, final int flags) {
 
-    /**
-     * Recenter the window on-screen.
-     */
-    public final void center() {
-        if ((flags & CENTERED) != 0) {
-            if (getWidth() < getScreen().getWidth()) {
-                setX((getScreen().getWidth() - getWidth()) / 2);
-            } else {
-                setX(0);
-            }
-            setY(((application.getDesktopBottom()
-                    - application.getDesktopTop()) - getHeight()) / 2);
-            if (getY() < 0) {
-                setY(0);
-            }
-            setY(getY() + application.getDesktopTop());
-        }
+        this(application, title, 0, 0, width, height, flags);
     }
 
     /**
-     * Maximize window.
+     * Public constructor.
+     *
+     * @param application TApplication that manages this window
+     * @param title window title, will be centered along the top border
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param width width of window
+     * @param height height of window
      */
-    public void maximize() {
-        if (maximized) {
-            return;
-        }
+    public TWindow(final TApplication application, final String title,
+        final int x, final int y, final int width, final int height) {
 
-        restoreWindowWidth = getWidth();
-        restoreWindowHeight = getHeight();
-        restoreWindowX = getX();
-        restoreWindowY = getY();
-        setWidth(getScreen().getWidth());
-        setHeight(application.getDesktopBottom() - 1);
-        setX(0);
-        setY(1);
-        maximized = true;
-    }
-
-    /**
-     * Restore (unmaximize) window.
-     */
-    public void restore() {
-        if (!maximized) {
-            return;
-        }
-
-        setWidth(restoreWindowWidth);
-        setHeight(restoreWindowHeight);
-        setX(restoreWindowX);
-        setY(restoreWindowY);
-        maximized = false;
-    }
-
-    // ------------------------------------------------------------------------
-    // Window visibility ------------------------------------------------------
-    // ------------------------------------------------------------------------
-
-    /**
-     * Hidden flag.  A hidden window will still have its onIdle() called, and
-     * will also have onClose() called at application exit.  Note package
-     * private access: TApplication will force hidden false if a modal window
-     * is active.
-     */
-    boolean hidden = false;
-
-    /**
-     * Returns true if this window is hidden.
-     *
-     * @return true if this window is hidden, false if the window is shown
-     */
-    public final boolean isHidden() {
-        return hidden;
-    }
-
-    /**
-     * Returns true if this window is shown.
-     *
-     * @return true if this window is shown, false if the window is hidden
-     */
-    public final boolean isShown() {
-        return !hidden;
-    }
-
-    /**
-     * Hide window.  A hidden window will still have its onIdle() called, and
-     * will also have onClose() called at application exit.  Hidden windows
-     * will not receive any other events.
-     */
-    public void hide() {
-        application.hideWindow(this);
-    }
-
-    /**
-     * Show window.
-     */
-    public void show() {
-        application.showWindow(this);
-    }
-
-    /**
-     * Activate window (bring to top and receive events).
-     */
-    public void activate() {
-        application.activateWindow(this);
-    }
-
-    /**
-     * Close window.  Note that windows without a close box can still be
-     * closed by calling the close() method.
-     */
-    public void close() {
-        application.closeWindow(this);
-    }
-
-    // ------------------------------------------------------------------------
-    // Constructors -----------------------------------------------------------
-    // ------------------------------------------------------------------------
-
-    /**
-     * Public constructor.  Window will be located at (0, 0).
-     *
-     * @param application TApplication that manages this window
-     * @param title window title, will be centered along the top border
-     * @param width width of window
-     * @param height height of window
-     */
-    public TWindow(final TApplication application, final String title,
-        final int width, final int height) {
-
-        this(application, title, 0, 0, width, height, RESIZABLE);
-    }
-
-    /**
-     * Public constructor.  Window will be located at (0, 0).
-     *
-     * @param application TApplication that manages this window
-     * @param title window title, will be centered along the top border
-     * @param width width of window
-     * @param height height of window
-     * @param flags bitmask of RESIZABLE, CENTERED, or MODAL
-     */
-    public TWindow(final TApplication application, final String title,
-        final int width, final int height, final int flags) {
-
-        this(application, title, 0, 0, width, height, flags);
-    }
-
-    /**
-     * Public constructor.
-     *
-     * @param application TApplication that manages this window
-     * @param title window title, will be centered along the top border
-     * @param x column relative to parent
-     * @param y row relative to parent
-     * @param width width of window
-     * @param height height of window
-     */
-    public TWindow(final TApplication application, final String title,
-        final int x, final int y, final int width, final int height) {
-
-        this(application, title, x, y, width, height, RESIZABLE);
+        this(application, title, x, y, width, height, RESIZABLE);
     }
 
     /**
@@ -545,310 +273,96 @@ public class TWindow extends TWidget {
         center();
 
         // Add me to the application
-        application.addWindow(this);
+        application.addWindowToApplication(this);
     }
 
     // ------------------------------------------------------------------------
-    // General behavior -------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
     // ------------------------------------------------------------------------
 
     /**
-     * See if this window is undergoing any movement/resize/etc.
+     * Returns true if the mouse is currently on the close button.
      *
-     * @return true if the window is moving
+     * @return true if mouse is currently on the close button
      */
-    public boolean inMovements() {
-        if (inWindowResize || inWindowMove || inKeyboardResize) {
+    protected boolean mouseOnClose() {
+        if ((flags & NOCLOSEBOX) != 0) {
+            return false;
+        }
+        if ((mouse != null)
+            && (mouse.getAbsoluteY() == getY())
+            && (mouse.getAbsoluteX() == getX() + 3)
+        ) {
             return true;
         }
         return false;
     }
 
     /**
-     * Stop any pending movement/resize/etc.
-     */
-    public void stopMovements() {
-        inWindowResize = false;
-        inWindowMove = false;
-        inKeyboardResize = false;
-    }
-
-    /**
-     * Returns true if this window is modal.
+     * Returns true if the mouse is currently on the maximize/restore button.
      *
-     * @return true if this window is modal
+     * @return true if the mouse is currently on the maximize/restore button
      */
-    public final boolean isModal() {
-        if ((flags & MODAL) == 0) {
+    protected boolean mouseOnMaximize() {
+        if ((flags & NOZOOMBOX) != 0) {
             return false;
         }
-        return true;
+        if ((mouse != null)
+            && !isModal()
+            && (mouse.getAbsoluteY() == getY())
+            && (mouse.getAbsoluteX() == getX() + getWidth() - 4)
+        ) {
+            return true;
+        }
+        return false;
     }
 
     /**
-     * Returns true if this window has a close box.
+     * Returns true if the mouse is currently on the resizable lower right
+     * corner.
      *
-     * @return true if this window has a close box
+     * @return true if the mouse is currently on the resizable lower right
+     * corner
      */
-    public final boolean hasCloseBox() {
-        if ((flags & NOCLOSEBOX) != 0) {
+    protected boolean mouseOnResize() {
+        if (((flags & RESIZABLE) != 0)
+            && !isModal()
+            && (mouse != null)
+            && (mouse.getAbsoluteY() == getY() + getHeight() - 1)
+            && ((mouse.getAbsoluteX() == getX() + getWidth() - 1)
+                || (mouse.getAbsoluteX() == getX() + getWidth() - 2))
+        ) {
             return true;
         }
         return false;
     }
 
     /**
-     * Returns true if this window has a maximize/zoom box.
-     *
-     * @return true if this window has a maximize/zoom box
+     * Subclasses should override this method to cleanup resources.  This is
+     * called by application.closeWindow().
      */
-    public final boolean hasZoomBox() {
-        if ((flags & NOZOOMBOX) != 0) {
-            return true;
-        }
-        return false;
+    public void onClose() {
+        // Default: do nothing
     }
 
     /**
-     * Retrieve the background color.
-     *
-     * @return the background color
+     * Called by application.switchWindow() when this window gets the
+     * focus, and also by application.addWindow().
      */
-    public CellAttributes getBackground() {
-        if (!isModal()
-            && (inWindowMove || inWindowResize || inKeyboardResize)
-        ) {
-            assert (isActive());
-            return getTheme().getColor("twindow.background.windowmove");
-        } else if (isModal() && inWindowMove) {
-            assert (isActive());
-            return getTheme().getColor("twindow.background.modal");
-        } else if (isModal()) {
-            if (isActive()) {
-                return getTheme().getColor("twindow.background.modal");
-            }
-            return getTheme().getColor("twindow.background.modal.inactive");
-        } else if (isActive()) {
-            assert (!isModal());
-            return getTheme().getColor("twindow.background");
-        } else {
-            assert (!isModal());
-            return getTheme().getColor("twindow.background.inactive");
-        }
+    public void onFocus() {
+        // Default: do nothing
     }
 
     /**
-     * Retrieve the border color.
-     *
-     * @return the border color
+     * Called by application.switchWindow() when another window gets the
+     * focus.
      */
-    public CellAttributes getBorder() {
-        if (!isModal()
-            && (inWindowMove || inWindowResize || inKeyboardResize)
-        ) {
-            assert (isActive());
-            return getTheme().getColor("twindow.border.windowmove");
-        } else if (isModal() && inWindowMove) {
-            assert (isActive());
-            return getTheme().getColor("twindow.border.modal.windowmove");
-        } else if (isModal()) {
-            if (isActive()) {
-                return getTheme().getColor("twindow.border.modal");
-            } else {
-                return getTheme().getColor("twindow.border.modal.inactive");
-            }
-        } else if (isActive()) {
-            assert (!isModal());
-            return getTheme().getColor("twindow.border");
-        } else {
-            assert (!isModal());
-            return getTheme().getColor("twindow.border.inactive");
-        }
+    public void onUnfocus() {
+        // Default: do nothing
     }
 
     /**
-     * Retrieve the border line type.
-     *
-     * @return the border line type
-     */
-    private int getBorderType() {
-        if (!isModal()
-            && (inWindowMove || inWindowResize || inKeyboardResize)
-        ) {
-            assert (isActive());
-            return 1;
-        } else if (isModal() && inWindowMove) {
-            assert (isActive());
-            return 1;
-        } else if (isModal()) {
-            if (isActive()) {
-                return 2;
-            } else {
-                return 1;
-            }
-        } else if (isActive()) {
-            return 2;
-        } else {
-            return 1;
-        }
-    }
-
-    /**
-     * Called by TApplication.drawChildren() to render on screen.
-     */
-    @Override
-    public void draw() {
-        // Draw the box and background first.
-        CellAttributes border = getBorder();
-        CellAttributes background = getBackground();
-        int borderType = getBorderType();
-
-        getScreen().drawBox(0, 0, getWidth(), getHeight(), border,
-            background, borderType, true);
-
-        // Draw the title
-        int titleLeft = (getWidth() - title.length() - 2) / 2;
-        putCharXY(titleLeft, 0, ' ', border);
-        putStringXY(titleLeft + 1, 0, title);
-        putCharXY(titleLeft + title.length() + 1, 0, ' ', border);
-
-        if (isActive()) {
-
-            // Draw the close button
-            if ((flags & NOCLOSEBOX) == 0) {
-                putCharXY(2, 0, '[', border);
-                putCharXY(4, 0, ']', border);
-                if (mouseOnClose() && mouse.isMouse1()) {
-                    putCharXY(3, 0, GraphicsChars.CP437[0x0F],
-                        !isModal()
-                        ? getTheme().getColor("twindow.border.windowmove")
-                        : getTheme().getColor("twindow.border.modal.windowmove"));
-                } else {
-                    putCharXY(3, 0, GraphicsChars.CP437[0xFE],
-                        !isModal()
-                        ? getTheme().getColor("twindow.border.windowmove")
-                        : getTheme().getColor("twindow.border.modal.windowmove"));
-                }
-            }
-
-            // Draw the maximize button
-            if (!isModal() && ((flags & NOZOOMBOX) == 0)) {
-
-                putCharXY(getWidth() - 5, 0, '[', border);
-                putCharXY(getWidth() - 3, 0, ']', border);
-                if (mouseOnMaximize() && mouse.isMouse1()) {
-                    putCharXY(getWidth() - 4, 0, GraphicsChars.CP437[0x0F],
-                        getTheme().getColor("twindow.border.windowmove"));
-                } else {
-                    if (maximized) {
-                        putCharXY(getWidth() - 4, 0, GraphicsChars.CP437[0x12],
-                            getTheme().getColor("twindow.border.windowmove"));
-                    } else {
-                        putCharXY(getWidth() - 4, 0, GraphicsChars.UPARROW,
-                            getTheme().getColor("twindow.border.windowmove"));
-                    }
-                }
-
-                // Draw the resize corner
-                if ((flags & RESIZABLE) != 0) {
-                    putCharXY(getWidth() - 2, getHeight() - 1,
-                        GraphicsChars.SINGLE_BAR,
-                        getTheme().getColor("twindow.border.windowmove"));
-                    putCharXY(getWidth() - 1, getHeight() - 1,
-                        GraphicsChars.LRCORNER,
-                        getTheme().getColor("twindow.border.windowmove"));
-                }
-            }
-        }
-    }
-
-    // ------------------------------------------------------------------------
-    // Event handlers ---------------------------------------------------------
-    // ------------------------------------------------------------------------
-
-    /**
-     * Returns true if the mouse is currently on the close button.
-     *
-     * @return true if mouse is currently on the close button
-     */
-    protected boolean mouseOnClose() {
-        if ((flags & NOCLOSEBOX) != 0) {
-            return false;
-        }
-        if ((mouse != null)
-            && (mouse.getAbsoluteY() == getY())
-            && (mouse.getAbsoluteX() == getX() + 3)
-        ) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Returns true if the mouse is currently on the maximize/restore button.
-     *
-     * @return true if the mouse is currently on the maximize/restore button
-     */
-    protected boolean mouseOnMaximize() {
-        if ((flags & NOZOOMBOX) != 0) {
-            return false;
-        }
-        if ((mouse != null)
-            && !isModal()
-            && (mouse.getAbsoluteY() == getY())
-            && (mouse.getAbsoluteX() == getX() + getWidth() - 4)
-        ) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Returns true if the mouse is currently on the resizable lower right
-     * corner.
-     *
-     * @return true if the mouse is currently on the resizable lower right
-     * corner
-     */
-    protected boolean mouseOnResize() {
-        if (((flags & RESIZABLE) != 0)
-            && !isModal()
-            && (mouse != null)
-            && (mouse.getAbsoluteY() == getY() + getHeight() - 1)
-            && ((mouse.getAbsoluteX() == getX() + getWidth() - 1)
-                || (mouse.getAbsoluteX() == getX() + getWidth() - 2))
-        ) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Subclasses should override this method to cleanup resources.  This is
-     * called by application.closeWindow().
-     */
-    public void onClose() {
-        // Default: do nothing
-    }
-
-    /**
-     * Called by application.switchWindow() when this window gets the
-     * focus, and also by application.addWindow().
-     */
-    public void onFocus() {
-        // Default: do nothing
-    }
-
-    /**
-     * Called by application.switchWindow() when another window gets the
-     * focus.
-     */
-    public void onUnfocus() {
-        // Default: do nothing
-    }
-
-    /**
-     * Called by application.hideWindow().
+     * Called by application.hideWindow().
      */
     public void onHide() {
         // Default: do nothing
@@ -936,8 +450,13 @@ public class TWindow extends TWidget {
         }
 
         if (mouse.isMouse1() && mouseOnClose()) {
-            // Close window
-            application.closeWindow(this);
+            if ((flags & HIDEONCLOSE) == 0) {
+                // Close window
+                application.closeWindow(this);
+            } else {
+                // Hide window
+                application.hideWindow(this);
+            }
             return;
         }
 
@@ -1147,7 +666,13 @@ public class TWindow extends TWidget {
             // Ctrl-W - close window
             if (keypress.equals(kbCtrlW)) {
                 if ((flags & NOCLOSEBOX) == 0) {
-                    application.closeWindow(this);
+                    if ((flags & HIDEONCLOSE) == 0) {
+                        // Close window
+                        application.closeWindow(this);
+                    } else {
+                        // Hide window
+                        application.hideWindow(this);
+                    }
                 }
                 return;
             }
@@ -1200,7 +725,13 @@ public class TWindow extends TWidget {
 
             if (command.equals(cmWindowClose)) {
                 if ((flags & NOCLOSEBOX) == 0) {
-                    application.closeWindow(this);
+                    if ((flags & HIDEONCLOSE) == 0) {
+                        // Close window
+                        application.closeWindow(this);
+                    } else {
+                        // Hide window
+                        application.hideWindow(this);
+                    }
                 }
                 return;
             }
@@ -1246,7 +777,13 @@ public class TWindow extends TWidget {
 
             if (menu.getId() == TMenu.MID_WINDOW_CLOSE) {
                 if ((flags & NOCLOSEBOX) == 0) {
-                    application.closeWindow(this);
+                    if ((flags & HIDEONCLOSE) == 0) {
+                        // Close window
+                        application.closeWindow(this);
+                    } else {
+                        // Hide window
+                        application.hideWindow(this);
+                    }
                 }
                 return;
             }
@@ -1283,6 +820,509 @@ public class TWindow extends TWidget {
         super.onMenu(menu);
     }
 
+    // ------------------------------------------------------------------------
+    // TWidget ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Get this TWindow's parent TApplication.
+     *
+     * @return this TWindow's parent TApplication
+     */
+    @Override
+    public final TApplication getApplication() {
+        return application;
+    }
+
+    /**
+     * Get the Screen.
+     *
+     * @return the Screen
+     */
+    @Override
+    public final Screen getScreen() {
+        return application.getScreen();
+    }
+
+    /**
+     * Called by TApplication.drawChildren() to render on screen.
+     */
+    @Override
+    public void draw() {
+        // Draw the box and background first.
+        CellAttributes border = getBorder();
+        CellAttributes background = getBackground();
+        int borderType = getBorderType();
+
+        getScreen().drawBox(0, 0, getWidth(), getHeight(), border,
+            background, borderType, true);
+
+        // Draw the title
+        int titleLeft = (getWidth() - title.length() - 2) / 2;
+        putCharXY(titleLeft, 0, ' ', border);
+        putStringXY(titleLeft + 1, 0, title);
+        putCharXY(titleLeft + title.length() + 1, 0, ' ', border);
+
+        if (isActive()) {
+
+            // Draw the close button
+            if ((flags & NOCLOSEBOX) == 0) {
+                putCharXY(2, 0, '[', border);
+                putCharXY(4, 0, ']', border);
+                if (mouseOnClose() && mouse.isMouse1()) {
+                    putCharXY(3, 0, GraphicsChars.CP437[0x0F],
+                        getBorderControls());
+                } else {
+                    putCharXY(3, 0, GraphicsChars.CP437[0xFE],
+                        getBorderControls());
+                }
+            }
+
+            // Draw the maximize button
+            if (!isModal() && ((flags & NOZOOMBOX) == 0)) {
+
+                putCharXY(getWidth() - 5, 0, '[', border);
+                putCharXY(getWidth() - 3, 0, ']', border);
+                if (mouseOnMaximize() && mouse.isMouse1()) {
+                    putCharXY(getWidth() - 4, 0, GraphicsChars.CP437[0x0F],
+                        getBorderControls());
+                } else {
+                    if (maximized) {
+                        putCharXY(getWidth() - 4, 0, GraphicsChars.CP437[0x12],
+                            getBorderControls());
+                    } else {
+                        putCharXY(getWidth() - 4, 0, GraphicsChars.UPARROW,
+                            getBorderControls());
+                    }
+                }
+
+                // Draw the resize corner
+                if ((flags & RESIZABLE) != 0) {
+                    putCharXY(getWidth() - 2, getHeight() - 1,
+                        GraphicsChars.SINGLE_BAR, getBorderControls());
+                    putCharXY(getWidth() - 1, getHeight() - 1,
+                        GraphicsChars.LRCORNER, getBorderControls());
+                }
+            }
+        }
+    }
+
+    // ------------------------------------------------------------------------
+    // TWindow ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Get window title.
+     *
+     * @return window title
+     */
+    public final String getTitle() {
+        return title;
+    }
+
+    /**
+     * Set window title.
+     *
+     * @param title new window title
+     */
+    public final void setTitle(final String title) {
+        this.title = title;
+    }
+
+    /**
+     * Get Z order.  Lower number means more in-front.
+     *
+     * @return Z value.  Lower number means more in-front.
+     */
+    public final int getZ() {
+        return z;
+    }
+
+    /**
+     * Set Z order.  Lower number means more in-front.
+     *
+     * @param z the new Z value.  Lower number means more in-front.
+     */
+    public final void setZ(final int z) {
+        this.z = z;
+    }
+
+    /**
+     * Add a keypress to be overridden for this window.
+     *
+     * @param key the key to start taking control of
+     */
+    protected void addShortcutKeypress(final TKeypress key) {
+        keyboardShortcuts.add(key);
+    }
+
+    /**
+     * Remove a keypress to be overridden for this window.
+     *
+     * @param key the key to stop taking control of
+     */
+    protected void removeShortcutKeypress(final TKeypress key) {
+        keyboardShortcuts.remove(key);
+    }
+
+    /**
+     * Remove all keypresses to be overridden for this window.
+     */
+    protected void clearShortcutKeypresses() {
+        keyboardShortcuts.clear();
+    }
+
+    /**
+     * Determine if a keypress is overridden for this window.
+     *
+     * @param key the key to check
+     * @return true if this window wants to process this key on its own
+     */
+    public boolean isShortcutKeypress(final TKeypress key) {
+        return keyboardShortcuts.contains(key);
+    }
+
+    /**
+     * Get the window's status bar, or null if it does not have one.
+     *
+     * @return the status bar, or null
+     */
+    public TStatusBar getStatusBar() {
+        return statusBar;
+    }
+
+    /**
+     * Set the window's status bar to a new one.
+     *
+     * @param text the status bar text
+     * @return the status bar
+     */
+    public TStatusBar newStatusBar(final String text) {
+        statusBar = new TStatusBar(this, text);
+        return statusBar;
+    }
+
+    /**
+     * Set the maximum width for this window.
+     *
+     * @param maximumWindowWidth new maximum width
+     */
+    public final void setMaximumWindowWidth(final int maximumWindowWidth) {
+        if ((maximumWindowWidth != -1)
+            && (maximumWindowWidth < minimumWindowWidth + 1)
+        ) {
+            throw new IllegalArgumentException("Maximum window width cannot " +
+                "be smaller than minimum window width + 1");
+        }
+        this.maximumWindowWidth = maximumWindowWidth;
+    }
+
+    /**
+     * Set the minimum width for this window.
+     *
+     * @param minimumWindowWidth new minimum width
+     */
+    public final void setMinimumWindowWidth(final int minimumWindowWidth) {
+        if ((maximumWindowWidth != -1)
+            && (minimumWindowWidth > maximumWindowWidth - 1)
+        ) {
+            throw new IllegalArgumentException("Minimum window width cannot " +
+                "be larger than maximum window width - 1");
+        }
+        this.minimumWindowWidth = minimumWindowWidth;
+    }
+
+    /**
+     * Set the maximum height for this window.
+     *
+     * @param maximumWindowHeight new maximum height
+     */
+    public final void setMaximumWindowHeight(final int maximumWindowHeight) {
+        if ((maximumWindowHeight != -1)
+            && (maximumWindowHeight < minimumWindowHeight + 1)
+        ) {
+            throw new IllegalArgumentException("Maximum window height cannot " +
+                "be smaller than minimum window height + 1");
+        }
+        this.maximumWindowHeight = maximumWindowHeight;
+    }
+
+    /**
+     * Set the minimum height for this window.
+     *
+     * @param minimumWindowHeight new minimum height
+     */
+    public final void setMinimumWindowHeight(final int minimumWindowHeight) {
+        if ((maximumWindowHeight != -1)
+            && (minimumWindowHeight > maximumWindowHeight - 1)
+        ) {
+            throw new IllegalArgumentException("Minimum window height cannot " +
+                "be larger than maximum window height - 1");
+        }
+        this.minimumWindowHeight = minimumWindowHeight;
+    }
+
+    /**
+     * Recenter the window on-screen.
+     */
+    public final void center() {
+        if ((flags & CENTERED) != 0) {
+            if (getWidth() < getScreen().getWidth()) {
+                setX((getScreen().getWidth() - getWidth()) / 2);
+            } else {
+                setX(0);
+            }
+            setY(((application.getDesktopBottom()
+                    - application.getDesktopTop()) - getHeight()) / 2);
+            if (getY() < 0) {
+                setY(0);
+            }
+            setY(getY() + application.getDesktopTop());
+        }
+    }
+
+    /**
+     * Maximize window.
+     */
+    public void maximize() {
+        if (maximized) {
+            return;
+        }
+
+        restoreWindowWidth = getWidth();
+        restoreWindowHeight = getHeight();
+        restoreWindowX = getX();
+        restoreWindowY = getY();
+        setWidth(getScreen().getWidth());
+        setHeight(application.getDesktopBottom() - 1);
+        setX(0);
+        setY(1);
+        maximized = true;
+
+        onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, getWidth(),
+                getHeight()));
+    }
+
+    /**
+     * Restore (unmaximize) window.
+     */
+    public void restore() {
+        if (!maximized) {
+            return;
+        }
+
+        setWidth(restoreWindowWidth);
+        setHeight(restoreWindowHeight);
+        setX(restoreWindowX);
+        setY(restoreWindowY);
+        maximized = false;
+
+        onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, getWidth(),
+                getHeight()));
+    }
+
+    /**
+     * Returns true if this window is hidden.
+     *
+     * @return true if this window is hidden, false if the window is shown
+     */
+    public final boolean isHidden() {
+        return hidden;
+    }
+
+    /**
+     * Returns true if this window is shown.
+     *
+     * @return true if this window is shown, false if the window is hidden
+     */
+    public final boolean isShown() {
+        return !hidden;
+    }
+
+    /**
+     * Hide window.  A hidden window will still have its onIdle() called, and
+     * will also have onClose() called at application exit.  Hidden windows
+     * will not receive any other events.
+     */
+    public void hide() {
+        application.hideWindow(this);
+    }
+
+    /**
+     * Show window.
+     */
+    public void show() {
+        application.showWindow(this);
+    }
+
+    /**
+     * Activate window (bring to top and receive events).
+     */
+    public void activate() {
+        application.activateWindow(this);
+    }
+
+    /**
+     * Close window.  Note that windows without a close box can still be
+     * closed by calling the close() method.
+     */
+    public void close() {
+        application.closeWindow(this);
+    }
+
+    /**
+     * See if this window is undergoing any movement/resize/etc.
+     *
+     * @return true if the window is moving
+     */
+    public boolean inMovements() {
+        if (inWindowResize || inWindowMove || inKeyboardResize) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Stop any pending movement/resize/etc.
+     */
+    public void stopMovements() {
+        inWindowResize = false;
+        inWindowMove = false;
+        inKeyboardResize = false;
+    }
+
+    /**
+     * Returns true if this window is modal.
+     *
+     * @return true if this window is modal
+     */
+    public final boolean isModal() {
+        if ((flags & MODAL) == 0) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns true if this window has a close box.
+     *
+     * @return true if this window has a close box
+     */
+    public final boolean hasCloseBox() {
+        if ((flags & NOCLOSEBOX) != 0) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if this window has a maximize/zoom box.
+     *
+     * @return true if this window has a maximize/zoom box
+     */
+    public final boolean hasZoomBox() {
+        if ((flags & NOZOOMBOX) != 0) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Retrieve the background color.
+     *
+     * @return the background color
+     */
+    public CellAttributes getBackground() {
+        if (!isModal()
+            && (inWindowMove || inWindowResize || inKeyboardResize)
+        ) {
+            assert (isActive());
+            return getTheme().getColor("twindow.background.windowmove");
+        } else if (isModal() && inWindowMove) {
+            assert (isActive());
+            return getTheme().getColor("twindow.background.modal");
+        } else if (isModal()) {
+            if (isActive()) {
+                return getTheme().getColor("twindow.background.modal");
+            }
+            return getTheme().getColor("twindow.background.modal.inactive");
+        } else if (isActive()) {
+            assert (!isModal());
+            return getTheme().getColor("twindow.background");
+        } else {
+            assert (!isModal());
+            return getTheme().getColor("twindow.background.inactive");
+        }
+    }
+
+    /**
+     * Retrieve the border color.
+     *
+     * @return the border color
+     */
+    public CellAttributes getBorder() {
+        if (!isModal()
+            && (inWindowMove || inWindowResize || inKeyboardResize)
+        ) {
+            assert (isActive());
+            return getTheme().getColor("twindow.border.windowmove");
+        } else if (isModal() && inWindowMove) {
+            assert (isActive());
+            return getTheme().getColor("twindow.border.modal.windowmove");
+        } else if (isModal()) {
+            if (isActive()) {
+                return getTheme().getColor("twindow.border.modal");
+            } else {
+                return getTheme().getColor("twindow.border.modal.inactive");
+            }
+        } else if (isActive()) {
+            assert (!isModal());
+            return getTheme().getColor("twindow.border");
+        } else {
+            assert (!isModal());
+            return getTheme().getColor("twindow.border.inactive");
+        }
+    }
+
+    /**
+     * Retrieve the color used by the window movement/sizing controls.
+     *
+     * @return the color used by the zoom box, resize bar, and close box
+     */
+    public CellAttributes getBorderControls() {
+        if (isModal()) {
+            return getTheme().getColor("twindow.border.modal.windowmove");
+        }
+        return getTheme().getColor("twindow.border.windowmove");
+    }
+
+    /**
+     * Retrieve the border line type.
+     *
+     * @return the border line type
+     */
+    private int getBorderType() {
+        if (!isModal()
+            && (inWindowMove || inWindowResize || inKeyboardResize)
+        ) {
+            assert (isActive());
+            return 1;
+        } else if (isModal() && inWindowMove) {
+            assert (isActive());
+            return 1;
+        } else if (isModal()) {
+            if (isActive()) {
+                return 2;
+            } else {
+                return 1;
+            }
+        } else if (isActive()) {
+            return 2;
+        } else {
+            return 1;
+        }
+    }
+
     // ------------------------------------------------------------------------
     // Passthru for Screen functions ------------------------------------------
     // ------------------------------------------------------------------------