Merge branch 'subtree'
[fanfix.git] / src / jexer / TWindow.java
index 140a38aa263f1c2138aed7ff558ada47df9507e1..4d14d0eee2debcf23b03e2df314ea41721c38c8c 100644 (file)
@@ -3,7 +3,7 @@
  *
  * The MIT License (MIT)
  *
- * Copyright (C) 2017 Kevin Lamonte
+ * Copyright (C) 2019 Kevin Lamonte
  *
  * Permission is hereby granted, free of charge, to any person obtaining a
  * copy of this software and associated documentation files (the "Software"),
@@ -32,9 +32,9 @@ import java.util.HashSet;
 import java.util.Set;
 
 import jexer.backend.Screen;
-import jexer.bits.Cell;
 import jexer.bits.CellAttributes;
 import jexer.bits.GraphicsChars;
+import jexer.bits.StringUtils;
 import jexer.event.TCommandEvent;
 import jexer.event.TKeypressEvent;
 import jexer.event.TMenuEvent;
@@ -91,6 +91,11 @@ public class TWindow extends TWidget {
      */
     public static final int HIDEONCLOSE = 0x40;
 
+    /**
+     * Menus cannot be used when this window is active (default no).
+     */
+    public static final int OVERRIDEMENU        = 0x80;
+
     // ------------------------------------------------------------------------
     // Variables --------------------------------------------------------------
     // ------------------------------------------------------------------------
@@ -185,6 +190,20 @@ public class TWindow extends TWidget {
      */
     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 -----------------------------------------------------------
     // ------------------------------------------------------------------------
@@ -337,19 +356,32 @@ public class TWindow extends TWidget {
         return false;
     }
 
+    /**
+     * 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().
+     */
+    protected void onPreClose() {
+        // Default: do nothing.
+    }
+
     /**
      * Subclasses should override this method to cleanup resources.  This is
      * called by application.closeWindow().
      */
-    public void onClose() {
-        // Default: do nothing
+    protected void onClose() {
+        // Default: perform widget-specific cleanup.
+        for (TWidget w: getChildren()) {
+            w.close();
+        }
     }
 
     /**
      * Called by application.switchWindow() when this window gets the
      * focus, and also by application.addWindow().
      */
-    public void onFocus() {
+    protected void onFocus() {
         // Default: do nothing
     }
 
@@ -357,21 +389,21 @@ public class TWindow extends TWidget {
      * Called by application.switchWindow() when another window gets the
      * focus.
      */
-    public void onUnfocus() {
+    protected void onUnfocus() {
         // Default: do nothing
     }
 
     /**
      * Called by application.hideWindow().
      */
-    public void onHide() {
+    protected void onHide() {
         // Default: do nothing
     }
 
     /**
      * Called by application.showWindow().
      */
-    public void onShow() {
+    protected void onShow() {
         // Default: do nothing
     }
 
@@ -385,6 +417,8 @@ public class TWindow extends TWidget {
         this.mouse = mouse;
 
         inKeyboardResize = false;
+        inWindowMove = false;
+        inWindowResize = false;
 
         if ((mouse.getAbsoluteY() == getY())
             && mouse.isMouse1()
@@ -512,12 +546,6 @@ public class TWindow extends TWidget {
         }
 
         if (inWindowResize) {
-            // Do not permit resizing below the status line
-            if (mouse.getAbsoluteY() == application.getDesktopBottom()) {
-                inWindowResize = false;
-                return;
-            }
-
             // Move window over
             setWidth(resizeWindowWidth + (mouse.getAbsoluteX()
                     - moveWindowMouseX));
@@ -537,23 +565,22 @@ public class TWindow extends TWidget {
             // 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
@@ -579,6 +606,15 @@ public class TWindow extends TWidget {
     @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 or ENTER - Exit size/move
@@ -820,6 +856,38 @@ public class TWindow extends TWidget {
         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;
+            }
+        }
+
+        // Pass on to TWidget.
+        super.onResize(resize);
+    }
+
     // ------------------------------------------------------------------------
     // TWidget ----------------------------------------------------------------
     // ------------------------------------------------------------------------
@@ -854,14 +922,15 @@ public class TWindow extends TWidget {
         CellAttributes background = getBackground();
         int borderType = getBorderType();
 
-        getScreen().drawBox(0, 0, getWidth(), getHeight(), border,
-            background, borderType, true);
+        drawBox(0, 0, getWidth(), getHeight(), border, background, borderType,
+            true);
 
         // Draw the title
-        int titleLeft = (getWidth() - title.length() - 2) / 2;
+        int titleLength = StringUtils.width(title);
+        int titleLeft = (getWidth() - titleLength - 2) / 2;
         putCharXY(titleLeft, 0, ' ', border);
-        putStringXY(titleLeft + 1, 0, title);
-        putCharXY(titleLeft + title.length() + 1, 0, ' ', border);
+        putStringXY(titleLeft + 1, 0, title, border);
+        putCharXY(titleLeft + titleLength + 1, 0, ' ', border);
 
         if (isActive()) {
 
@@ -1094,9 +1163,9 @@ public class TWindow extends TWidget {
         restoreWindowX = getX();
         restoreWindowY = getY();
         setWidth(getScreen().getWidth());
-        setHeight(application.getDesktopBottom() - 1);
+        setHeight(application.getDesktopBottom() - application.getDesktopTop());
         setX(0);
-        setY(1);
+        setY(application.getDesktopTop());
         maximized = true;
 
         onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, getWidth(),
@@ -1158,6 +1227,7 @@ public class TWindow extends TWidget {
     /**
      * Activate window (bring to top and receive events).
      */
+    @Override
     public void activate() {
         application.activateWindow(this);
     }
@@ -1166,6 +1236,7 @@ public class TWindow extends TWidget {
      * 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);
     }
@@ -1227,6 +1298,20 @@ public class TWindow extends TWidget {
         return false;
     }
 
+    /**
+     * 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;
+    }
+
     /**
      * Retrieve the background color.
      *
@@ -1264,7 +1349,15 @@ public class TWindow extends TWidget {
         if (!isModal()
             && (inWindowMove || inWindowResize || inKeyboardResize)
         ) {
-            assert (isActive());
+            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");
+            }
+
             return getTheme().getColor("twindow.border.windowmove");
         } else if (isModal() && inWindowMove) {
             assert (isActive());
@@ -1323,148 +1416,48 @@ public class TWindow extends TWidget {
         }
     }
 
-    // ------------------------------------------------------------------------
-    // Passthru for Screen functions ------------------------------------------
-    // ------------------------------------------------------------------------
-
     /**
-     * Get the attributes at one location.
+     * Returns true if this window does not want the application-wide mouse
+     * cursor drawn over it.
      *
-     * @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 does not want the application-wide mouse
+     * cursor drawn over it
      */
-    public final CellAttributes getAttrXY(final int x, final int y) {
-        return getScreen().getAttrXY(x, y);
+    public boolean hasHiddenMouse() {
+        return hideMouse;
     }
 
     /**
-     * Set the attributes at one location.
+     * 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 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 putAttrXY(final int x, final int y,
-        final CellAttributes attr) {
-
-        getScreen().putAttrXY(x, y, attr);
+    public final void setHiddenMouse(final boolean hideMouse) {
+        this.hideMouse = hideMouse;
     }
 
     /**
-     * Set the attributes at one location.
+     * Get this window's help topic to load.
      *
-     * @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 the topic name
      */
-    public final void putAttrXY(final int x, final int y,
-        final CellAttributes attr, final boolean clip) {
-
-        getScreen().putAttrXY(x, y, attr, clip);
+    public String getHelpTopic() {
+        return helpTopic;
     }
 
     /**
-     * Fill the entire screen with one character with attributes.
+     * Generate a human-readable string for this window.
      *
-     * @param ch character to draw
-     * @param attr attributes to use (bold, foreColor, backColor)
+     * @return a human-readable string
      */
-    public final void putAll(final char ch, final CellAttributes attr) {
-        getScreen().putAll(ch, attr);
-    }
-
-    /**
-     * Render one character with attributes.
-     *
-     * @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
-     */
-    public final void putCharXY(final int x, final int y, final Cell ch) {
-        getScreen().putCharXY(x, y, ch);
-    }
-
-    /**
-     * Render one character with attributes.
-     *
-     * @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)
-     */
-    public final void putCharXY(final int x, final int y, final char ch,
-        final CellAttributes attr) {
-
-        getScreen().putCharXY(x, y, ch, attr);
-    }
-
-    /**
-     * Render one character without changing the underlying attributes.
-     *
-     * @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
-     */
-    public final void putCharXY(final int x, final int y, final char ch) {
-        getScreen().putCharXY(x, y, ch);
-    }
-
-    /**
-     * Render a string.  Does not wrap if the string exceeds the line.
-     *
-     * @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)
-     */
-    public final void putStringXY(final int x, final int y, final String str,
-        final CellAttributes attr) {
-
-        getScreen().putStringXY(x, y, str, attr);
-    }
-
-    /**
-     * Render a string without changing the underlying attribute.  Does not
-     * wrap if the string exceeds the line.
-     *
-     * @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
-     */
-    public final void putStringXY(final int x, final int y, final String str) {
-        getScreen().putStringXY(x, y, str);
-    }
-
-    /**
-     * Draw a vertical line from (x, y) to (x, y + n).
-     *
-     * @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)
-     */
-    public final void vLineXY(final int x, final int y, final int n,
-        final char ch, final CellAttributes attr) {
-
-        getScreen().vLineXY(x, y, n, ch, attr);
-    }
-
-    /**
-     * Draw a horizontal line from (x, y) to (x + n, y).
-     *
-     * @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)
-     */
-    public final void hLineXY(final int x, final int y, final int n,
-        final char ch, final CellAttributes attr) {
-
-        getScreen().hLineXY(x, y, n, ch, attr);
+    @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());
     }
 
 }