--- /dev/null
+import jexer.TApplication;
+import jexer.TTerminalWindow;
+import jexer.TWindow;
+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 root non-moveable
+ * non-resizable terminal window is created first, which can be split
+ * horizontally or vertically. Each new window retains a reference to its
+ * "parent", and upon closing resizes that parent back to its original size.
+ *
+ * This example shows what can be done with minimal changes to stock Jexer
+ * widgets. You will quickly see that closing a "parent" tile does not cause
+ * the "child" tile to resize. You could make a real subclass of
+ * TTerminalWindow that has extra fields and/or communicates more with
+ * JexerTilingWindowManager to get full coverage of tile creation,
+ * destruction, placement, movement, and so on.
+ */
+public class JexerTilingWindowManager 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;
+
+ /**
+ * Main entry point.
+ */
+ public static void main(String [] args) throws Exception {
+ // For this application, we must use ptypipe so that the tile shells
+ // can be aware of their size.
+ System.setProperty("jexer.TTerminal.ptypipe", "true");
+
+ JexerTilingWindowManager jtwm = new JexerTilingWindowManager();
+ (new Thread(jtwm)).start();
+ }
+
+ /**
+ * Public constructor chooses the ECMA-48 / Xterm backend.
+ */
+ public JexerTilingWindowManager() throws Exception {
+ super(BackendType.XTERM);
+
+ // 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 tile
+ TTerminalWindow rootTile = makeTile(0, 0, getScreen().getWidth(),
+ getDesktopBottom() - 1, null);
+
+ // Let's add some bling! Enable focus-follows-mouse.
+ setFocusFollowsMouse(true);
+ }
+
+ /**
+ * 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() {
+ TWindow window = getActiveWindow();
+ if (!(window instanceof TTerminalWindow)) {
+ return;
+ }
+
+ TTerminalWindow tile = (TTerminalWindow) window;
+ // Give the extra column to the new tile.
+ int newWidth = (tile.getWidth() + 1) / 2;
+ int newY = tile.getY() - 1;
+ int newX = tile.getX() + tile.getWidth() - newWidth;
+ makeTile(newX, newY, newWidth, tile.getHeight(), tile);
+ tile.setWidth(tile.getWidth() - newWidth);
+ tile.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET,
+ tile.getWidth(), tile.getHeight()));
+ }
+
+ /**
+ * Perform the horizontal split.
+ */
+ private void splitHorizontal() {
+ TWindow window = getActiveWindow();
+ if (!(window instanceof TTerminalWindow)) {
+ return;
+ }
+
+ TTerminalWindow tile = (TTerminalWindow) window;
+ // Give the extra row to the new tile.
+ int newHeight = (tile.getHeight() + 1) / 2;
+ int newY = tile.getY() - 1 + tile.getHeight() - newHeight;
+ int newX = tile.getX();
+ makeTile(newX, newY, tile.getWidth(), newHeight, tile);
+ tile.setHeight(tile.getHeight() - newHeight);
+ tile.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET,
+ tile.getWidth(), tile.getHeight()));
+ }
+
+ /**
+ * Create a non-resizable non-movable terminal window.
+ *
+ * @param x the column number to place the top-left corner at. 0 is the
+ * left-most column.
+ * @param y the row number to place the top-left corner at. 0 is the
+ * top-most column.
+ * @param width the width of the window
+ * @param height the height of the window
+ * @param otherTile the other tile to resize when this window closes
+ */
+ private TTerminalWindow makeTile(int x, int y, int width, int height,
+ final TTerminalWindow otherTile) {
+
+ // We pass flags to disable the zoom (maximize) button, disable
+ // "smart" window placement, and set the specific location.
+ TTerminalWindow tile = new TTerminalWindow(this, x, y,
+ TWindow.NOZOOMBOX | TWindow.ABSOLUTEXY,
+ new String[] { "/bin/bash", "--login" }, true) {
+
+ /**
+ * When this terminal closes, if otherTile is defined then resize
+ * it to overcover me.
+ */
+ @Override
+ public void onClose() {
+ super.onClose();
+
+ if (otherTile != null) {
+ if (otherTile.getX() != getX()) {
+ // Undo the vertical split
+ otherTile.setX(Math.min(otherTile.getX(), getX()));
+ otherTile.setWidth(otherTile.getWidth() + getWidth());
+ }
+ if (otherTile.getY() != getY()) {
+ otherTile.setY(Math.min(otherTile.getY(), getY()));
+ otherTile.setHeight(otherTile.getHeight() + getHeight());
+ }
+ otherTile.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET,
+ otherTile.getWidth(), otherTile.getHeight()));
+ }
+ }
+
+ /**
+ * Prevent the user from resizing or moving this window.
+ */
+ @Override
+ public void onMouseDown(final TMouseEvent mouse) {
+ super.onMouseDown(mouse);
+ stopMovements();
+ }
+
+ /**
+ * Prevent the user from resizing or moving this window.
+ */
+ @Override
+ public void onKeypress(final TKeypressEvent keypress) {
+ super.onKeypress(keypress);
+ stopMovements();
+ }
+
+ /**
+ * Permit the user to use all of the menu items.
+ */
+ @Override
+ public void onIdle() {
+ super.onIdle();
+ removeShortcutKeypress(jexer.TKeypress.kbAltT);
+ removeShortcutKeypress(jexer.TKeypress.kbF6);
+ }
+
+ };
+
+ // The initial window size was stock VT100 80x24. Change that now,
+ // and then call onResize() to notify ptypipe to set the shell's
+ // window size.
+ tile.setWidth(width);
+ tile.setHeight(height);
+ tile.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET,
+ tile.getWidth(), tile.getHeight()));
+
+ return tile;
+ }
+
+}