From 2ce6dab2bbd951e6d0f09f94759efda5ee4b65ac Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Sat, 18 Mar 2017 16:46:35 -0400 Subject: [PATCH] TStatusBar --- README.md | 17 +- docs/TODO.md | 5 - src/jexer/TApplication.java | 548 +++++++++++++---------- src/jexer/TCommand.java | 70 +-- src/jexer/TEditColorThemeWindow.java | 2 + src/jexer/TLabel.java | 3 +- src/jexer/TStatusBar.java | 305 +++++++++++++ src/jexer/TTerminalWindow.java | 15 +- src/jexer/TWidget.java | 158 ++++--- src/jexer/TWindow.java | 375 ++++++++++------ src/jexer/bits/ColorTheme.java | 17 + src/jexer/bits/GraphicsChars.java | 1 + src/jexer/demos/DemoCheckboxWindow.java | 8 + src/jexer/demos/DemoMainWindow.java | 131 +++--- src/jexer/demos/DemoMsgBoxWindow.java | 11 +- src/jexer/demos/DemoTextFieldWindow.java | 86 ++++ src/jexer/demos/DemoTextWindow.java | 8 + src/jexer/demos/DemoTreeViewWindow.java | 8 + src/jexer/io/package-info.java | 2 +- src/jexer/tterminal/ECMA48.java | 2 +- 20 files changed, 1212 insertions(+), 560 deletions(-) create mode 100644 src/jexer/TStatusBar.java create mode 100644 src/jexer/demos/DemoTextFieldWindow.java diff --git a/README.md b/README.md index 2f146cc..2243655 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Jexer currently supports three backends: sequences generated by the library itself: ncurses is not required or linked to. xterm mouse tracking using UTF8 and SGR coordinates are supported. For the demo application, this is the default - backend on non-Windows platforms. + backend on non-Windows/non-Mac platforms. * The same command-line ECMA-48 / ANSI X3.64 type terminal as above, but to any general InputStream/OutputStream or Reader/Writer. See @@ -23,9 +23,9 @@ Jexer currently supports three backends: character encoding than the default UTF-8. * Java Swing UI. This backend can be selected by setting - jexer.Swing=true. The default window size for Swing is 132x40, - which is set in jexer.session.SwingSession. For the demo - application, this is the default backend on Windows platforms. + jexer.Swing=true. The default window size for Swing is 80x25, which + is set in jexer.session.SwingSession. For the demo application, + this is the default backend on Windows and Mac platforms. Additional backends can be created by subclassing jexer.backend.Backend and passing it into the TApplication @@ -180,10 +180,11 @@ ambiguous. This section describes such issues. input (see the ENABLE_LINE_INPUT flag for GetConsoleMode() and SetConsoleMode()). - - TTerminalWindow launches 'script -fqe /dev/null' on non-Windows - platforms. This is a workaround for the C library behavior of - checking for a tty: script launches $SHELL in a pseudo-tty. This - works on Linux but might not on other Posix-y platforms. + - TTerminalWindow launches 'script -fqe /dev/null' or 'script -q -F + /dev/null' on non-Windows platforms. This is a workaround for the + C library behavior of checking for a tty: script launches $SHELL + in a pseudo-tty. This works on Linux and Mac but might not on + other Posix-y platforms. - Closing a TTerminalWindow without exiting the process inside it may result in a zombie 'script' process. diff --git a/docs/TODO.md b/docs/TODO.md index 47f07d0..f3e1233 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -7,11 +7,6 @@ Roadmap 0.0.4 -- TStatusBar - - TMenu version - - TWindow version - - Click mouse - - TWindow - "Smart placement" for new windows diff --git a/src/jexer/TApplication.java b/src/jexer/TApplication.java index 91d2e98..ff9c19a 100644 --- a/src/jexer/TApplication.java +++ b/src/jexer/TApplication.java @@ -58,12 +58,17 @@ import jexer.io.Screen; import jexer.menu.TMenu; import jexer.menu.TMenuItem; import static jexer.TCommand.*; +import static jexer.TKeypress.*; /** * TApplication sets up a full Text User Interface application. */ public class TApplication implements Runnable { + // ------------------------------------------------------------------------ + // Public constants ------------------------------------------------------- + // ------------------------------------------------------------------------ + /** * If true, emit thread stuff to System.err. */ @@ -94,6 +99,10 @@ public class TApplication implements Runnable { XTERM } + // ------------------------------------------------------------------------ + // Primary/secondary event handlers --------------------------------------- + // ------------------------------------------------------------------------ + /** * WidgetEventHandler is the main event consumer loop. There are at most * two such threads in existence: the primary for normal case and a @@ -365,6 +374,10 @@ public class TApplication implements Runnable { lockoutHandleEvent = false; } + // ------------------------------------------------------------------------ + // TApplication attributes ------------------------------------------------ + // ------------------------------------------------------------------------ + /** * Access to the physical screen, keyboard, and mouse. */ @@ -508,6 +521,23 @@ public class TApplication implements Runnable { return desktopBottom; } + // ------------------------------------------------------------------------ + // General behavior ------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Display the about dialog. + */ + protected void showAboutDialog() { + messageBox("About", "Jexer Version " + + this.getClass().getPackage().getImplementationVersion(), + TMessageBox.Type.OK); + } + + // ------------------------------------------------------------------------ + // Constructors ----------------------------------------------------------- + // ------------------------------------------------------------------------ + /** * Public constructor. * @@ -620,6 +650,10 @@ public class TApplication implements Runnable { (new Thread(primaryEventHandler)).start(); } + // ------------------------------------------------------------------------ + // Screen refresh loop ---------------------------------------------------- + // ------------------------------------------------------------------------ + /** * Invert the cell color at a position. This is used to track the mouse. * @@ -681,6 +715,7 @@ public class TApplication implements Runnable { // Draw each window in reverse Z order List sorted = new LinkedList(windows); Collections.sort(sorted); + TWindow topLevel = sorted.get(0); Collections.reverse(sorted); for (TWindow window: sorted) { window.drawChildren(); @@ -699,6 +734,7 @@ public class TApplication implements Runnable { if (menu.isActive()) { menuColor = theme.getColor("tmenu.highlighted"); menuMnemonicColor = theme.getColor("tmenu.mnemonic.highlighted"); + topLevel = menu; } else { menuColor = theme.getColor("tmenu"); menuMnemonicColor = theme.getColor("tmenu.mnemonic"); @@ -725,6 +761,20 @@ public class TApplication implements Runnable { menu.drawChildren(); } + // Draw the status bar of the top-level window + TStatusBar statusBar = topLevel.getStatusBar(); + if (statusBar != null) { + getScreen().resetClipping(); + statusBar.setWidth(getScreen().getWidth()); + statusBar.setY(getScreen().getHeight() - topLevel.getY()); + statusBar.draw(); + } else { + CellAttributes barColor = new CellAttributes(); + barColor.setTo(getTheme().getColor("tstatusbar.text")); + getScreen().hLineXY(0, desktopBottom, getScreen().getWidth(), ' ', + barColor); + } + // Draw the mouse pointer invertCell(mouseX, mouseY); oldMouseX = mouseX; @@ -754,6 +804,10 @@ public class TApplication implements Runnable { repaint = false; } + // ------------------------------------------------------------------------ + // Main loop -------------------------------------------------------------- + // ------------------------------------------------------------------------ + /** * Run this application until it exits. */ @@ -988,7 +1042,7 @@ public class TApplication implements Runnable { } } - if (!windowWillShortcut) { + if (!windowWillShortcut && !modalWindowActive()) { TKeypress keypressLowercase = keypress.getKey().toLowerCase(); TMenuItem item = null; synchronized (accelerators) { @@ -1001,11 +1055,11 @@ public class TApplication implements Runnable { return; } } - } - // Handle the keypress - if (onKeypress(keypress)) { - return; + // Handle the keypress + if (onKeypress(keypress)) { + return; + } } } @@ -1120,31 +1174,9 @@ public class TApplication implements Runnable { } } - /** - * Get the amount of time I can sleep before missing a Timer tick. - * - * @param timeout = initial (maximum) timeout in millis - * @return number of milliseconds between now and the next timer event - */ - private long getSleepTime(final long timeout) { - Date now = new Date(); - long nowTime = now.getTime(); - long sleepTime = timeout; - for (TTimer timer: timers) { - long nextTickTime = timer.getNextTick().getTime(); - if (nextTickTime < nowTime) { - return 0; - } - - long timeDifference = nextTickTime - nowTime; - if (timeDifference < sleepTime) { - sleepTime = timeDifference; - } - } - assert (sleepTime >= 0); - assert (sleepTime <= timeout); - return sleepTime; - } + // ------------------------------------------------------------------------ + // TWindow management ----------------------------------------------------- + // ------------------------------------------------------------------------ /** * Close window. Note that the window's destructor is NOT called by this @@ -1254,9 +1286,11 @@ public class TApplication implements Runnable { */ public final void addWindow(final TWindow window) { synchronized (windows) { - // Do not allow a modal window to spawn a non-modal window - if ((windows.size() > 0) && (windows.get(0).isModal())) { - assert (window.isModal()); + // Do not allow a modal window to spawn a non-modal window. If a + // modal window is active, then this window will become modal + // too. + if (modalWindowActive()) { + window.flags |= TWindow.MODAL; } for (TWindow w: windows) { if (w.isActive()) { @@ -1281,9 +1315,119 @@ public class TApplication implements Runnable { if (windows.size() == 0) { return false; } - return windows.get(windows.size() - 1).isModal(); + + for (TWindow w: windows) { + if (w.isModal()) { + return true; + } + } + + return false; + } + + /** + * Close all open windows. + */ + private void closeAllWindows() { + // Don't do anything if we are in the menu + if (activeMenu != null) { + return; + } + while (windows.size() > 0) { + closeWindow(windows.get(0)); + } } + /** + * Re-layout the open windows as non-overlapping tiles. This produces + * almost the same results as Turbo Pascal 7.0's IDE. + */ + private void tileWindows() { + synchronized (windows) { + // Don't do anything if we are in the menu + if (activeMenu != null) { + return; + } + int z = windows.size(); + if (z == 0) { + return; + } + int a = 0; + int b = 0; + a = (int)(Math.sqrt(z)); + int c = 0; + while (c < a) { + b = (z - c) / a; + if (((a * b) + c) == z) { + break; + } + c++; + } + assert (a > 0); + assert (b > 0); + assert (c < a); + int newWidth = (getScreen().getWidth() / a); + int newHeight1 = ((getScreen().getHeight() - 1) / b); + int newHeight2 = ((getScreen().getHeight() - 1) / (b + c)); + + List sorted = new LinkedList(windows); + Collections.sort(sorted); + Collections.reverse(sorted); + for (int i = 0; i < sorted.size(); i++) { + int logicalX = i / b; + int logicalY = i % b; + if (i >= ((a - 1) * b)) { + logicalX = a - 1; + logicalY = i - ((a - 1) * b); + } + + TWindow w = sorted.get(i); + w.setX(logicalX * newWidth); + w.setWidth(newWidth); + if (i >= ((a - 1) * b)) { + w.setY((logicalY * newHeight2) + 1); + w.setHeight(newHeight2); + } else { + w.setY((logicalY * newHeight1) + 1); + w.setHeight(newHeight1); + } + } + } + } + + /** + * Re-layout the open windows as overlapping cascaded windows. + */ + private void cascadeWindows() { + synchronized (windows) { + // Don't do anything if we are in the menu + if (activeMenu != null) { + return; + } + int x = 0; + int y = 1; + List sorted = new LinkedList(windows); + Collections.sort(sorted); + Collections.reverse(sorted); + for (TWindow window: sorted) { + window.setX(x); + window.setY(y); + x++; + y++; + if (x > getScreen().getWidth()) { + x = 0; + } + if (y >= getScreen().getHeight()) { + y = 1; + } + } + } + } + + // ------------------------------------------------------------------------ + // TMenu management ------------------------------------------------------- + // ------------------------------------------------------------------------ + /** * Check if a mouse event would hit either the active menu or any open * sub-menus. @@ -1485,127 +1629,6 @@ public class TApplication implements Runnable { } } - /** - * Method that TApplication subclasses can override to handle menu or - * posted command events. - * - * @param command command event - * @return if true, this event was consumed - */ - protected boolean onCommand(final TCommandEvent command) { - // Default: handle cmExit - if (command.equals(cmExit)) { - if (messageBox("Confirmation", "Exit application?", - TMessageBox.Type.YESNO).getResult() == TMessageBox.Result.YES) { - quit = true; - } - return true; - } - - if (command.equals(cmShell)) { - openTerminal(0, 0, TWindow.RESIZABLE); - return true; - } - - if (command.equals(cmTile)) { - tileWindows(); - return true; - } - if (command.equals(cmCascade)) { - cascadeWindows(); - return true; - } - if (command.equals(cmCloseAll)) { - closeAllWindows(); - return true; - } - - return false; - } - - /** - * Display the about dialog. - */ - protected void showAboutDialog() { - messageBox("About", "Jexer Version " + - this.getClass().getPackage().getImplementationVersion(), - TMessageBox.Type.OK); - } - - /** - * Method that TApplication subclasses can override to handle menu - * events. - * - * @param menu menu event - * @return if true, this event was consumed - */ - protected boolean onMenu(final TMenuEvent menu) { - - // Default: handle MID_EXIT - if (menu.getId() == TMenu.MID_EXIT) { - if (messageBox("Confirmation", "Exit application?", - TMessageBox.Type.YESNO).getResult() == TMessageBox.Result.YES) { - quit = true; - } - return true; - } - - if (menu.getId() == TMenu.MID_SHELL) { - openTerminal(0, 0, TWindow.RESIZABLE); - return true; - } - - if (menu.getId() == TMenu.MID_TILE) { - tileWindows(); - return true; - } - if (menu.getId() == TMenu.MID_CASCADE) { - cascadeWindows(); - return true; - } - if (menu.getId() == TMenu.MID_CLOSE_ALL) { - closeAllWindows(); - return true; - } - if (menu.getId() == TMenu.MID_ABOUT) { - showAboutDialog(); - return true; - } - return false; - } - - /** - * Method that TApplication subclasses can override to handle keystrokes. - * - * @param keypress keystroke event - * @return if true, this event was consumed - */ - protected boolean onKeypress(final TKeypressEvent keypress) { - // Default: only menu shortcuts - - // Process Alt-F, Alt-E, etc. menu shortcut keys - if (!keypress.getKey().isFnKey() - && keypress.getKey().isAlt() - && !keypress.getKey().isCtrl() - && (activeMenu == null) - ) { - - assert (subMenus.size() == 0); - - for (TMenu menu: menus) { - if (Character.toLowerCase(menu.getMnemonic().getShortcut()) - == Character.toLowerCase(keypress.getKey().getChar()) - ) { - activeMenu = menu; - menu.setActive(true); - return true; - } - } - } - - return false; - } - /** * Add a menu item to the global list. If it has a keyboard accelerator, * that will be added the global hash. @@ -1738,6 +1761,9 @@ public class TApplication implements Runnable { fileMenu.addSeparator(); fileMenu.addDefaultItem(TMenu.MID_SHELL); fileMenu.addDefaultItem(TMenu.MID_EXIT); + TStatusBar statusBar = fileMenu.newStatusBar("File-management " + + "commands (Open, Save, Print, etc.)"); + statusBar.addShortcutKeypress(kbF1, cmHelp, "Help"); return fileMenu; } @@ -1752,6 +1778,9 @@ public class TApplication implements Runnable { editMenu.addDefaultItem(TMenu.MID_COPY); editMenu.addDefaultItem(TMenu.MID_PASTE); editMenu.addDefaultItem(TMenu.MID_CLEAR); + TStatusBar statusBar = editMenu.newStatusBar("Editor operations, " + + "undo, and Clipboard access"); + statusBar.addShortcutKeypress(kbF1, cmHelp, "Help"); return editMenu; } @@ -1771,6 +1800,9 @@ public class TApplication implements Runnable { windowMenu.addDefaultItem(TMenu.MID_WINDOW_NEXT); windowMenu.addDefaultItem(TMenu.MID_WINDOW_PREVIOUS); windowMenu.addDefaultItem(TMenu.MID_WINDOW_CLOSE); + TStatusBar statusBar = windowMenu.newStatusBar("Open, arrange, and " + + "list windows"); + statusBar.addShortcutKeypress(kbF1, cmHelp, "Help"); return windowMenu; } @@ -1789,106 +1821,156 @@ public class TApplication implements Runnable { helpMenu.addDefaultItem(TMenu.MID_HELP_ACTIVE_FILE); helpMenu.addSeparator(); helpMenu.addDefaultItem(TMenu.MID_ABOUT); + TStatusBar statusBar = helpMenu.newStatusBar("Access online help"); + statusBar.addShortcutKeypress(kbF1, cmHelp, "Help"); return helpMenu; } + // ------------------------------------------------------------------------ + // Event handlers --------------------------------------------------------- + // ------------------------------------------------------------------------ + /** - * Close all open windows. + * Method that TApplication subclasses can override to handle menu or + * posted command events. + * + * @param command command event + * @return if true, this event was consumed */ - private void closeAllWindows() { - // Don't do anything if we are in the menu - if (activeMenu != null) { - return; + protected boolean onCommand(final TCommandEvent command) { + // Default: handle cmExit + if (command.equals(cmExit)) { + if (messageBox("Confirmation", "Exit application?", + TMessageBox.Type.YESNO).getResult() == TMessageBox.Result.YES) { + quit = true; + } + return true; } - while (windows.size() > 0) { - closeWindow(windows.get(0)); + + if (command.equals(cmShell)) { + openTerminal(0, 0, TWindow.RESIZABLE); + return true; + } + + if (command.equals(cmTile)) { + tileWindows(); + return true; + } + if (command.equals(cmCascade)) { + cascadeWindows(); + return true; + } + if (command.equals(cmCloseAll)) { + closeAllWindows(); + return true; } + + return false; } /** - * Re-layout the open windows as non-overlapping tiles. This produces - * almost the same results as Turbo Pascal 7.0's IDE. + * Method that TApplication subclasses can override to handle menu + * events. + * + * @param menu menu event + * @return if true, this event was consumed */ - private void tileWindows() { - synchronized (windows) { - // Don't do anything if we are in the menu - if (activeMenu != null) { - return; - } - int z = windows.size(); - if (z == 0) { - return; - } - int a = 0; - int b = 0; - a = (int)(Math.sqrt(z)); - int c = 0; - while (c < a) { - b = (z - c) / a; - if (((a * b) + c) == z) { - break; - } - c++; + protected boolean onMenu(final TMenuEvent menu) { + + // Default: handle MID_EXIT + if (menu.getId() == TMenu.MID_EXIT) { + if (messageBox("Confirmation", "Exit application?", + TMessageBox.Type.YESNO).getResult() == TMessageBox.Result.YES) { + quit = true; } - assert (a > 0); - assert (b > 0); - assert (c < a); - int newWidth = (getScreen().getWidth() / a); - int newHeight1 = ((getScreen().getHeight() - 1) / b); - int newHeight2 = ((getScreen().getHeight() - 1) / (b + c)); + return true; + } - List sorted = new LinkedList(windows); - Collections.sort(sorted); - Collections.reverse(sorted); - for (int i = 0; i < sorted.size(); i++) { - int logicalX = i / b; - int logicalY = i % b; - if (i >= ((a - 1) * b)) { - logicalX = a - 1; - logicalY = i - ((a - 1) * b); - } + if (menu.getId() == TMenu.MID_SHELL) { + openTerminal(0, 0, TWindow.RESIZABLE); + return true; + } - TWindow w = sorted.get(i); - w.setX(logicalX * newWidth); - w.setWidth(newWidth); - if (i >= ((a - 1) * b)) { - w.setY((logicalY * newHeight2) + 1); - w.setHeight(newHeight2); - } else { - w.setY((logicalY * newHeight1) + 1); - w.setHeight(newHeight1); + if (menu.getId() == TMenu.MID_TILE) { + tileWindows(); + return true; + } + if (menu.getId() == TMenu.MID_CASCADE) { + cascadeWindows(); + return true; + } + if (menu.getId() == TMenu.MID_CLOSE_ALL) { + closeAllWindows(); + return true; + } + if (menu.getId() == TMenu.MID_ABOUT) { + showAboutDialog(); + return true; + } + return false; + } + + /** + * Method that TApplication subclasses can override to handle keystrokes. + * + * @param keypress keystroke event + * @return if true, this event was consumed + */ + protected boolean onKeypress(final TKeypressEvent keypress) { + // Default: only menu shortcuts + + // Process Alt-F, Alt-E, etc. menu shortcut keys + if (!keypress.getKey().isFnKey() + && keypress.getKey().isAlt() + && !keypress.getKey().isCtrl() + && (activeMenu == null) + && !modalWindowActive() + ) { + + assert (subMenus.size() == 0); + + for (TMenu menu: menus) { + if (Character.toLowerCase(menu.getMnemonic().getShortcut()) + == Character.toLowerCase(keypress.getKey().getChar()) + ) { + activeMenu = menu; + menu.setActive(true); + return true; } } } + + return false; } + // ------------------------------------------------------------------------ + // TTimer management ------------------------------------------------------ + // ------------------------------------------------------------------------ + /** - * Re-layout the open windows as overlapping cascaded windows. + * Get the amount of time I can sleep before missing a Timer tick. + * + * @param timeout = initial (maximum) timeout in millis + * @return number of milliseconds between now and the next timer event */ - private void cascadeWindows() { - synchronized (windows) { - // Don't do anything if we are in the menu - if (activeMenu != null) { - return; + private long getSleepTime(final long timeout) { + Date now = new Date(); + long nowTime = now.getTime(); + long sleepTime = timeout; + for (TTimer timer: timers) { + long nextTickTime = timer.getNextTick().getTime(); + if (nextTickTime < nowTime) { + return 0; } - int x = 0; - int y = 1; - List sorted = new LinkedList(windows); - Collections.sort(sorted); - Collections.reverse(sorted); - for (TWindow window: sorted) { - window.setX(x); - window.setY(y); - x++; - y++; - if (x > getScreen().getWidth()) { - x = 0; - } - if (y >= getScreen().getHeight()) { - y = 1; - } + + long timeDifference = nextTickTime - nowTime; + if (timeDifference < sleepTime) { + sleepTime = timeDifference; } } + assert (sleepTime >= 0); + assert (sleepTime <= timeout); + return sleepTime; } /** @@ -1920,6 +2002,10 @@ public class TApplication implements Runnable { } } + // ------------------------------------------------------------------------ + // Other TWindow constructors --------------------------------------------- + // ------------------------------------------------------------------------ + /** * Convenience function to spawn a message box. * diff --git a/src/jexer/TCommand.java b/src/jexer/TCommand.java index 0bb3f95..584994f 100644 --- a/src/jexer/TCommand.java +++ b/src/jexer/TCommand.java @@ -39,82 +39,87 @@ public class TCommand { * Immediately abort the application (e.g. remote side closed * connection). */ - public static final int ABORT = 1; + public static final int ABORT = 1; /** * File open dialog. */ - public static final int OPEN = 2; + public static final int OPEN = 2; /** * Exit application. */ - public static final int EXIT = 3; + public static final int EXIT = 3; /** * Spawn OS shell window. */ - public static final int SHELL = 4; + public static final int SHELL = 4; /** * Cut selected text and copy to the clipboard. */ - public static final int CUT = 5; + public static final int CUT = 5; /** * Copy selected text to clipboard. */ - public static final int COPY = 6; + public static final int COPY = 6; /** * Paste from clipboard. */ - public static final int PASTE = 7; + public static final int PASTE = 7; /** * Clear selected text without copying it to the clipboard. */ - public static final int CLEAR = 8; + public static final int CLEAR = 8; /** * Tile windows. */ - public static final int TILE = 9; + public static final int TILE = 9; /** * Cascade windows. */ - public static final int CASCADE = 10; + public static final int CASCADE = 10; /** * Close all windows. */ - public static final int CLOSE_ALL = 11; + public static final int CLOSE_ALL = 11; /** * Move (move/resize) window. */ - public static final int WINDOW_MOVE = 12; + public static final int WINDOW_MOVE = 12; /** * Zoom (maximize/restore) window. */ - public static final int WINDOW_ZOOM = 13; + public static final int WINDOW_ZOOM = 13; /** * Next window (like Alt-TAB). */ - public static final int WINDOW_NEXT = 14; + public static final int WINDOW_NEXT = 14; /** * Previous window (like Shift-Alt-TAB). */ - public static final int WINDOW_PREVIOUS = 15; + public static final int WINDOW_PREVIOUS = 15; /** * Close window. */ - public static final int WINDOW_CLOSE = 16; + public static final int WINDOW_CLOSE = 16; + + /** + * Enter help system. + */ + public static final int HELP = 20; /** * Type of command, one of EXIT, CASCADE, etc. @@ -166,22 +171,23 @@ public class TCommand { return type; } - public static final TCommand cmAbort = new TCommand(ABORT); - public static final TCommand cmExit = new TCommand(EXIT); - public static final TCommand cmQuit = new TCommand(EXIT); - public static final TCommand cmOpen = new TCommand(OPEN); - public static final TCommand cmShell = new TCommand(SHELL); - public static final TCommand cmCut = new TCommand(CUT); - public static final TCommand cmCopy = new TCommand(COPY); - public static final TCommand cmPaste = new TCommand(PASTE); - public static final TCommand cmClear = new TCommand(CLEAR); - public static final TCommand cmTile = new TCommand(TILE); - public static final TCommand cmCascade = new TCommand(CASCADE); - public static final TCommand cmCloseAll = new TCommand(CLOSE_ALL); - public static final TCommand cmWindowMove = new TCommand(WINDOW_MOVE); - public static final TCommand cmWindowZoom = new TCommand(WINDOW_ZOOM); - public static final TCommand cmWindowNext = new TCommand(WINDOW_NEXT); + public static final TCommand cmAbort = new TCommand(ABORT); + public static final TCommand cmExit = new TCommand(EXIT); + public static final TCommand cmQuit = new TCommand(EXIT); + public static final TCommand cmOpen = new TCommand(OPEN); + public static final TCommand cmShell = new TCommand(SHELL); + public static final TCommand cmCut = new TCommand(CUT); + public static final TCommand cmCopy = new TCommand(COPY); + public static final TCommand cmPaste = new TCommand(PASTE); + public static final TCommand cmClear = new TCommand(CLEAR); + public static final TCommand cmTile = new TCommand(TILE); + public static final TCommand cmCascade = new TCommand(CASCADE); + public static final TCommand cmCloseAll = new TCommand(CLOSE_ALL); + public static final TCommand cmWindowMove = new TCommand(WINDOW_MOVE); + public static final TCommand cmWindowZoom = new TCommand(WINDOW_ZOOM); + public static final TCommand cmWindowNext = new TCommand(WINDOW_NEXT); public static final TCommand cmWindowPrevious = new TCommand(WINDOW_PREVIOUS); - public static final TCommand cmWindowClose = new TCommand(WINDOW_CLOSE); + public static final TCommand cmWindowClose = new TCommand(WINDOW_CLOSE); + public static final TCommand cmHelp = new TCommand(HELP); } diff --git a/src/jexer/TEditColorThemeWindow.java b/src/jexer/TEditColorThemeWindow.java index 976c895..47197d4 100644 --- a/src/jexer/TEditColorThemeWindow.java +++ b/src/jexer/TEditColorThemeWindow.java @@ -707,6 +707,8 @@ public final class TEditColorThemeWindow extends TWindow { // Default to the color list activate(colorNames); + // Add shortcut text + newStatusBar("Select Colors"); } /** diff --git a/src/jexer/TLabel.java b/src/jexer/TLabel.java index f5e9665..d6b817a 100644 --- a/src/jexer/TLabel.java +++ b/src/jexer/TLabel.java @@ -99,7 +99,8 @@ public final class TLabel extends TWidget { /** * Draw a static label. */ - @Override public void draw() { + @Override + public void draw() { // Setup my color CellAttributes color = new CellAttributes(); color.setTo(getTheme().getColor(colorKey)); diff --git a/src/jexer/TStatusBar.java b/src/jexer/TStatusBar.java new file mode 100644 index 0000000..d03d526 --- /dev/null +++ b/src/jexer/TStatusBar.java @@ -0,0 +1,305 @@ +/* + * Jexer - Java Text User Interface + * + * The MIT License (MIT) + * + * Copyright (C) 2016 Kevin Lamonte + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * @author Kevin Lamonte [kevin.lamonte@gmail.com] + * @version 1 + */ +package jexer; + +import java.util.ArrayList; + +import jexer.bits.CellAttributes; +import jexer.bits.GraphicsChars; +import jexer.event.TCommandEvent; +import jexer.event.TKeypressEvent; +import jexer.event.TMouseEvent; + +/** + * TStatusBar implements a status line with clickable buttons. + */ +public final class TStatusBar extends TWidget { + + /** + * A single shortcut key. + */ + private class TStatusBarKey { + + /** + * The keypress for this action. + */ + public TKeypress key; + + /** + * The command to issue. + */ + public TCommand cmd; + + /** + * The label text. + */ + public String label; + + /** + * If true, the mouse is on this key. + */ + public boolean selected; + + /** + * The left edge coordinate to draw this key with. + */ + public int x = 0; + + /** + * The width of this key on the screen. + * + * @return the number of columns this takes when drawn + */ + public int width() { + return this.label.length() + this.key.toString().length() + 3; + } + + /** + * Add a key to this status bar. + * + * @param key the key to trigger on + * @param cmd the command event to issue when key is pressed or this + * item is clicked + * @param label the label for this action + */ + public TStatusBarKey(final TKeypress key, final TCommand cmd, + final String label) { + + this.key = key; + this.cmd = cmd; + this.label = label; + } + + } + + /** + * Remember mouse state. + */ + private TMouseEvent mouse; + + /** + * The text to display on the right side of the shortcut keys. + */ + private String text = null; + + /** + * The shortcut keys. + */ + private ArrayList keys = new ArrayList(); + + /** + * Add a key to this status bar. + * + * @param key the key to trigger on + * @param cmd the command event to issue when key is pressed or this item + * is clicked + * @param label the label for this action + */ + public void addShortcutKeypress(final TKeypress key, final TCommand cmd, + final String label) { + + TStatusBarKey newKey = new TStatusBarKey(key, cmd, label); + if (keys.size() > 0) { + TStatusBarKey oldKey = keys.get(keys.size() - 1); + newKey.x = oldKey.x + oldKey.width(); + } + keys.add(newKey); + } + + /** + * Set the text to display on the right side of the shortcut keys. + * + * @param text the new text + */ + public void setText(final String text) { + this.text = text; + } + + /** + * Public constructor. + * + * @param parent parent widget + * @param text text for the bar on the bottom row + */ + public TStatusBar(final TWidget parent, final String text) { + + // Set parent and window + super(parent, false, 0, 0, text.length(), 1); + + this.text = text; + } + + /** + * Public constructor. + * + * @param parent parent widget + */ + public TStatusBar(final TWidget parent) { + this(parent, ""); + } + + /** + * Draw the bar. + */ + @Override + public void draw() { + CellAttributes barColor = new CellAttributes(); + barColor.setTo(getTheme().getColor("tstatusbar.text")); + CellAttributes keyColor = new CellAttributes(); + keyColor.setTo(getTheme().getColor("tstatusbar.button")); + CellAttributes selectedColor = new CellAttributes(); + selectedColor.setTo(getTheme().getColor("tstatusbar.selected")); + + // Status bar is weird. Its draw() method is called directly by + // TApplication after everything is drawn, and after + // Screen.resetClipping(). So at this point we are drawing in + // absolute coordinates, not relative to our TWindow. + int row = getScreen().getHeight() - 1; + int width = getScreen().getWidth(); + + getScreen().hLineXY(0, row, width, ' ', barColor); + + int col = 0; + for (TStatusBarKey key: keys) { + String keyStr = key.key.toString(); + if (key.selected) { + getScreen().putCharXY(col++, row, ' ', selectedColor); + getScreen().putStringXY(col, row, keyStr, selectedColor); + col += keyStr.length(); + getScreen().putCharXY(col++, row, ' ', selectedColor); + getScreen().putStringXY(col, row, key.label, selectedColor); + col += key.label.length(); + getScreen().putCharXY(col++, row, ' ', selectedColor); + } else { + getScreen().putCharXY(col++, row, ' ', barColor); + getScreen().putStringXY(col, row, keyStr, keyColor); + col += keyStr.length() + 1; + getScreen().putStringXY(col, row, key.label, barColor); + col += key.label.length(); + getScreen().putCharXY(col++, row, ' ', barColor); + } + } + if (text.length() > 0) { + if (keys.size() > 0) { + getScreen().putCharXY(col++, row, GraphicsChars.VERTICAL_BAR, + barColor); + } + getScreen().putCharXY(col++, row, ' ', barColor); + getScreen().putStringXY(col, row, text, barColor); + } + } + + /** + * Handle keypresses. + * + * @param keypress keystroke event + * @return true if this keypress was consumed + */ + public boolean statusBarKeypress(final TKeypressEvent keypress) { + for (TStatusBarKey key: keys) { + if (keypress.equals(key.key)) { + getApplication().postMenuEvent(new TCommandEvent(key.cmd)); + return true; + } + } + return false; + } + + /** + * Returns true if the mouse is currently on the button. + * + * @param statusBarKey the status bar item + * @return if true the mouse is currently on the button + */ + private boolean mouseOnShortcut(final TStatusBarKey statusBarKey) { + if ((mouse != null) + && (mouse.getAbsoluteY() == getApplication().getDesktopBottom()) + && (mouse.getAbsoluteX() >= statusBarKey.x) + && (mouse.getAbsoluteX() < statusBarKey.x + statusBarKey.width()) + ) { + return true; + } + return false; + } + + /** + * Handle mouse button presses. + * + * @param mouse mouse button event + * @return true if this mouse event was consumed + */ + public boolean statusBarMouseDown(final TMouseEvent mouse) { + this.mouse = mouse; + + for (TStatusBarKey key: keys) { + if ((mouseOnShortcut(key)) && (mouse.isMouse1())) { + key.selected = true; + return true; + } + } + return false; + } + + /** + * Handle mouse button releases. + * + * @param mouse mouse button release event + * @return true if this mouse event was consumed + */ + public boolean statusBarMouseUp(final TMouseEvent mouse) { + this.mouse = mouse; + + for (TStatusBarKey key: keys) { + if (key.selected && mouse.isMouse1()) { + key.selected = false; + + // Dispatch the event + getApplication().postMenuEvent(new TCommandEvent(key.cmd)); + return true; + } + } + return false; + } + + /** + * Handle mouse movements. + * + * @param mouse mouse motion event + */ + public void statusBarMouseMotion(final TMouseEvent mouse) { + this.mouse = mouse; + + for (TStatusBarKey key: keys) { + if (!mouseOnShortcut(key)) { + key.selected = false; + } + } + } + +} diff --git a/src/jexer/TTerminalWindow.java b/src/jexer/TTerminalWindow.java index c0af74b..50c87d6 100644 --- a/src/jexer/TTerminalWindow.java +++ b/src/jexer/TTerminalWindow.java @@ -166,7 +166,7 @@ public class TTerminalWindow extends TWindow { "script", "-fqe", "/dev/null" }; String [] cmdShellBSD = { - "script", "-qe", "-F", "/dev/null" + "script", "-q", "-F", "/dev/null" }; // Spawn a shell and pass its I/O to the other constructor. @@ -200,6 +200,9 @@ public class TTerminalWindow extends TWindow { // Claim the keystrokes the emulator will need. addShortcutKeys(); + + // Add shortcut text + newStatusBar("Terminal session executing..."); } /** @@ -263,6 +266,9 @@ public class TTerminalWindow extends TWindow { // Claim the keystrokes the emulator will need. addShortcutKeys(); + + // Add shortcut text + newStatusBar("Terminal session executing..."); } /** @@ -358,7 +364,8 @@ public class TTerminalWindow extends TWindow { /** * Handle window close. */ - @Override public void onClose() { + @Override + public void onClose() { emulator.close(); if (shell != null) { terminateShellChildProcess(); @@ -406,6 +413,8 @@ public class TTerminalWindow extends TWindow { shell = null; emulator.close(); clearShortcutKeypresses(); + statusBar.setText("Terminal session completed, exit " + + "code " + rc + "."); } catch (IllegalThreadStateException e) { // The emulator thread has exited, but the shell Process // hasn't figured that out yet. Do nothing, we will see @@ -421,6 +430,8 @@ public class TTerminalWindow extends TWindow { shell = null; emulator.close(); clearShortcutKeypresses(); + statusBar.setText("Terminal session completed, exit " + + "code " + rc + "."); } catch (IllegalThreadStateException e) { // The shell is still running, do nothing. } diff --git a/src/jexer/TWidget.java b/src/jexer/TWidget.java index 71032ab..4dfe5a1 100644 --- a/src/jexer/TWidget.java +++ b/src/jexer/TWidget.java @@ -30,7 +30,7 @@ package jexer; import java.io.IOException; import java.util.List; -import java.util.LinkedList; +import java.util.ArrayList; import jexer.bits.ColorTheme; import jexer.event.TCommandEvent; @@ -49,6 +49,10 @@ import static jexer.TKeypress.*; */ public abstract class TWidget implements Comparable { + // ------------------------------------------------------------------------ + // Common widget attributes ----------------------------------------------- + // ------------------------------------------------------------------------ + /** * Every widget has a parent widget that it may be "contained" in. For * example, a TWindow might contain several TTextFields, or a TComboBox @@ -65,44 +69,6 @@ public abstract class TWidget implements Comparable { return parent; } - /** - * Backdoor access for TWindow's constructor. ONLY TWindow USES THIS. - * - * @param window the top-level window - * @param x column relative to parent - * @param y row relative to parent - * @param width width of window - * @param height height of window - */ - protected final void setupForTWindow(final TWindow window, - final int x, final int y, final int width, final int height) { - - this.parent = window; - this.window = window; - this.x = x; - this.y = y; - this.width = width; - this.height = height; - } - - /** - * Get this TWidget's parent TApplication. - * - * @return the parent TApplication - */ - public TApplication getApplication() { - return window.getApplication(); - } - - /** - * Get the Screen. - * - * @return the Screen - */ - public Screen getScreen() { - return window.getScreen(); - } - /** * Child widgets that this widget contains. */ @@ -368,6 +334,28 @@ public abstract class TWidget implements Comparable { this.cursorY = cursorY; } + // ------------------------------------------------------------------------ + // TApplication integration ----------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Get this TWidget's parent TApplication. + * + * @return the parent TApplication + */ + public TApplication getApplication() { + return window.getApplication(); + } + + /** + * Get the Screen. + * + * @return the Screen + */ + public Screen getScreen() { + return window.getScreen(); + } + /** * Comparison operator. For various subclasses it sorts on: *
    @@ -492,6 +480,12 @@ public abstract class TWidget implements Comparable { assert (getScreen() != null); Screen screen = getScreen(); + // Special case: TStatusBar is drawn by TApplication, not anything + // else. + if (this instanceof TStatusBar) { + return; + } + screen.setClipRight(width); screen.setClipBottom(height); @@ -533,11 +527,15 @@ public abstract class TWidget implements Comparable { } } + // ------------------------------------------------------------------------ + // Constructors ----------------------------------------------------------- + // ------------------------------------------------------------------------ + /** * Default constructor for subclasses. */ protected TWidget() { - children = new LinkedList(); + children = new ArrayList(); } /** @@ -574,7 +572,7 @@ public abstract class TWidget implements Comparable { this.enabled = enabled; this.parent = parent; this.window = parent.window; - children = new LinkedList(); + children = new ArrayList(); parent.addChild(this); } @@ -594,7 +592,7 @@ public abstract class TWidget implements Comparable { this.enabled = enabled; this.parent = parent; this.window = parent.window; - children = new LinkedList(); + children = new ArrayList(); parent.addChild(this); this.x = x; @@ -603,6 +601,30 @@ public abstract class TWidget implements Comparable { this.height = height; } + /** + * Backdoor access for TWindow's constructor. ONLY TWindow USES THIS. + * + * @param window the top-level window + * @param x column relative to parent + * @param y row relative to parent + * @param width width of window + * @param height height of window + */ + protected final void setupForTWindow(final TWindow window, + final int x, final int y, final int width, final int height) { + + this.parent = window; + this.window = window; + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + // ------------------------------------------------------------------------ + // General behavior ------------------------------------------------------- + // ------------------------------------------------------------------------ + /** * Add a child widget to my list of children. We set its tabOrder to 0 * and increment the tabOrder of all other children. @@ -752,6 +774,33 @@ public abstract class TWidget implements Comparable { return this; } + // ------------------------------------------------------------------------ + // Event handlers --------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Check if a mouse press/release event coordinate is contained in this + * widget. + * + * @param mouse a mouse-based event + * @return whether or not a mouse click would be sent to this widget + */ + public final boolean mouseWouldHit(final TMouseEvent mouse) { + + if (!enabled) { + return false; + } + + if ((mouse.getAbsoluteX() >= getAbsoluteX()) + && (mouse.getAbsoluteX() < getAbsoluteX() + width) + && (mouse.getAbsoluteY() >= getAbsoluteY()) + && (mouse.getAbsoluteY() < getAbsoluteY() + height) + ) { + return true; + } + return false; + } + /** * Method that subclasses can override to handle keystrokes. * @@ -971,28 +1020,9 @@ public abstract class TWidget implements Comparable { return; } - /** - * Check if a mouse press/release event coordinate is contained in this - * widget. - * - * @param mouse a mouse-based event - * @return whether or not a mouse click would be sent to this widget - */ - public final boolean mouseWouldHit(final TMouseEvent mouse) { - - if (!enabled) { - return false; - } - - if ((mouse.getAbsoluteX() >= getAbsoluteX()) - && (mouse.getAbsoluteX() < getAbsoluteX() + width) - && (mouse.getAbsoluteY() >= getAbsoluteY()) - && (mouse.getAbsoluteY() < getAbsoluteY() + height) - ) { - return true; - } - return false; - } + // ------------------------------------------------------------------------ + // Other TWidget constructors --------------------------------------------- + // ------------------------------------------------------------------------ /** * Convenience function to add a label to this container/window. diff --git a/src/jexer/TWindow.java b/src/jexer/TWindow.java index 7302695..32d94b9 100644 --- a/src/jexer/TWindow.java +++ b/src/jexer/TWindow.java @@ -48,30 +48,33 @@ import static jexer.TKeypress.*; */ public class TWindow extends TWidget { + // ------------------------------------------------------------------------ + // Public constants ------------------------------------------------------- + // ------------------------------------------------------------------------ + /** - * Window's parent TApplication. + * Window is resizable (default yes). */ - private TApplication application; + public static final int RESIZABLE = 0x01; /** - * Get this TWindow's parent TApplication. - * - * @return this TWindow's parent TApplication + * Window is modal (default no). */ - @Override - public final TApplication getApplication() { - return application; - } + public static final int MODAL = 0x02; /** - * Get the Screen. - * - * @return the Screen + * Window is centered (default no). */ - @Override - public final Screen getScreen() { - return application.getScreen(); - } + public static final int CENTERED = 0x04; + + // ------------------------------------------------------------------------ + // Common window attributes ----------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Window flags. Note package private access. + */ + int flags = RESIZABLE; /** * Window title. @@ -96,25 +99,34 @@ public class TWindow extends TWidget { this.title = title; } - /** - * Window is resizable (default yes). - */ - public static final int RESIZABLE = 0x01; + // ------------------------------------------------------------------------ + // TApplication integration ----------------------------------------------- + // ------------------------------------------------------------------------ /** - * Window is modal (default no). + * Window's parent TApplication. */ - public static final int MODAL = 0x02; + private TApplication application; /** - * Window is centered (default no). + * Get this TWindow's parent TApplication. + * + * @return this TWindow's parent TApplication */ - public static final int CENTERED = 0x04; + @Override + public final TApplication getApplication() { + return application; + } /** - * Window flags. + * Get the Screen. + * + * @return the Screen */ - private int flags = RESIZABLE; + @Override + public final Screen getScreen() { + return application.getScreen(); + } /** * Z order. Lower number means more in-front. @@ -181,6 +193,37 @@ public class TWindow extends TWidget { return keyboardShortcuts.contains(key); } + /** + * A window may have a status bar associated with it. TApplication will + * draw this status bar last, and will also route events to it first + * before the window. + */ + protected TStatusBar statusBar = null; + + /** + * Get the window's status bar, or null if it does not have one. + * + * @return the status bar, or null + */ + public TStatusBar getStatusBar() { + return statusBar; + } + + /** + * Set the window's status bar to a new one. + * + * @param text the status bar text + * @return the status bar + */ + public TStatusBar newStatusBar(final String text) { + statusBar = new TStatusBar(this, text); + return statusBar; + } + + // ------------------------------------------------------------------------ + // Window movement/resizing support --------------------------------------- + // ------------------------------------------------------------------------ + /** * If true, then the user clicked on the title bar and is moving the * window. @@ -238,6 +281,55 @@ public class TWindow extends TWidget { this.maximumWindowWidth = maximumWindowWidth; } + /** + * Recenter the window on-screen. + */ + public final void center() { + if ((flags & CENTERED) != 0) { + if (getWidth() < getScreen().getWidth()) { + setX((getScreen().getWidth() - getWidth()) / 2); + } else { + setX(0); + } + setY(((application.getDesktopBottom() + - application.getDesktopTop()) - getHeight()) / 2); + if (getY() < 0) { + setY(0); + } + setY(getY() + application.getDesktopTop()); + } + } + + /** + * Maximize window. + */ + private void maximize() { + restoreWindowWidth = getWidth(); + restoreWindowHeight = getHeight(); + restoreWindowX = getX(); + restoreWindowY = getY(); + setWidth(getScreen().getWidth()); + setHeight(application.getDesktopBottom() - 1); + setX(0); + setY(1); + maximized = true; + } + + /** + * Restote (unmaximize) window. + */ + private void restore() { + setWidth(restoreWindowWidth); + setHeight(restoreWindowHeight); + setX(restoreWindowX); + setY(restoreWindowY); + maximized = false; + } + + // ------------------------------------------------------------------------ + // Constructors ----------------------------------------------------------- + // ------------------------------------------------------------------------ + /** * Public constructor. Window will be located at (0, 0). * @@ -325,24 +417,9 @@ public class TWindow extends TWidget { application.addWindow(this); } - /** - * Recenter the window on-screen. - */ - public final void center() { - if ((flags & CENTERED) != 0) { - if (getWidth() < getScreen().getWidth()) { - setX((getScreen().getWidth() - getWidth()) / 2); - } else { - setX(0); - } - setY(((application.getDesktopBottom() - - application.getDesktopTop()) - getHeight()) / 2); - if (getY() < 0) { - setY(0); - } - setY(getY() + application.getDesktopTop()); - } - } + // ------------------------------------------------------------------------ + // General behavior ------------------------------------------------------- + // ------------------------------------------------------------------------ /** * Returns true if this window is modal. @@ -356,57 +433,6 @@ public class TWindow extends TWidget { return true; } - /** - * Returns true if the mouse is currently on the close button. - * - * @return true if mouse is currently on the close button - */ - private boolean mouseOnClose() { - if ((mouse != null) - && (mouse.getAbsoluteY() == getY()) - && (mouse.getAbsoluteX() == getX() + 3) - ) { - return true; - } - 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 - */ - private boolean mouseOnMaximize() { - if ((mouse != null) - && !isModal() - && (mouse.getAbsoluteY() == getY()) - && (mouse.getAbsoluteX() == getX() + getWidth() - 4) - ) { - return true; - } - 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 - */ - private boolean mouseOnResize() { - if (((flags & RESIZABLE) != 0) - && !isModal() - && (mouse != null) - && (mouse.getAbsoluteY() == getY() + getHeight() - 1) - && ((mouse.getAbsoluteX() == getX() + getWidth() - 1) - || (mouse.getAbsoluteX() == getX() + getWidth() - 2)) - ) { - return true; - } - return false; - } - /** * Retrieve the background color. * @@ -491,30 +517,6 @@ public class TWindow extends TWidget { } } - /** - * Subclasses should override this method to cleanup resources. This is - * called by application.closeWindow(). - */ - public void onClose() { - // Default: do nothing - } - - /** - * Called by application.switchWindow() when this window gets the - * focus, and also by application.addWindow(). - */ - public void onFocus() { - // Default: do nothing - } - - /** - * Called by application.switchWindow() when another window gets the - * focus. - */ - public void onUnfocus() { - // Default: do nothing - } - /** * Called by TApplication.drawChildren() to render on screen. */ @@ -582,6 +584,85 @@ public class TWindow extends TWidget { } } + // ------------------------------------------------------------------------ + // Event handlers --------------------------------------------------------- + // ------------------------------------------------------------------------ + + /** + * Returns true if the mouse is currently on the close button. + * + * @return true if mouse is currently on the close button + */ + private boolean mouseOnClose() { + if ((mouse != null) + && (mouse.getAbsoluteY() == getY()) + && (mouse.getAbsoluteX() == getX() + 3) + ) { + return true; + } + 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 + */ + private boolean mouseOnMaximize() { + if ((mouse != null) + && !isModal() + && (mouse.getAbsoluteY() == getY()) + && (mouse.getAbsoluteX() == getX() + getWidth() - 4) + ) { + return true; + } + 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 + */ + private boolean mouseOnResize() { + if (((flags & RESIZABLE) != 0) + && !isModal() + && (mouse != null) + && (mouse.getAbsoluteY() == getY() + getHeight() - 1) + && ((mouse.getAbsoluteX() == getX() + getWidth() - 1) + || (mouse.getAbsoluteX() == getX() + getWidth() - 2)) + ) { + return true; + } + return false; + } + + /** + * Subclasses should override this method to cleanup resources. This is + * called by application.closeWindow(). + */ + public void onClose() { + // Default: do nothing + } + + /** + * Called by application.switchWindow() when this window gets the + * focus, and also by application.addWindow(). + */ + public void onFocus() { + // Default: do nothing + } + + /** + * Called by application.switchWindow() when another window gets the + * focus. + */ + public void onUnfocus() { + // Default: do nothing + } + /** * Handle mouse button presses. * @@ -624,36 +705,17 @@ public class TWindow extends TWidget { return; } + // Give the shortcut bar a shot at this. + if (statusBar != null) { + if (statusBar.statusBarMouseDown(mouse)) { + return; + } + } + // I didn't take it, pass it on to my children super.onMouseDown(mouse); } - /** - * Maximize window. - */ - private void maximize() { - restoreWindowWidth = getWidth(); - restoreWindowHeight = getHeight(); - restoreWindowX = getX(); - restoreWindowY = getY(); - setWidth(getScreen().getWidth()); - setHeight(application.getDesktopBottom() - 1); - setX(0); - setY(1); - maximized = true; - } - - /** - * Restote (unmaximize) window. - */ - private void restore() { - setWidth(restoreWindowWidth); - setHeight(restoreWindowHeight); - setX(restoreWindowX); - setY(restoreWindowY); - maximized = false; - } - /** * Handle mouse button releases. * @@ -697,6 +759,13 @@ public class TWindow extends TWidget { return; } + // Give the shortcut bar a shot at this. + if (statusBar != null) { + if (statusBar.statusBarMouseUp(mouse)) { + return; + } + } + // I didn't take it, pass it on to my children super.onMouseUp(mouse); } @@ -718,6 +787,10 @@ public class TWindow extends TWidget { if (getY() < application.getDesktopTop()) { setY(application.getDesktopTop()); } + // Don't go below the status bar + if (getY() >= application.getDesktopBottom()) { + setY(application.getDesktopBottom() - 1); + } return; } @@ -766,6 +839,11 @@ public class TWindow extends TWidget { return; } + // Give the shortcut bar a shot at this. + if (statusBar != null) { + statusBar.statusBarMouseMotion(mouse); + } + // I didn't take it, pass it on to my children super.onMouseMotion(mouse); } @@ -841,6 +919,13 @@ public class TWindow extends TWidget { return; } + // Give the shortcut bar a shot at this. + if (statusBar != null) { + if (statusBar.statusBarKeypress(keypress)) { + return; + } + } + // These keystrokes will typically not be seen unless a subclass // overrides onMenu() due to how TApplication dispatches // accelerators. diff --git a/src/jexer/bits/ColorTheme.java b/src/jexer/bits/ColorTheme.java index 626a6c8..d3a468f 100644 --- a/src/jexer/bits/ColorTheme.java +++ b/src/jexer/bits/ColorTheme.java @@ -437,6 +437,23 @@ public final class ColorTheme { color.setBold(true); colors.put("tlist.inactive", color); + // TStatusBar + color = new CellAttributes(); + color.setForeColor(Color.BLACK); + color.setBackColor(Color.WHITE); + color.setBold(false); + colors.put("tstatusbar.text", color); + color = new CellAttributes(); + color.setForeColor(Color.RED); + color.setBackColor(Color.WHITE); + color.setBold(false); + colors.put("tstatusbar.button", color); + color = new CellAttributes(); + color.setForeColor(Color.WHITE); + color.setBackColor(Color.BLUE); + color.setBold(false); + colors.put("tstatusbar.selected", color); + // TEditor color = new CellAttributes(); color.setForeColor(Color.WHITE); diff --git a/src/jexer/bits/GraphicsChars.java b/src/jexer/bits/GraphicsChars.java index cd48fc8..8e7a883 100644 --- a/src/jexer/bits/GraphicsChars.java +++ b/src/jexer/bits/GraphicsChars.java @@ -145,4 +145,5 @@ public final class GraphicsChars { public static final char WINDOW_RIGHT_TOP_DOUBLE = CP437[0xBB]; public static final char WINDOW_LEFT_BOTTOM_DOUBLE = CP437[0xC8]; public static final char WINDOW_RIGHT_BOTTOM_DOUBLE = CP437[0xBC]; + public static final char VERTICAL_BAR = CP437[0xB3]; } diff --git a/src/jexer/demos/DemoCheckboxWindow.java b/src/jexer/demos/DemoCheckboxWindow.java index 9ef561c..22d0cec 100644 --- a/src/jexer/demos/DemoCheckboxWindow.java +++ b/src/jexer/demos/DemoCheckboxWindow.java @@ -29,6 +29,8 @@ package jexer.demos; import jexer.*; +import static jexer.TCommand.*; +import static jexer.TKeypress.*; /** * This window demonstates the TRadioGroup, TRadioButton, and TCheckbox @@ -78,6 +80,12 @@ public class DemoCheckboxWindow extends TWindow { } } ); + + statusBar = newStatusBar("Radiobuttons and checkboxes"); + statusBar.addShortcutKeypress(kbF1, cmHelp, "Help"); + statusBar.addShortcutKeypress(kbF2, cmShell, "Shell"); + statusBar.addShortcutKeypress(kbF3, cmOpen, "Open"); + statusBar.addShortcutKeypress(kbF10, cmExit, "Exit"); } } diff --git a/src/jexer/demos/DemoMainWindow.java b/src/jexer/demos/DemoMainWindow.java index 5d74d7d..febdb40 100644 --- a/src/jexer/demos/DemoMainWindow.java +++ b/src/jexer/demos/DemoMainWindow.java @@ -29,6 +29,8 @@ package jexer.demos; import jexer.*; +import static jexer.TCommand.*; +import static jexer.TKeypress.*; /** * This is the main "demo" application window. It makes use of the TTimer, @@ -75,21 +77,19 @@ public class DemoMainWindow extends TWindow { private DemoMainWindow(final TApplication parent, final int flags) { // Construct a demo window. X and Y don't matter because it will be // centered on screen. - super(parent, "Demo Window", 0, 0, 60, 24, flags); + super(parent, "Demo Window", 0, 0, 60, 22, flags); int row = 1; // Add some widgets - if (!isModal()) { - addLabel("Message Boxes", 1, row); - addButton("&MessageBoxes", 35, row, - new TAction() { - public void DO() { - new DemoMsgBoxWindow(getApplication()); - } + addLabel("Message Boxes", 1, row); + addButton("&MessageBoxes", 35, row, + new TAction() { + public void DO() { + new DemoMsgBoxWindow(getApplication()); } - ); - } + } + ); row += 2; addLabel("Open me as modal", 1, row); @@ -100,29 +100,26 @@ public class DemoMainWindow extends TWindow { } } ); - row += 2; - addLabel("Variable-width text field:", 1, row); - addField(35, row++, 15, false, "Field text"); - addLabel("Fixed-width text field:", 1, row); - addField(35, row++, 15, true); - addLabel("Variable-width password:", 1, row); - addPasswordField(35, row++, 15, false); - addLabel("Fixed-width password:", 1, row); - addPasswordField(35, row++, 15, true, "hunter2"); - row += 1; + addLabel("Text fields", 1, row); + addButton("Field&s", 35, row, + new TAction() { + public void DO() { + new DemoTextFieldWindow(getApplication()); + } + } + ); + row += 2; - if (!isModal()) { - addLabel("Radio buttons and checkboxes", 1, row); - addButton("&Checkboxes", 35, row, - new TAction() { - public void DO() { - new DemoCheckboxWindow(getApplication()); - } + addLabel("Radio buttons and checkboxes", 1, row); + addButton("&Checkboxes", 35, row, + new TAction() { + public void DO() { + new DemoCheckboxWindow(getApplication()); } - ); - } + } + ); row += 2; /* @@ -137,56 +134,48 @@ public class DemoMainWindow extends TWindow { row += 2; */ - if (!isModal()) { - addLabel("Text areas", 1, row); - addButton("&Text", 35, row, - new TAction() { - public void DO() { - new DemoTextWindow(getApplication()); - } + addLabel("Text areas", 1, row); + addButton("&Text", 35, row, + new TAction() { + public void DO() { + new DemoTextWindow(getApplication()); } - ); - } + } + ); row += 2; - if (!isModal()) { - addLabel("Tree views", 1, row); - addButton("Tree&View", 35, row, - new TAction() { - public void DO() { - try { - new DemoTreeViewWindow(getApplication()); - } catch (Exception e) { - e.printStackTrace(); - } + addLabel("Tree views", 1, row); + addButton("Tree&View", 35, row, + new TAction() { + public void DO() { + try { + new DemoTreeViewWindow(getApplication()); + } catch (Exception e) { + e.printStackTrace(); } } - ); - } + } + ); row += 2; - if (!isModal()) { - addLabel("Terminal", 1, row); - addButton("Termi&nal", 35, row, - new TAction() { - public void DO() { - getApplication().openTerminal(0, 0); - } + addLabel("Terminal", 1, row); + addButton("Termi&nal", 35, row, + new TAction() { + public void DO() { + getApplication().openTerminal(0, 0); } - ); - } + } + ); row += 2; - if (!isModal()) { - addLabel("Color editor", 1, row); - addButton("Co&lors", 35, row, - new TAction() { - public void DO() { - new TEditColorThemeWindow(getApplication()); - } + addLabel("Color editor", 1, row); + addButton("Co&lors", 35, row, + new TAction() { + public void DO() { + new TEditColorThemeWindow(getApplication()); } - ); - } + } + ); row += 2; progressBar = addProgressBar(1, row, 22, 0); @@ -205,5 +194,11 @@ public class DemoMainWindow extends TWindow { } } ); + + statusBar = newStatusBar("Demo Main Window"); + statusBar.addShortcutKeypress(kbF1, cmHelp, "Help"); + statusBar.addShortcutKeypress(kbF2, cmShell, "Shell"); + statusBar.addShortcutKeypress(kbF3, cmOpen, "Open"); + statusBar.addShortcutKeypress(kbF10, cmExit, "Exit"); } } diff --git a/src/jexer/demos/DemoMsgBoxWindow.java b/src/jexer/demos/DemoMsgBoxWindow.java index a56da43..77bc331 100644 --- a/src/jexer/demos/DemoMsgBoxWindow.java +++ b/src/jexer/demos/DemoMsgBoxWindow.java @@ -29,6 +29,8 @@ package jexer.demos; import jexer.*; +import static jexer.TCommand.*; +import static jexer.TKeypress.*; /** * This window demonstates the TMessageBox and TInputBox widgets. @@ -53,7 +55,7 @@ public class DemoMsgBoxWindow extends TWindow { DemoMsgBoxWindow(final TApplication parent, final int flags) { // Construct a demo window. X and Y don't matter because it // will be centered on screen. - super(parent, "Message Boxes", 0, 0, 60, 15, flags); + super(parent, "Message Boxes", 0, 0, 60, 16, flags); int row = 1; @@ -154,6 +156,11 @@ public class DemoMsgBoxWindow extends TWindow { } } ); + + statusBar = newStatusBar("Message boxes"); + statusBar.addShortcutKeypress(kbF1, cmHelp, "Help"); + statusBar.addShortcutKeypress(kbF2, cmShell, "Shell"); + statusBar.addShortcutKeypress(kbF3, cmOpen, "Open"); + statusBar.addShortcutKeypress(kbF10, cmExit, "Exit"); } } - diff --git a/src/jexer/demos/DemoTextFieldWindow.java b/src/jexer/demos/DemoTextFieldWindow.java new file mode 100644 index 0000000..cd989b8 --- /dev/null +++ b/src/jexer/demos/DemoTextFieldWindow.java @@ -0,0 +1,86 @@ +/* + * Jexer - Java Text User Interface + * + * The MIT License (MIT) + * + * Copyright (C) 2016 Kevin Lamonte + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * @author Kevin Lamonte [kevin.lamonte@gmail.com] + * @version 1 + */ +package jexer.demos; + +import jexer.*; +import static jexer.TCommand.*; +import static jexer.TKeypress.*; + +/** + * This window demonstates the TField and TPasswordField widgets. + */ +public class DemoTextFieldWindow extends TWindow { + + /** + * Constructor. + * + * @param parent the main application + */ + DemoTextFieldWindow(final TApplication parent) { + this(parent, TWindow.CENTERED | TWindow.RESIZABLE); + } + + /** + * Constructor. + * + * @param parent the main application + * @param flags bitmask of MODAL, CENTERED, or RESIZABLE + */ + DemoTextFieldWindow(final TApplication parent, final int flags) { + // Construct a demo window. X and Y don't matter because it + // will be centered on screen. + super(parent, "Text Fields", 0, 0, 60, 10, flags); + + int row = 1; + + addLabel("Variable-width text field:", 1, row); + addField(35, row++, 15, false, "Field text"); + addLabel("Fixed-width text field:", 1, row); + addField(35, row++, 15, true); + addLabel("Variable-width password:", 1, row); + addPasswordField(35, row++, 15, false); + addLabel("Fixed-width password:", 1, row); + addPasswordField(35, row++, 15, true, "hunter2"); + row += 1; + + addButton("&Close Window", (getWidth() - 14) / 2, getHeight() - 4, + new TAction() { + public void DO() { + getApplication().closeWindow(DemoTextFieldWindow.this); + } + } + ); + + statusBar = newStatusBar("Text fields"); + statusBar.addShortcutKeypress(kbF1, cmHelp, "Help"); + statusBar.addShortcutKeypress(kbF2, cmShell, "Shell"); + statusBar.addShortcutKeypress(kbF3, cmOpen, "Open"); + statusBar.addShortcutKeypress(kbF10, cmExit, "Exit"); + } +} diff --git a/src/jexer/demos/DemoTextWindow.java b/src/jexer/demos/DemoTextWindow.java index a2ab68b..0ca34b0 100644 --- a/src/jexer/demos/DemoTextWindow.java +++ b/src/jexer/demos/DemoTextWindow.java @@ -31,6 +31,8 @@ package jexer.demos; import jexer.*; import jexer.event.*; import jexer.menu.*; +import static jexer.TCommand.*; +import static jexer.TKeypress.*; /** * This window demonstates the TText, THScroller, and TVScroller widgets. @@ -54,6 +56,12 @@ public class DemoTextWindow extends TWindow { super(parent, title, 0, 0, 44, 20, RESIZABLE); textField = addText(text, 1, 1, 40, 16); + + statusBar = newStatusBar("Reflowable text window"); + statusBar.addShortcutKeypress(kbF1, cmHelp, "Help"); + statusBar.addShortcutKeypress(kbF2, cmShell, "Shell"); + statusBar.addShortcutKeypress(kbF3, cmOpen, "Open"); + statusBar.addShortcutKeypress(kbF10, cmExit, "Exit"); } /** diff --git a/src/jexer/demos/DemoTreeViewWindow.java b/src/jexer/demos/DemoTreeViewWindow.java index a1f4a61..ad588a9 100644 --- a/src/jexer/demos/DemoTreeViewWindow.java +++ b/src/jexer/demos/DemoTreeViewWindow.java @@ -32,6 +32,8 @@ import java.io.IOException; import jexer.*; import jexer.event.*; +import static jexer.TCommand.*; +import static jexer.TKeypress.*; /** * This window demonstates the TTreeView widget. @@ -55,6 +57,12 @@ public class DemoTreeViewWindow extends TWindow { // Load the treeview with "stuff" treeView = addTreeView(1, 1, 40, 12); new TDirectoryTreeItem(treeView, ".", true); + + statusBar = newStatusBar("Treeview demonstration"); + statusBar.addShortcutKeypress(kbF1, cmHelp, "Help"); + statusBar.addShortcutKeypress(kbF2, cmShell, "Shell"); + statusBar.addShortcutKeypress(kbF3, cmOpen, "Open"); + statusBar.addShortcutKeypress(kbF10, cmExit, "Exit"); } /** diff --git a/src/jexer/io/package-info.java b/src/jexer/io/package-info.java index 3063bc4..f7e7870 100644 --- a/src/jexer/io/package-info.java +++ b/src/jexer/io/package-info.java @@ -29,6 +29,6 @@ /** * This package contains the user-facing I/O, including screen, keyboard, and - * mouse handling.. + * mouse handling classes. */ package jexer.io; diff --git a/src/jexer/tterminal/ECMA48.java b/src/jexer/tterminal/ECMA48.java index b3cb0ac..a282058 100644 --- a/src/jexer/tterminal/ECMA48.java +++ b/src/jexer/tterminal/ECMA48.java @@ -51,7 +51,7 @@ import jexer.io.TimeoutInputStream; import static jexer.TKeypress.*; /** - * This implements a complex ANSI ECMA-48/ISO 6429/ANSI X3.64 type consoles, + * This implements a complex ECMA-48/ISO 6429/ANSI X3.64 type console, * including a scrollback buffer. * *

    -- 2.27.0