From 2bc32111fa17ae50a8aaa09ab347191fefc494a1 Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Tue, 20 Aug 2019 08:47:14 -0500 Subject: [PATCH 1/1] #51 wip --- examples/JexerTilingWindowManager2.java | 125 +++++++++++++++++++ src/jexer/TApplication.java | 25 +++- src/jexer/TDesktop.java | 156 +++++++++++++++--------- src/jexer/TTerminalWidget.java | 113 ++++++++++++++--- src/jexer/TTerminalWindow.java | 12 +- src/jexer/TWidget.java | 19 ++- src/jexer/TWindow.java | 9 +- src/jexer/menu/TMenu.java | 1 - 8 files changed, 374 insertions(+), 86 deletions(-) create mode 100644 examples/JexerTilingWindowManager2.java diff --git a/examples/JexerTilingWindowManager2.java b/examples/JexerTilingWindowManager2.java new file mode 100644 index 0000000..cc23aa7 --- /dev/null +++ b/examples/JexerTilingWindowManager2.java @@ -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 + } + +} diff --git a/src/jexer/TApplication.java b/src/jexer/TApplication.java index f86ca27..14ebacd 100644 --- a/src/jexer/TApplication.java +++ b/src/jexer/TApplication.java @@ -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(), diff --git a/src/jexer/TDesktop.java b/src/jexer/TDesktop.java index fd56a7f..5aa52af 100644 --- a/src/jexer/TDesktop.java +++ b/src/jexer/TDesktop.java @@ -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; + } + } diff --git a/src/jexer/TTerminalWidget.java b/src/jexer/TTerminalWidget.java index 36966a0..8c0082c 100644 --- a/src/jexer/TTerminalWidget.java +++ b/src/jexer/TTerminalWidget.java @@ -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)); } /** diff --git a/src/jexer/TTerminalWindow.java b/src/jexer/TTerminalWindow.java index a5d92ac..34e7dc4 100644 --- a/src/jexer/TTerminalWindow.java +++ b/src/jexer/TTerminalWindow.java @@ -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(); + } + }); } // ------------------------------------------------------------------------ diff --git a/src/jexer/TWidget.java b/src/jexer/TWidget.java index bca2be0..729a5f5 100644 --- a/src/jexer/TWidget.java +++ b/src/jexer/TWidget.java @@ -1115,6 +1115,11 @@ public abstract class TWidget implements Comparable { 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 { * 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 { 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; diff --git a/src/jexer/TWindow.java b/src/jexer/TWindow.java index 222efbc..6763df3 100644 --- a/src/jexer/TWindow.java +++ b/src/jexer/TWindow.java @@ -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; } diff --git a/src/jexer/menu/TMenu.java b/src/jexer/menu/TMenu.java index 9983580..acac991 100644 --- a/src/jexer/menu/TMenu.java +++ b/src/jexer/menu/TMenu.java @@ -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); } -- 2.27.0