--- /dev/null
+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
+ }
+
+}
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);
}
// 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(),
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
}
// ------------------------------------------------------------------------
- // 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);
+ }
+ }
}
/**
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;
+ }
+
}
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 {
*/
private String title = "";
+ /**
+ * Action to perform when the terminal exits.
+ */
+ private TAction closeAction = null;
+
// ------------------------------------------------------------------------
// Constructors -----------------------------------------------------------
// ------------------------------------------------------------------------
* @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+"));
}
* @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;
* @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");
@Override
public void draw() {
int width = getDisplayWidth();
+
boolean syncEmulator = false;
if ((System.currentTimeMillis() - lastUpdateTime >= 25)
&& (dirty == true)
dirty = false;
}
- // Draw the box using my superclass
- super.draw();
-
// Put together the visible rows
int visibleHeight = getHeight();
int visibleBottom = scrollback.size() + display.size()
}
/**
- * Handle window close.
+ * Handle widget close.
*/
@Override
public void close() {
*/
@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.
}
/**
- * 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() {
* 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));
}
/**
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();
+ }
+ });
}
/**
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();
+ }
+ });
}
// ------------------------------------------------------------------------
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)
* 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);
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;
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;
}
* @return the new menu item
*/
public TMenuItem addItem(final int id, final String label) {
- assert (id >= 1024);
return addItemInternal(id, label, null);
}