#51 wip
authorKevin Lamonte <kevin.lamonte@gmail.com>
Tue, 20 Aug 2019 13:47:14 +0000 (08:47 -0500)
committerKevin Lamonte <kevin.lamonte@gmail.com>
Tue, 20 Aug 2019 13:47:14 +0000 (08:47 -0500)
examples/JexerTilingWindowManager2.java [new file with mode: 0644]
src/jexer/TApplication.java
src/jexer/TDesktop.java
src/jexer/TTerminalWidget.java
src/jexer/TTerminalWindow.java
src/jexer/TWidget.java
src/jexer/TWindow.java
src/jexer/menu/TMenu.java

diff --git a/examples/JexerTilingWindowManager2.java b/examples/JexerTilingWindowManager2.java
new file mode 100644 (file)
index 0000000..cc23aa7
--- /dev/null
@@ -0,0 +1,125 @@
+import jexer.TAction;
+import jexer.TApplication;
+import jexer.TDesktop;
+import jexer.TTerminalWidget;
+import jexer.TWidget;
+import jexer.event.TKeypressEvent;
+import jexer.event.TMenuEvent;
+import jexer.event.TMouseEvent;
+import jexer.event.TResizeEvent;
+import jexer.menu.TMenu;
+
+/**
+ * Implements a simple tiling window manager.  A terminal widget is added to
+ * the desktop, which can be split horizontally or vertically.  A close
+ * action is provided to each window to remove the split when its shell
+ * exits.
+ *
+ * This example shows what can be done with minimal changes to stock Jexer
+ * widgets.
+ */
+public class JexerTilingWindowManager2 extends TApplication {
+
+    /**
+     * Menu item: split the terminal vertically.
+     */
+    private static final int MENU_SPLIT_VERTICAL = 2000;
+
+    /**
+     * Menu item: split the terminal horizontally.
+     */
+    private static final int MENU_SPLIT_HORIZONTAL = 2001;
+
+    /**
+     * Handle to the root widget.
+     */
+    private TWidget root = null;
+    
+    /**
+     * Main entry point.
+     */
+    public static void main(String [] args) throws Exception {
+        // For this application, we must use ptypipe so that the terminal
+        // shells can be aware of their size.
+        System.setProperty("jexer.TTerminal.ptypipe", "true");
+
+        // Let's also suppress the status line.
+        System.setProperty("jexer.hideStatusBar", "true");
+
+        JexerTilingWindowManager2 jtwm = new JexerTilingWindowManager2();
+        (new Thread(jtwm)).start();
+    }
+
+    /**
+     * Public constructor chooses the ECMA-48 / Xterm backend.
+     */
+    public JexerTilingWindowManager2() throws Exception {
+        super(BackendType.SWING);
+
+        // The stock tool menu has items for redrawing the screen, opening
+        // images, and (when using the Swing backend) setting the font.
+        addToolMenu();
+
+        // We will have one menu containing a mix of new and stock commands
+        TMenu tileMenu = addMenu("&Tile");
+
+        // New commands for this example: split vertical and horizontal.
+        tileMenu.addItem(MENU_SPLIT_VERTICAL, "&Vertical Split");
+        tileMenu.addItem(MENU_SPLIT_HORIZONTAL, "&Horizontal Split");
+
+        // Stock commands: a new shell with resizable window, previous, next,
+        // close, and exit program.
+        tileMenu.addItem(TMenu.MID_SHELL, "&Floating");
+        tileMenu.addSeparator();
+        tileMenu.addDefaultItem(TMenu.MID_WINDOW_PREVIOUS);
+        tileMenu.addDefaultItem(TMenu.MID_WINDOW_NEXT);
+        tileMenu.addDefaultItem(TMenu.MID_WINDOW_CLOSE);
+        tileMenu.addSeparator();
+        tileMenu.addDefaultItem(TMenu.MID_EXIT);
+
+        // Spin up the root terminal
+        root = new TTerminalWidget(getDesktop(), 0, 0,
+            getDesktop().getWidth(), getDesktop().getHeight(),
+            new TAction() {
+                public void DO() {
+                    // TODO: if root's parent is TSplitPane, call
+                    // TSplitPane.removeSplit(TWidget).
+                    if (root != null) {
+                        root.remove();
+                    }
+                }
+            });
+    }
+
+    /**
+     * Process menu events.
+     */
+    @Override
+    protected boolean onMenu(TMenuEvent event) {
+        if (event.getId() == MENU_SPLIT_VERTICAL) {
+            splitVertical();
+            return true;
+        }
+        if (event.getId() == MENU_SPLIT_HORIZONTAL) {
+            splitHorizontal();
+            return true;
+        }
+
+        return super.onMenu(event);
+    }
+
+    /**
+     * Perform the vertical split.
+     */
+    private void splitVertical() {
+        // TODO
+    }
+
+    /**
+     * Perform the horizontal split.
+     */
+    private void splitHorizontal() {
+        // TODO
+    }
+
+}
index f86ca27a1c2fdc3d4935c65abb55ed30617672d6..14ebacda7f4fa0a645c0958018d1bb8871f332e9 100644 (file)
@@ -1100,8 +1100,8 @@ public class TApplication implements Runnable {
                     oldMouseY = 0;
                 }
                 if (desktop != null) {
-                    desktop.setDimensions(0, 0, resize.getWidth(),
-                        resize.getHeight() - (hideStatusBar ? 0 : 1));
+                    desktop.setDimensions(0, desktopTop, resize.getWidth(),
+                        (desktopBottom - desktopTop));
                     desktop.onResize(resize);
                 }
 
@@ -2001,12 +2001,29 @@ public class TApplication implements Runnable {
 
         // Place the cursor if it is visible
         if (!menuIsActive) {
+
+            int visibleWindowCount = 0;
+            for (TWindow window: sorted) {
+                if (window.isShown()) {
+                    visibleWindowCount++;
+                }
+            }
+            if (visibleWindowCount == 0) {
+                // No windows are visible, only the desktop.  Allow it to
+                // have the cursor.
+                if (desktop != null) {
+                    sorted.add(desktop);
+                }
+            }
+
             TWidget activeWidget = null;
             if (sorted.size() > 0) {
                 activeWidget = sorted.get(sorted.size() - 1).getActiveChild();
+                int cursorClipTop = desktopTop;
+                int cursorClipBottom = desktopBottom;
                 if (activeWidget.isCursorVisible()) {
-                    if ((activeWidget.getCursorAbsoluteY() < desktopBottom)
-                        && (activeWidget.getCursorAbsoluteY() > desktopTop)
+                    if ((activeWidget.getCursorAbsoluteY() <= cursorClipBottom)
+                        && (activeWidget.getCursorAbsoluteY() >= cursorClipTop)
                     ) {
                         getScreen().putCursor(true,
                             activeWidget.getCursorAbsoluteX(),
index fd56a7fb55abbe8d3d71b0c5e6f6310aa2dd04e3..5aa52af74a981f97901c0e09c9d7af4062056fa3 100644 (file)
@@ -33,6 +33,7 @@ import jexer.bits.GraphicsChars;
 import jexer.event.TKeypressEvent;
 import jexer.event.TMenuEvent;
 import jexer.event.TMouseEvent;
+import jexer.event.TResizeEvent;
 
 /**
  * TDesktop is a special-class window that is drawn underneath everything
@@ -68,72 +69,30 @@ public class TDesktop extends TWindow {
     }
 
     // ------------------------------------------------------------------------
-    // TWindow ----------------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
     // ------------------------------------------------------------------------
 
     /**
-     * The default TDesktop draws a hatch character across everything.
-     */
-    @Override
-    public void draw() {
-        CellAttributes background = getTheme().getColor("tdesktop.background");
-        putAll(GraphicsChars.HATCH, background);
-    }
-
-    /**
-     * Hide window.  This is a NOP for TDesktop.
-     */
-    @Override
-    public final void hide() {}
-
-    /**
-     * Show window.  This is a NOP for TDesktop.
-     */
-    @Override
-    public final void show() {}
-
-    /**
-     * Called by hide().  This is a NOP for TDesktop.
-     */
-    @Override
-    public final void onHide() {}
-
-    /**
-     * Called by show().  This is a NOP for TDesktop.
-     */
-    @Override
-    public final void onShow() {}
-
-    /**
-     * Returns true if the mouse is currently on the close button.
+     * Handle window/screen resize events.
      *
-     * @return true if mouse is currently on the close button
+     * @param resize resize event
      */
     @Override
-    protected final boolean mouseOnClose() {
-        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
-     */
-    @Override
-    protected final boolean mouseOnMaximize() {
-        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
-     */
-    @Override
-    protected final boolean mouseOnResize() {
-        return false;
+    public void onResize(final TResizeEvent resize) {
+        if (getChildren().size() == 1) {
+            TWidget child = getChildren().get(0);
+            if (!(child instanceof TWindow)) {
+                // Only one child, resize it to match my size.
+                child.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET,
+                        getWidth(), getHeight()));
+            }
+        }
+        if (resize.getType() == TResizeEvent.Type.SCREEN) {
+            // Let children see the screen resize
+            for (TWidget widget: getChildren()) {
+                widget.onResize(resize);
+            }
+        }
     }
 
     /**
@@ -219,4 +178,81 @@ public class TDesktop extends TWindow {
         super.onMenu(menu);
     }
 
+    // ------------------------------------------------------------------------
+    // TWindow ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * The default TDesktop draws a hatch character across everything.
+     */
+    @Override
+    public void draw() {
+        CellAttributes background = getTheme().getColor("tdesktop.background");
+        putAll(GraphicsChars.HATCH, background);
+
+        /*
+        // For debugging, let's see where the desktop bounds really are.
+        putCharXY(0, 0, '0', background);
+        putCharXY(getWidth() - 1, 0, '1', background);
+        putCharXY(0, getHeight() - 1, '2', background);
+        putCharXY(getWidth() - 1, getHeight() - 1, '3', background);
+         */
+    }
+
+    /**
+     * Hide window.  This is a NOP for TDesktop.
+     */
+    @Override
+    public final void hide() {}
+
+    /**
+     * Show window.  This is a NOP for TDesktop.
+     */
+    @Override
+    public final void show() {}
+
+    /**
+     * Called by hide().  This is a NOP for TDesktop.
+     */
+    @Override
+    public final void onHide() {}
+
+    /**
+     * Called by show().  This is a NOP for TDesktop.
+     */
+    @Override
+    public final void onShow() {}
+
+    /**
+     * Returns true if the mouse is currently on the close button.
+     *
+     * @return true if mouse is currently on the close button
+     */
+    @Override
+    protected final boolean mouseOnClose() {
+        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
+     */
+    @Override
+    protected final boolean mouseOnMaximize() {
+        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
+     */
+    @Override
+    protected final boolean mouseOnResize() {
+        return false;
+    }
+
 }
index 36966a0cb80805adcfd1a68e9482a96739a5af3b..8c0082c08d8af431161a324c1a10316840900747 100644 (file)
@@ -60,7 +60,7 @@ import jexer.tterminal.ECMA48;
 import static jexer.TKeypress.*;
 
 /**
- * TTerminalWindow exposes a ECMA-48 / ANSI X3.64 style terminal in a widget.
+ * TTerminalWidget exposes a ECMA-48 / ANSI X3.64 style terminal in a widget.
  */
 public class TTerminalWidget extends TScrollableWidget
                              implements DisplayListener {
@@ -159,6 +159,11 @@ public class TTerminalWidget extends TScrollableWidget
      */
     private String title = "";
 
+    /**
+     * Action to perform when the terminal exits.
+     */
+    private TAction closeAction = null;
+
     // ------------------------------------------------------------------------
     // Constructors -----------------------------------------------------------
     // ------------------------------------------------------------------------
@@ -171,8 +176,8 @@ public class TTerminalWidget extends TScrollableWidget
      * @param y row relative to parent
      * @param commandLine the command line to execute
      */
-    public TTerminalWidget(final TWidget parent, final int x,
-        final int y, final String commandLine) {
+    public TTerminalWidget(final TWidget parent, final int x, final int y,
+        final String commandLine) {
 
         this(parent, x, y, commandLine.split("\\s+"));
     }
@@ -185,10 +190,45 @@ public class TTerminalWidget extends TScrollableWidget
      * @param y row relative to parent
      * @param command the command line to execute
      */
-    public TTerminalWidget(final TWidget parent, final int x,
-        final int y, final String [] command) {
+    public TTerminalWidget(final TWidget parent, final int x, final int y,
+        final String [] command) {
+
+        this(parent, x, y, command, null);
+    }
+
+    /**
+     * Public constructor spawns a custom command line.
+     *
+     * @param parent parent widget
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param command the command line to execute
+     * @param closeAction action to perform when the shell sxits
+     */
+    public TTerminalWidget(final TWidget parent, final int x, final int y,
+        final String [] command, final TAction closeAction) {
+
+        this(parent, x, y, 80, 24, command, closeAction);
+    }
+
+    /**
+     * Public constructor spawns a custom command line.
+     *
+     * @param parent parent widget
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param width width of widget
+     * @param height height of widget
+     * @param command the command line to execute
+     * @param closeAction action to perform when the shell sxits
+     */
+    public TTerminalWidget(final TWidget parent, final int x, final int y,
+        final int width, final int height, final String [] command,
+        final TAction closeAction) {
+
+        super(parent, x, y, width, height);
 
-        super(parent, x, y, 80, 24);
+        this.closeAction = closeAction;
 
         String [] fullCommand;
 
@@ -234,8 +274,39 @@ public class TTerminalWidget extends TScrollableWidget
      * @param y row relative to parent
      */
     public TTerminalWidget(final TWidget parent, final int x, final int y) {
+        this(parent, x, y, (TAction) null);
+    }
+
+    /**
+     * Public constructor spawns a shell.
+     *
+     * @param parent parent widget
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param closeAction action to perform when the shell sxits
+     */
+    public TTerminalWidget(final TWidget parent, final int x, final int y,
+        final TAction closeAction) {
+
+        this(parent, x, y, 80, 24, closeAction);
+    }
+
+    /**
+     * Public constructor spawns a shell.
+     *
+     * @param parent parent widget
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param width width of widget
+     * @param height height of widget
+     * @param closeAction action to perform when the shell sxits
+     */
+    public TTerminalWidget(final TWidget parent, final int x, final int y,
+        final int width, final int height, final TAction closeAction) {
+
+        super(parent, x, y, width, height);
 
-        super(parent, x, y, 80, 24);
+        this.closeAction = closeAction;
 
         if (System.getProperty("jexer.TTerminal.shell") != null) {
             String shell = System.getProperty("jexer.TTerminal.shell");
@@ -288,6 +359,7 @@ public class TTerminalWidget extends TScrollableWidget
     @Override
     public void draw() {
         int width = getDisplayWidth();
+
         boolean syncEmulator = false;
         if ((System.currentTimeMillis() - lastUpdateTime >= 25)
             && (dirty == true)
@@ -321,9 +393,6 @@ public class TTerminalWidget extends TScrollableWidget
             dirty = false;
         }
 
-        // Draw the box using my superclass
-        super.draw();
-
         // Put together the visible rows
         int visibleHeight = getHeight();
         int visibleBottom = scrollback.size() + display.size()
@@ -408,7 +477,7 @@ public class TTerminalWidget extends TScrollableWidget
     }
 
     /**
-     * Handle window close.
+     * Handle widget close.
      */
     @Override
     public void close() {
@@ -427,6 +496,8 @@ public class TTerminalWidget extends TScrollableWidget
      */
     @Override
     public void onResize(final TResizeEvent resize) {
+        // Let TWidget set my size.
+        super.onResize(resize);
 
         // Synchronize against the emulator so we don't stomp on its reader
         // thread.
@@ -621,10 +692,10 @@ public class TTerminalWidget extends TScrollableWidget
     }
 
     /**
-     * Returns true if this window does not want the application-wide mouse
+     * Returns true if this widget does not want the application-wide mouse
      * cursor drawn over it.
      *
-     * @return true if this window does not want the application-wide mouse
+     * @return true if this widget does not want the application-wide mouse
      * cursor drawn over it
      */
     public boolean hasHiddenMouse() {
@@ -747,10 +818,20 @@ public class TTerminalWidget extends TScrollableWidget
      * Hook for subclasses to be notified of the shell termination.
      */
     public void onShellExit() {
-        if (getParent() instanceof TTerminalWindow) {
-            ((TTerminalWindow) getParent()).onShellExit();
+        TApplication app = getApplication();
+        if (app != null) {
+            app.invokeLater(new Runnable() {
+                public void run() {
+                    if (closeAction != null) {
+                        closeAction.DO();
+                    }
+                    if (getApplication() != null) {
+                        getApplication().postEvent(new TMenuEvent(
+                            TMenu.MID_REPAINT));
+                    }
+                }
+            });
         }
-        getApplication().postEvent(new TMenuEvent(TMenu.MID_REPAINT));
     }
 
     /**
index a5d92aca58bff5a8daa0aa006f9756492bd50847..34e7dc411d298af3612af465b641db1e95547e83 100644 (file)
@@ -163,7 +163,11 @@ public class TTerminalWindow extends TScrollableWindow {
         newStatusBar(i18n.getString("statusBarRunning"));
 
         // Spin it up
-        terminal = new TTerminalWidget(this, 0, 0);
+        terminal = new TTerminalWidget(this, 0, 0, new TAction() {
+            public void DO() {
+                onShellExit();
+            }
+        });
     }
 
     /**
@@ -208,7 +212,11 @@ public class TTerminalWindow extends TScrollableWindow {
         newStatusBar(i18n.getString("statusBarRunning"));
 
         // Spin it up
-        terminal = new TTerminalWidget(this, 0, 0);
+        terminal = new TTerminalWidget(this, 0, 0, new TAction() {
+            public void DO() {
+                onShellExit();
+            }
+        });
     }
 
     // ------------------------------------------------------------------------
index bca2be02f21f03cc305985a56fbf82c5314ae75c..729a5f5d0c3a1b43056087e43c39fae1b7f39527 100644 (file)
@@ -1115,6 +1115,11 @@ public abstract class TWidget implements Comparable<TWidget> {
 
         assert (window != null);
 
+        if (window instanceof TDesktop) {
+            // Desktop doesn't have a window border.
+            return cursorVisible;
+        }
+
         // If cursor is out of my window's bounds, it is not visible.
         if ((getCursorAbsoluteX() >= window.getAbsoluteX()
                 + window.getWidth() - 1)
@@ -1311,6 +1316,10 @@ public abstract class TWidget implements Comparable<TWidget> {
      * Called by parent to render to TWindow.  Note package private access.
      */
     final void drawChildren() {
+        if (window == null) {
+            return;
+        }
+
         // Set my clipping rectangle
         assert (window != null);
         assert (getScreen() != null);
@@ -1327,10 +1336,16 @@ public abstract class TWidget implements Comparable<TWidget> {
 
         int absoluteRightEdge = window.getAbsoluteX() + window.getWidth();
         int absoluteBottomEdge = window.getAbsoluteY() + window.getHeight();
-        if (!(this instanceof TWindow) && !(this instanceof TVScroller)) {
+        if (!(this instanceof TWindow)
+            && !(this instanceof TVScroller)
+            && !(parent instanceof TDesktop)
+        ) {
             absoluteRightEdge -= 1;
         }
-        if (!(this instanceof TWindow) && !(this instanceof THScroller)) {
+        if (!(this instanceof TWindow)
+            && !(this instanceof THScroller)
+            && !(parent instanceof TDesktop)
+        ) {
             absoluteBottomEdge -= 1;
         }
         int myRightEdge = getAbsoluteX() + width;
index 222efbcde7484867b2266e399a3bd5f56076b1e8..6763df3063640e5e2504dfa34d2e6f5bf6997b43 100644 (file)
@@ -863,8 +863,15 @@ public class TWindow extends TWidget {
                 if ((child instanceof TSplitPane)
                     || (child instanceof TPanel)
                 ) {
-                    child.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET,
+                    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;
             }
index 9983580ac1ff02b5742be5342ec4c0edcb218342..acac991ea571e9979e909a5d7197922284208f8e 100644 (file)
@@ -435,7 +435,6 @@ public class TMenu extends TWindow {
      * @return the new menu item
      */
     public TMenuItem addItem(final int id, final String label) {
-        assert (id >= 1024);
         return addItemInternal(id, label, null);
     }