This commit is contained in:
Kevin Lamonte 2019-08-20 08:47:14 -05:00
parent 0b5d7658ba
commit 2bc32111fa
8 changed files with 374 additions and 86 deletions

View file

@ -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
}
}

View 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(),

View 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;
}
}

View 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) {
super(parent, x, y, 80, 24);
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);
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);
}
super(parent, x, y, 80, 24);
/**
* 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);
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));
}
/**

View 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();
}
});
}
// ------------------------------------------------------------------------

View 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;

View 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;
}

View 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);
}