Merge commit '77d3a60869e7a780c6ae069e51530e1eacece5e2'
[fanfix.git] / src / jexer / TWindow.java
index d57b844d7d4408c4c14c70e4076914782bc90f4a..4d14d0eee2debcf23b03e2df314ea41721c38c8c 100644 (file)
@@ -1,44 +1,45 @@
-/**
+/*
  * Jexer - Java Text User Interface
  *
- * License: LGPLv3 or later
- *
- * This module is licensed under the GNU Lesser General Public License
- * Version 3.  Please see the file "COPYING" in this directory for more
- * information about the GNU Lesser General Public License Version 3.
+ * The MIT License (MIT)
  *
- *     Copyright (C) 2015  Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
  *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public License
- * as published by the Free Software Foundation; either version 3 of
- * the License, or (at your option) any later version.
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
  *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
  *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this program; if not, see
- * http://www.gnu.org/licenses/, or write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
  *
  * @author Kevin Lamonte [kevin.lamonte@gmail.com]
  * @version 1
  */
 package jexer;
 
-import jexer.bits.Cell;
+import java.util.HashSet;
+import java.util.Set;
+
+import jexer.backend.Screen;
 import jexer.bits.CellAttributes;
 import jexer.bits.GraphicsChars;
+import jexer.bits.StringUtils;
 import jexer.event.TCommandEvent;
 import jexer.event.TKeypressEvent;
 import jexer.event.TMenuEvent;
 import jexer.event.TMouseEvent;
 import jexer.event.TResizeEvent;
-import jexer.io.Screen;
 import jexer.menu.TMenu;
 import static jexer.TCommand.*;
 import static jexer.TKeypress.*;
@@ -46,116 +47,103 @@ import static jexer.TKeypress.*;
 /**
  * TWindow is the top-level container and drawing surface for other widgets.
  */
-public class TWindow extends TWidget implements Comparable<TWindow> {
+public class TWindow extends TWidget {
+
+    // ------------------------------------------------------------------------
+    // Constants --------------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
-     * Window's parent TApplication.
+     * Window is resizable (default yes).
      */
-    private TApplication application;
+    public static final int RESIZABLE   = 0x01;
 
     /**
-     * Get this TWindow's parent TApplication.
-     *
-     * @return this TWindow's parent TApplication
+     * Window is modal (default no).
      */
-    @Override
-    public final TApplication getApplication() {
-        return application;
-    }
+    public static final int MODAL       = 0x02;
 
     /**
-     * Get the Screen.
-     *
-     * @return the Screen
+     * Window is centered (default no).
      */
-    @Override
-    public final Screen getScreen() {
-        return application.getScreen();
-    }
+    public static final int CENTERED    = 0x04;
 
     /**
-     * Window title.
+     * Window has no close box (default no).  Window can still be closed via
+     * TApplication.closeWindow() and TWindow.close().
      */
-    private String title = "";
+    public static final int NOCLOSEBOX  = 0x08;
 
     /**
-     * Get window title.
-     *
-     * @return window title
+     * Window has no maximize box (default no).
      */
-    public final String getTitle() {
-        return title;
-    }
+    public static final int NOZOOMBOX   = 0x10;
 
     /**
-     * Set window title.
-     *
-     * @param title new window title
+     * Window is placed at absolute position (no smart placement) (default
+     * no).
      */
-    public final void setTitle(final String title) {
-        this.title = title;
-    }
+    public static final int ABSOLUTEXY  = 0x20;
 
     /**
-     * Window is resizable (default yes).
+     * Hitting the closebox with the mouse calls TApplication.hideWindow()
+     * rather than TApplication.closeWindow() (default no).
      */
-    public static final int RESIZABLE   = 0x01;
+    public static final int HIDEONCLOSE = 0x40;
 
     /**
-     * Window is modal (default no).
+     * Menus cannot be used when this window is active (default no).
      */
-    public static final int MODAL       = 0x02;
+    public static final int OVERRIDEMENU        = 0x80;
+
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
-     * Window is centered (default no).
+     * Window flags.  Note package private access.
      */
-    public static final int CENTERED    = 0x04;
+    int flags = RESIZABLE;
 
     /**
-     * Window flags.
+     * Window title.
      */
-    private int flags = RESIZABLE;
+    private String title = "";
 
     /**
-     * Z order.  Lower number means more in-front.
+     * Window's parent TApplication.
      */
-    private int z = 0;
+    private TApplication application;
 
     /**
-     * Get Z order.  Lower number means more in-front.
-     *
-     * @return Z value.  Lower number means more in-front.
+     * Z order.  Lower number means more in-front.
      */
-    public final int getZ() {
-        return z;
-    }
+    private int z = 0;
 
     /**
-     * Set Z order.  Lower number means more in-front.
-     *
-     * @param z the new Z value.  Lower number means more in-front.
+     * Window's keyboard shortcuts.  Any key in this set will be passed to
+     * the window directly rather than processed through the menu
+     * accelerators.
      */
-    public final void setZ(final int z) {
-        this.z = z;
-    }
+    private Set<TKeypress> keyboardShortcuts = new HashSet<TKeypress>();
 
     /**
      * If true, then the user clicked on the title bar and is moving the
      * window.
      */
-    private boolean inWindowMove = false;
+    protected boolean inWindowMove = false;
 
     /**
      * If true, then the user clicked on the bottom right corner and is
      * resizing the window.
      */
-    private boolean inWindowResize = false;
+    protected boolean inWindowResize = false;
 
     /**
      * 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.
@@ -187,6 +175,39 @@ public class TWindow extends TWidget implements Comparable<TWindow> {
     private int restoreWindowX;
     private int restoreWindowY;
 
+    /**
+     * 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;
+
+    /**
+     * 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;
+
+    /**
+     * A window may request that TApplication NOT draw the mouse cursor over
+     * it by setting this to true.  This is currently only used within Jexer
+     * by TTerminalWindow so that only the bottom-most instance of nested
+     * Jexer's draws the mouse within its application window.  But perhaps
+     * other applications can use it, so public getter/setter is provided.
+     */
+    private boolean hideMouse = false;
+
+    /**
+     * The help topic for this window.
+     */
+    protected String helpTopic = "Help";
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Public constructor.  Window will be located at (0, 0).
      *
@@ -271,57 +292,22 @@ public class TWindow extends TWidget implements Comparable<TWindow> {
         center();
 
         // Add me to the application
-        application.addWindow(this);
-    }
-
-    /**
-     * 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());
-        }
-    }
-
-    /**
-     * 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;
+        application.addWindowToApplication(this);
     }
 
-    /**
-     * Comparison operator sorts on z.
-     *
-     * @param that another TWindow instance
-     * @return difference between this.z and that.z
-     */
-    @Override
-    public final int compareTo(final TWindow that) {
-        return (this.z - that.z);
-    }
+    // ------------------------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Returns true if the mouse is currently on the close button.
      *
      * @return true if mouse is currently on the close button
      */
-    private boolean mouseOnClose() {
+    protected boolean mouseOnClose() {
+        if ((flags & NOCLOSEBOX) != 0) {
+            return false;
+        }
         if ((mouse != null)
             && (mouse.getAbsoluteY() == getY())
             && (mouse.getAbsoluteX() == getX() + 3)
@@ -336,7 +322,10 @@ public class TWindow extends TWidget implements Comparable<TWindow> {
      *
      * @return true if the mouse is currently on the maximize/restore button
      */
-    private boolean mouseOnMaximize() {
+    protected boolean mouseOnMaximize() {
+        if ((flags & NOZOOMBOX) != 0) {
+            return false;
+        }
         if ((mouse != null)
             && !isModal()
             && (mouse.getAbsoluteY() == getY())
@@ -354,7 +343,7 @@ public class TWindow extends TWidget implements Comparable<TWindow> {
      * @return true if the mouse is currently on the resizable lower right
      * corner
      */
-    private boolean mouseOnResize() {
+    protected boolean mouseOnResize() {
         if (((flags & RESIZABLE) != 0)
             && !isModal()
             && (mouse != null)
@@ -368,162 +357,54 @@ public class TWindow extends TWidget implements Comparable<TWindow> {
     }
 
     /**
-     * Retrieve the background color.
-     *
-     * @return the background color
+     * Subclasses should override this method to perform any user prompting
+     * before they are offscreen.  Note that unlike other windowing toolkits,
+     * windows can NOT use this function in some manner to avoid being
+     * closed.  This is called by application.closeWindow().
      */
-    public final CellAttributes getBackground() {
-        if (!isModal()
-            && (inWindowMove || inWindowResize || inKeyboardResize)
-        ) {
-            assert (getActive());
-            return getTheme().getColor("twindow.background.windowmove");
-        } else if (isModal() && inWindowMove) {
-            assert (getActive());
-            return getTheme().getColor("twindow.background.modal");
-        } else if (isModal()) {
-            if (getActive()) {
-                return getTheme().getColor("twindow.background.modal");
-            }
-            return getTheme().getColor("twindow.background.modal.inactive");
-        } else if (getActive()) {
-            assert (!isModal());
-            return getTheme().getColor("twindow.background");
-        } else {
-            assert (!isModal());
-            return getTheme().getColor("twindow.background.inactive");
-        }
+    protected void onPreClose() {
+        // Default: do nothing.
     }
 
     /**
-     * Retrieve the border color.
-     *
-     * @return the border color
+     * Subclasses should override this method to cleanup resources.  This is
+     * called by application.closeWindow().
      */
-    private CellAttributes getBorder() {
-        if (!isModal()
-            && (inWindowMove || inWindowResize || inKeyboardResize)
-        ) {
-            assert (getActive());
-            return getTheme().getColor("twindow.border.windowmove");
-        } else if (isModal() && inWindowMove) {
-            assert (getActive());
-            return getTheme().getColor("twindow.border.modal.windowmove");
-        } else if (isModal()) {
-            if (getActive()) {
-                return getTheme().getColor("twindow.border.modal");
-            } else {
-                return getTheme().getColor("twindow.border.modal.inactive");
-            }
-        } else if (getActive()) {
-            assert (!isModal());
-            return getTheme().getColor("twindow.border");
-        } else {
-            assert (!isModal());
-            return getTheme().getColor("twindow.border.inactive");
+    protected void onClose() {
+        // Default: perform widget-specific cleanup.
+        for (TWidget w: getChildren()) {
+            w.close();
         }
     }
 
     /**
-     * Retrieve the border line type.
-     *
-     * @return the border line type
+     * Called by application.switchWindow() when this window gets the
+     * focus, and also by application.addWindow().
      */
-    private int getBorderType() {
-        if (!isModal()
-            && (inWindowMove || inWindowResize || inKeyboardResize)
-        ) {
-            assert (getActive());
-            return 1;
-        } else if (isModal() && inWindowMove) {
-            assert (getActive());
-            return 1;
-        } else if (isModal()) {
-            if (getActive()) {
-                return 2;
-            } else {
-                return 1;
-            }
-        } else if (getActive()) {
-            return 2;
-        } else {
-            return 1;
-        }
+    protected void onFocus() {
+        // Default: do nothing
     }
 
     /**
-     * Subclasses should override this method to cleanup resources.  This is
-     * called by application.closeWindow().
+     * Called by application.switchWindow() when another window gets the
+     * focus.
      */
-    public void onClose() {
+    protected void onUnfocus() {
         // Default: do nothing
     }
 
     /**
-     * Called by TApplication.drawChildren() to render on screen.
+     * Called by application.hideWindow().
      */
-    @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);
-        putStrXY(titleLeft + 1, 0, title);
-        putCharXY(titleLeft + title.length() + 1, 0, ' ', border);
-
-        if (getActive()) {
-
-            // Draw the close button
-            putCharXY(2, 0, '[', border);
-            putCharXY(4, 0, ']', border);
-            if (mouseOnClose() && mouse.getMouse1()) {
-                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()) {
-
-                putCharXY(getWidth() - 5, 0, '[', border);
-                putCharXY(getWidth() - 3, 0, ']', border);
-                if (mouseOnMaximize() && mouse.getMouse1()) {
-                    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"));
-                    }
-                }
+    protected void onHide() {
+        // Default: do nothing
+    }
 
-                // 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"));
-                }
-            }
-        }
+    /**
+     * Called by application.showWindow().
+     */
+    protected void onShow() {
+        // Default: do nothing
     }
 
     /**
@@ -534,12 +415,13 @@ public class TWindow extends TWidget implements Comparable<TWindow> {
     @Override
     public void onMouseDown(final TMouseEvent mouse) {
         this.mouse = mouse;
-        application.setRepaint();
 
         inKeyboardResize = false;
+        inWindowMove = false;
+        inWindowResize = false;
 
         if ((mouse.getAbsoluteY() == getY())
-            && mouse.getMouse1()
+            && mouse.isMouse1()
             && (getX() <= mouse.getAbsoluteX())
             && (mouse.getAbsoluteX() < getX() + getWidth())
             && !mouseOnClose()
@@ -569,36 +451,17 @@ public class TWindow extends TWidget implements Comparable<TWindow> {
             return;
         }
 
+        // Give the shortcut bar a shot at this.
+        if (statusBar != null) {
+            if (statusBar.statusBarMouseDown(mouse)) {
+                return;
+            }
+        }
+
         // I didn't take it, pass it on to my children
         super.onMouseDown(mouse);
     }
 
-    /**
-     * Maximize window.
-     */
-    private void maximize() {
-        restoreWindowWidth = getWidth();
-        restoreWindowHeight = getHeight();
-        restoreWindowX = getX();
-        restoreWindowY = getY();
-        setWidth(getScreen().getWidth());
-        setHeight(application.getDesktopBottom() - 1);
-        setX(0);
-        setY(1);
-        maximized = true;
-    }
-
-    /**
-     * Restote (unmaximize) window.
-     */
-    private void restore() {
-        setWidth(restoreWindowWidth);
-        setHeight(restoreWindowHeight);
-        setX(restoreWindowX);
-        setY(restoreWindowY);
-        maximized = false;
-    }
-
     /**
      * Handle mouse button releases.
      *
@@ -607,28 +470,32 @@ public class TWindow extends TWidget implements Comparable<TWindow> {
     @Override
     public void onMouseUp(final TMouseEvent mouse) {
         this.mouse = mouse;
-        application.setRepaint();
 
-        if ((inWindowMove) && (mouse.getMouse1())) {
+        if ((inWindowMove) && (mouse.isMouse1())) {
             // Stop moving window
             inWindowMove = false;
             return;
         }
 
-        if ((inWindowResize) && (mouse.getMouse1())) {
+        if ((inWindowResize) && (mouse.isMouse1())) {
             // Stop resizing window
             inWindowResize = false;
             return;
         }
 
-        if (mouse.getMouse1() && mouseOnClose()) {
-            // Close window
-            application.closeWindow(this);
-            return;
+        if (mouse.isMouse1() && mouseOnClose()) {
+            if ((flags & HIDEONCLOSE) == 0) {
+                // Close window
+                application.closeWindow(this);
+            } else {
+                // Hide window
+                application.hideWindow(this);
+            }
+            return;
         }
 
         if ((mouse.getAbsoluteY() == getY())
-            && mouse.getMouse1()
+            && mouse.isMouse1()
             && mouseOnMaximize()) {
             if (maximized) {
                 // Restore
@@ -643,6 +510,13 @@ public class TWindow extends TWidget implements Comparable<TWindow> {
             return;
         }
 
+        // Give the shortcut bar a shot at this.
+        if (statusBar != null) {
+            if (statusBar.statusBarMouseUp(mouse)) {
+                return;
+            }
+        }
+
         // I didn't take it, pass it on to my children
         super.onMouseUp(mouse);
     }
@@ -655,7 +529,6 @@ public class TWindow extends TWidget implements Comparable<TWindow> {
     @Override
     public void onMouseMotion(final TMouseEvent mouse) {
         this.mouse = mouse;
-        application.setRepaint();
 
         if (inWindowMove) {
             // Move window over
@@ -665,6 +538,10 @@ public class TWindow extends TWidget implements Comparable<TWindow> {
             if (getY() < application.getDesktopTop()) {
                 setY(application.getDesktopTop());
             }
+            // Don't go below the status bar
+            if (getY() >= application.getDesktopBottom()) {
+                setY(application.getDesktopBottom() - 1);
+            }
             return;
         }
 
@@ -688,23 +565,22 @@ public class TWindow extends TWidget implements Comparable<TWindow> {
             // Keep within min/max bounds
             if (getWidth() < minimumWindowWidth) {
                 setWidth(minimumWindowWidth);
-                inWindowResize = false;
             }
             if (getHeight() < minimumWindowHeight) {
                 setHeight(minimumWindowHeight);
-                inWindowResize = false;
             }
             if ((maximumWindowWidth > 0)
                 && (getWidth() > maximumWindowWidth)
             ) {
                 setWidth(maximumWindowWidth);
-                inWindowResize = false;
             }
             if ((maximumWindowHeight > 0)
                 && (getHeight() > maximumWindowHeight)
             ) {
                 setHeight(maximumWindowHeight);
-                inWindowResize = false;
+            }
+            if (getHeight() + getY() >= getApplication().getDesktopBottom()) {
+                setHeight(getApplication().getDesktopBottom() - getY());
             }
 
             // Pass a resize event to my children
@@ -713,6 +589,11 @@ public class TWindow extends TWidget implements Comparable<TWindow> {
             return;
         }
 
+        // Give the shortcut bar a shot at this.
+        if (statusBar != null) {
+            statusBar.statusBarMouseMotion(mouse);
+        }
+
         // I didn't take it, pass it on to my children
         super.onMouseMotion(mouse);
     }
@@ -725,10 +606,19 @@ public class TWindow extends TWidget implements Comparable<TWindow> {
     @Override
     public void onKeypress(final TKeypressEvent keypress) {
 
+        if (inWindowMove || inWindowResize) {
+            // ESC or ENTER - Exit size/move
+            if (keypress.equals(kbEsc) || keypress.equals(kbEnter)) {
+                inWindowMove = false;
+                inWindowResize = false;
+                return;
+            }
+        }
+
         if (inKeyboardResize) {
 
-            // ESC - Exit size/move
-            if (keypress.equals(kbEsc)) {
+            // ESC or ENTER - Exit size/move
+            if (keypress.equals(kbEsc) || keypress.equals(kbEnter)) {
                 inKeyboardResize = false;
             }
 
@@ -752,65 +642,104 @@ public class TWindow extends TWidget implements Comparable<TWindow> {
                     setY(getY() - 1);
                 }
             }
-            if (keypress.equals(kbShiftLeft)) {
-                if (getWidth() > minimumWindowWidth) {
-                    setWidth(getWidth() - 1);
+
+            /*
+             * Only permit keyboard resizing if the window was RESIZABLE.
+             */
+            if ((flags & RESIZABLE) != 0) {
+
+                if (keypress.equals(kbShiftLeft)) {
+                    if ((getWidth() > minimumWindowWidth)
+                        || (minimumWindowWidth <= 0)
+                    ) {
+                        setWidth(getWidth() - 1);
+                    }
                 }
-            }
-            if (keypress.equals(kbShiftRight)) {
-                if (getWidth() < maximumWindowWidth) {
-                    setWidth(getWidth() + 1);
+                if (keypress.equals(kbShiftRight)) {
+                    if ((getWidth() < maximumWindowWidth)
+                        || (maximumWindowWidth <= 0)
+                    ) {
+                        setWidth(getWidth() + 1);
+                    }
                 }
-            }
-            if (keypress.equals(kbShiftUp)) {
-                if (getHeight() > minimumWindowHeight) {
-                    setHeight(getHeight() - 1);
+                if (keypress.equals(kbShiftUp)) {
+                    if ((getHeight() > minimumWindowHeight)
+                        || (minimumWindowHeight <= 0)
+                    ) {
+                        setHeight(getHeight() - 1);
+                    }
                 }
-            }
-            if (keypress.equals(kbShiftDown)) {
-                if (getHeight() < maximumWindowHeight) {
-                    setHeight(getHeight() + 1);
+                if (keypress.equals(kbShiftDown)) {
+                    if ((getHeight() < maximumWindowHeight)
+                        || (maximumWindowHeight <= 0)
+                    ) {
+                        setHeight(getHeight() + 1);
+                    }
                 }
-            }
+
+                // Pass a resize event to my children
+                onResize(new TResizeEvent(TResizeEvent.Type.WIDGET,
+                        getWidth(), getHeight()));
+
+            } // if ((flags & RESIZABLE) != 0)
 
             return;
         }
 
+        // Give the shortcut bar a shot at this.
+        if (statusBar != null) {
+            if (statusBar.statusBarKeypress(keypress)) {
+                return;
+            }
+        }
+
         // These keystrokes will typically not be seen unless a subclass
         // overrides onMenu() due to how TApplication dispatches
         // accelerators.
 
-        // Ctrl-W - close window
-        if (keypress.equals(kbCtrlW)) {
-            application.closeWindow(this);
-            return;
-        }
+        if (!(this instanceof TDesktop)) {
 
-        // F6 - behave like Alt-TAB
-        if (keypress.equals(kbF6)) {
-            application.switchWindow(true);
-            return;
-        }
+            // Ctrl-W - close window
+            if (keypress.equals(kbCtrlW)) {
+                if ((flags & NOCLOSEBOX) == 0) {
+                    if ((flags & HIDEONCLOSE) == 0) {
+                        // Close window
+                        application.closeWindow(this);
+                    } else {
+                        // Hide window
+                        application.hideWindow(this);
+                    }
+                }
+                return;
+            }
 
-        // Shift-F6 - behave like Shift-Alt-TAB
-        if (keypress.equals(kbShiftF6)) {
-            application.switchWindow(false);
-            return;
-        }
+            // F6 - behave like Alt-TAB
+            if (keypress.equals(kbF6)) {
+                application.switchWindow(true);
+                return;
+            }
 
-        // F5 - zoom
-        if (keypress.equals(kbF5)) {
-            if (maximized) {
-                restore();
-            } else {
-                maximize();
+            // Shift-F6 - behave like Shift-Alt-TAB
+            if (keypress.equals(kbShiftF6)) {
+                application.switchWindow(false);
+                return;
             }
-        }
 
-        // Ctrl-F5 - size/move
-        if (keypress.equals(kbCtrlF5)) {
-            inKeyboardResize = !inKeyboardResize;
-        }
+            // F5 - zoom
+            if (keypress.equals(kbF5) && ((flags & NOZOOMBOX) == 0)) {
+                if (maximized) {
+                    restore();
+                } else {
+                    maximize();
+                }
+            }
+
+            // Ctrl-F5 - size/move
+            if (keypress.equals(kbCtrlF5)) {
+                inKeyboardResize = !inKeyboardResize;
+            }
+
+        } // if (!(this instanceof TDesktop))
 
         // I didn't take it, pass it on to my children
         super.onKeypress(keypress);
@@ -828,33 +757,45 @@ public class TWindow extends TWidget implements Comparable<TWindow> {
         // overrides onMenu() due to how TApplication dispatches
         // accelerators.
 
-        if (command.equals(cmWindowClose)) {
-            application.closeWindow(this);
-            return;
-        }
+        if (!(this instanceof TDesktop)) {
 
-        if (command.equals(cmWindowNext)) {
-            application.switchWindow(true);
-            return;
-        }
+            if (command.equals(cmWindowClose)) {
+                if ((flags & NOCLOSEBOX) == 0) {
+                    if ((flags & HIDEONCLOSE) == 0) {
+                        // Close window
+                        application.closeWindow(this);
+                    } else {
+                        // Hide window
+                        application.hideWindow(this);
+                    }
+                }
+                return;
+            }
 
-        if (command.equals(cmWindowPrevious)) {
-            application.switchWindow(false);
-            return;
-        }
+            if (command.equals(cmWindowNext)) {
+                application.switchWindow(true);
+                return;
+            }
 
-        if (command.equals(cmWindowMove)) {
-            inKeyboardResize = true;
-            return;
-        }
+            if (command.equals(cmWindowPrevious)) {
+                application.switchWindow(false);
+                return;
+            }
 
-        if (command.equals(cmWindowZoom)) {
-            if (maximized) {
-                restore();
-            } else {
-                maximize();
+            if (command.equals(cmWindowMove)) {
+                inKeyboardResize = true;
+                return;
             }
-        }
+
+            if (command.equals(cmWindowZoom) && ((flags & NOZOOMBOX) == 0)) {
+                if (maximized) {
+                    restore();
+                } else {
+                    maximize();
+                }
+            }
+
+        } // if (!(this instanceof TDesktop))
 
         // I didn't take it, pass it on to my children
         super.onCommand(command);
@@ -867,182 +808,656 @@ public class TWindow extends TWidget implements Comparable<TWindow> {
      */
     @Override
     public void onMenu(final TMenuEvent menu) {
-        if (menu.getId() == TMenu.MID_WINDOW_CLOSE) {
-            application.closeWindow(this);
-            return;
+
+        if (!(this instanceof TDesktop)) {
+
+            if (menu.getId() == TMenu.MID_WINDOW_CLOSE) {
+                if ((flags & NOCLOSEBOX) == 0) {
+                    if ((flags & HIDEONCLOSE) == 0) {
+                        // Close window
+                        application.closeWindow(this);
+                    } else {
+                        // Hide window
+                        application.hideWindow(this);
+                    }
+                }
+                return;
+            }
+
+            if (menu.getId() == TMenu.MID_WINDOW_NEXT) {
+                application.switchWindow(true);
+                return;
+            }
+
+            if (menu.getId() == TMenu.MID_WINDOW_PREVIOUS) {
+                application.switchWindow(false);
+                return;
+            }
+
+            if (menu.getId() == TMenu.MID_WINDOW_MOVE) {
+                inKeyboardResize = true;
+                return;
+            }
+
+            if ((menu.getId() == TMenu.MID_WINDOW_ZOOM)
+                && ((flags & NOZOOMBOX) == 0)
+            ) {
+                if (maximized) {
+                    restore();
+                } else {
+                    maximize();
+                }
+                return;
+            }
+
+        } // if (!(this instanceof TDesktop))
+
+        // I didn't take it, pass it on to my children
+        super.onMenu(menu);
+    }
+
+    /**
+     * Method that subclasses can override to handle window/screen resize
+     * events.
+     *
+     * @param resize resize event
+     */
+    @Override
+    public void onResize(final TResizeEvent resize) {
+        if (resize.getType() == TResizeEvent.Type.WIDGET) {
+            if (getChildren().size() == 1) {
+                TWidget child = getChildren().get(0);
+                if ((child instanceof TSplitPane)
+                    || (child instanceof TPanel)
+                ) {
+                    if (this instanceof TDesktop) {
+                        child.onResize(new TResizeEvent(
+                            TResizeEvent.Type.WIDGET,
+                            resize.getWidth(), resize.getHeight()));
+                    } else {
+                        child.onResize(new TResizeEvent(
+                            TResizeEvent.Type.WIDGET,
+                            resize.getWidth() - 2, resize.getHeight() - 2));
+                    }
+                }
+                return;
+            }
         }
 
-        if (menu.getId() == TMenu.MID_WINDOW_NEXT) {
-            application.switchWindow(true);
-            return;
+        // Pass on to TWidget.
+        super.onResize(resize);
+    }
+
+    // ------------------------------------------------------------------------
+    // 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();
+
+        drawBox(0, 0, getWidth(), getHeight(), border, background, borderType,
+            true);
+
+        // Draw the title
+        int titleLength = StringUtils.width(title);
+        int titleLeft = (getWidth() - titleLength - 2) / 2;
+        putCharXY(titleLeft, 0, ' ', border);
+        putStringXY(titleLeft + 1, 0, title, border);
+        putCharXY(titleLeft + titleLength + 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());
+                }
+            }
         }
+    }
 
-        if (menu.getId() == TMenu.MID_WINDOW_PREVIOUS) {
-            application.switchWindow(false);
-            return;
+    // ------------------------------------------------------------------------
+    // 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;
+    }
 
-        if (menu.getId() == TMenu.MID_WINDOW_MOVE) {
-            inKeyboardResize = true;
-            return;
+    /**
+     * 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;
+    }
 
-        if (menu.getId() == TMenu.MID_WINDOW_ZOOM) {
-            if (maximized) {
-                restore();
+    /**
+     * 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 {
-                maximize();
+                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;
         }
 
-        // I didn't take it, pass it on to my children
-        super.onMenu(menu);
+        restoreWindowWidth = getWidth();
+        restoreWindowHeight = getHeight();
+        restoreWindowX = getX();
+        restoreWindowY = getY();
+        setWidth(getScreen().getWidth());
+        setHeight(application.getDesktopBottom() - application.getDesktopTop());
+        setX(0);
+        setY(application.getDesktopTop());
+        maximized = true;
+
+        onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, getWidth(),
+                getHeight()));
     }
 
-    // ------------------------------------------------------------------------
-    // Passthru for Screen functions ------------------------------------------
-    // ------------------------------------------------------------------------
+    /**
+     * 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()));
+    }
 
     /**
-     * Get the attributes at one location.
+     * Returns true if this window is hidden.
      *
-     * @param x column coordinate.  0 is the left-most column.
-     * @param y row coordinate.  0 is the top-most row.
-     * @return attributes at (x, y)
+     * @return true if this window is hidden, false if the window is shown
      */
-    public final CellAttributes getAttrXY(final int x, final int y) {
-        return getScreen().getAttrXY(x, y);
+    public final boolean isHidden() {
+        return hidden;
     }
 
     /**
-     * Set the attributes at one location.
+     * Returns true if this window is shown.
      *
-     * @param x column coordinate.  0 is the left-most column.
-     * @param y row coordinate.  0 is the top-most row.
-     * @param attr attributes to use (bold, foreColor, backColor)
+     * @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 final void putAttrXY(final int x, final int y,
-        final CellAttributes attr) {
+    @Override
+    public void activate() {
+        application.activateWindow(this);
+    }
 
-        getScreen().putAttrXY(x, y, attr);
+    /**
+     * Close window.  Note that windows without a close box can still be
+     * closed by calling the close() method.
+     */
+    @Override
+    public void close() {
+        application.closeWindow(this);
     }
 
     /**
-     * Set the attributes at one location.
+     * See if this window is undergoing any movement/resize/etc.
      *
-     * @param x column coordinate.  0 is the left-most column.
-     * @param y row coordinate.  0 is the top-most row.
-     * @param attr attributes to use (bold, foreColor, backColor)
-     * @param clip if true, honor clipping/offset
+     * @return true if the window is moving
      */
-    public final void putAttrXY(final int x, final int y,
-        final CellAttributes attr, final boolean clip) {
+    public boolean inMovements() {
+        if (inWindowResize || inWindowMove || inKeyboardResize) {
+            return true;
+        }
+        return false;
+    }
 
-        getScreen().putAttrXY(x, y, attr, clip);
+    /**
+     * Stop any pending movement/resize/etc.
+     */
+    public void stopMovements() {
+        inWindowResize = false;
+        inWindowMove = false;
+        inKeyboardResize = false;
     }
 
     /**
-     * Fill the entire screen with one character with attributes.
+     * Returns true if this window is modal.
      *
-     * @param ch character to draw
-     * @param attr attributes to use (bold, foreColor, backColor)
+     * @return true if this window is modal
      */
-    public final void putAll(final char ch, final CellAttributes attr) {
-        getScreen().putAll(ch, attr);
+    public final boolean isModal() {
+        if ((flags & MODAL) == 0) {
+            return false;
+        }
+        return true;
     }
 
     /**
-     * Render one character with attributes.
+     * Returns true if this window has a close box.
      *
-     * @param x column coordinate.  0 is the left-most column.
-     * @param y row coordinate.  0 is the top-most row.
-     * @param ch character + attributes to draw
+     * @return true if this window has a close box
      */
-    public final void putCharXY(final int x, final int y, final Cell ch) {
-        getScreen().putCharXY(x, y, ch);
+    public final boolean hasCloseBox() {
+        if ((flags & NOCLOSEBOX) != 0) {
+            return true;
+        }
+        return false;
     }
 
     /**
-     * Render one character with attributes.
+     * Returns true if this window has a maximize/zoom box.
      *
-     * @param x column coordinate.  0 is the left-most column.
-     * @param y row coordinate.  0 is the top-most row.
-     * @param ch character to draw
-     * @param attr attributes to use (bold, foreColor, backColor)
+     * @return true if this window has a maximize/zoom box
      */
-    public final void putCharXY(final int x, final int y, final char ch,
-        final CellAttributes attr) {
+    public final boolean hasZoomBox() {
+        if ((flags & NOZOOMBOX) != 0) {
+            return true;
+        }
+        return false;
+    }
 
-        getScreen().putCharXY(x, y, ch, attr);
+    /**
+     * Returns true if this window does not want menus to work while it is
+     * visible.
+     *
+     * @return true if this window does not want menus to work while it is
+     * visible
+     */
+    public final boolean hasOverriddenMenu() {
+        if ((flags & OVERRIDEMENU) != 0) {
+            return true;
+        }
+        return false;
     }
 
     /**
-     * Render one character without changing the underlying attributes.
+     * Retrieve the background color.
      *
-     * @param x column coordinate.  0 is the left-most column.
-     * @param y row coordinate.  0 is the top-most row.
-     * @param ch character to draw
+     * @return the background color
      */
-    public final void putCharXY(final int x, final int y, final char ch) {
-        getScreen().putCharXY(x, y, ch);
+    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");
+        }
     }
 
     /**
-     * Render a string.  Does not wrap if the string exceeds the line.
+     * Retrieve the border color.
      *
-     * @param x column coordinate.  0 is the left-most column.
-     * @param y row coordinate.  0 is the top-most row.
-     * @param str string to draw
-     * @param attr attributes to use (bold, foreColor, backColor)
+     * @return the border color
      */
-    public final void putStrXY(final int x, final int y, final String str,
-        final CellAttributes attr) {
+    public CellAttributes getBorder() {
+        if (!isModal()
+            && (inWindowMove || inWindowResize || inKeyboardResize)
+        ) {
+            if (!isActive()) {
+                // The user's terminal never passed a mouse up event, and now
+                // another window is active but we never finished a drag.
+                inWindowMove = false;
+                inWindowResize = false;
+                inKeyboardResize = false;
+                return getTheme().getColor("twindow.border.inactive");
+            }
 
-        getScreen().putStrXY(x, y, str, attr);
+            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");
+        }
     }
 
     /**
-     * Render a string without changing the underlying attribute.  Does not
-     * wrap if the string exceeds the line.
+     * Retrieve the color used by the window movement/sizing controls.
      *
-     * @param x column coordinate.  0 is the left-most column.
-     * @param y row coordinate.  0 is the top-most row.
-     * @param str string to draw
+     * @return the color used by the zoom box, resize bar, and close box
      */
-    public final void putStrXY(final int x, final int y, final String str) {
-        getScreen().putStrXY(x, y, str);
+    public CellAttributes getBorderControls() {
+        if (isModal()) {
+            return getTheme().getColor("twindow.border.modal.windowmove");
+        }
+        return getTheme().getColor("twindow.border.windowmove");
     }
 
     /**
-     * Draw a vertical line from (x, y) to (x, y + n).
+     * Retrieve the border line type.
      *
-     * @param x column coordinate.  0 is the left-most column.
-     * @param y row coordinate.  0 is the top-most row.
-     * @param n number of characters to draw
-     * @param ch character to draw
-     * @param attr attributes to use (bold, foreColor, backColor)
+     * @return the border line type
      */
-    public final void vLineXY(final int x, final int y, final int n,
-        final char ch, final CellAttributes attr) {
+    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;
+        }
+    }
 
-        getScreen().vLineXY(x, y, n, ch, attr);
+    /**
+     * Returns true if this window does not want the application-wide mouse
+     * cursor drawn over it.
+     *
+     * @return true if this window does not want the application-wide mouse
+     * cursor drawn over it
+     */
+    public boolean hasHiddenMouse() {
+        return hideMouse;
     }
 
     /**
-     * Draw a horizontal line from (x, y) to (x + n, y).
+     * Set request to prevent the application-wide mouse cursor from being
+     * drawn over this window.
      *
-     * @param x column coordinate.  0 is the left-most column.
-     * @param y row coordinate.  0 is the top-most row.
-     * @param n number of characters to draw
-     * @param ch character to draw
-     * @param attr attributes to use (bold, foreColor, backColor)
+     * @param hideMouse if true, this window does not want the
+     * application-wide mouse cursor drawn over it
      */
-    public final void hLineXY(final int x, final int y, final int n,
-        final char ch, final CellAttributes attr) {
+    public final void setHiddenMouse(final boolean hideMouse) {
+        this.hideMouse = hideMouse;
+    }
 
-        getScreen().hLineXY(x, y, n, ch, attr);
+    /**
+     * Get this window's help topic to load.
+     *
+     * @return the topic name
+     */
+    public String getHelpTopic() {
+        return helpTopic;
     }
 
+    /**
+     * Generate a human-readable string for this window.
+     *
+     * @return a human-readable string
+     */
+    @Override
+    public String toString() {
+        return String.format("%s(%8x) \'%s\' Z %d position (%d, %d) " +
+            "geometry %dx%d  hidden %s modal %s",
+            getClass().getName(), hashCode(), title, getZ(),
+            getX(), getY(), getWidth(), getHeight(), hidden, isModal());
+    }
 
 }