From 928811d8e292801029b7b6605453524f65e9ebd9 Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Sat, 14 Mar 2015 08:33:54 -0400 Subject: [PATCH] menu system compiles --- src/jexer/TApplication.java | 56 ++- src/jexer/TMenu.java | 115 ------- src/jexer/TWidget.java | 58 +++- src/jexer/TWindow.java | 59 ++-- src/jexer/event/TMenuEvent.java | 6 +- src/jexer/menu/TMenu.java | 525 +++++++++++++++++++++++++++++ src/jexer/menu/TMenuItem.java | 284 ++++++++++++++++ src/jexer/menu/TMenuSeparator.java | 69 ++++ src/jexer/menu/TSubMenu.java | 176 ++++++++++ src/jexer/menu/package-info.java | 35 ++ 10 files changed, 1233 insertions(+), 150 deletions(-) delete mode 100644 src/jexer/TMenu.java create mode 100644 src/jexer/menu/TMenu.java create mode 100644 src/jexer/menu/TMenuItem.java create mode 100644 src/jexer/menu/TMenuSeparator.java create mode 100644 src/jexer/menu/TSubMenu.java create mode 100644 src/jexer/menu/package-info.java diff --git a/src/jexer/TApplication.java b/src/jexer/TApplication.java index 2d2c100..b2ee507 100644 --- a/src/jexer/TApplication.java +++ b/src/jexer/TApplication.java @@ -49,6 +49,8 @@ import jexer.event.TResizeEvent; import jexer.backend.Backend; import jexer.backend.ECMA48Backend; import jexer.io.Screen; +import jexer.menu.TMenu; +import jexer.menu.TMenuItem; import static jexer.TCommand.*; import static jexer.TKeypress.*; @@ -908,7 +910,7 @@ public class TApplication { /** * Turn off the menu. */ - private void closeMenu() { + public final void closeMenu() { if (activeMenu != null) { activeMenu.setActive(false); activeMenu = null; @@ -923,7 +925,7 @@ public class TApplication { /** * Turn off a sub-menu. */ - private void closeSubMenu() { + public final void closeSubMenu() { assert (activeMenu != null); TMenu item = subMenus.get(subMenus.size() - 1); assert (item != null); @@ -938,7 +940,7 @@ public class TApplication { * @param forward if true, then switch to the next menu in the list, * otherwise switch to the previous menu in the list */ - private void switchMenu(final boolean forward) { + public final void switchMenu(final boolean forward) { assert (activeMenu != null); for (TMenu menu: subMenus) { @@ -1091,4 +1093,52 @@ public class TApplication { return false; } + /** + * Add a keyboard accelerator to the global hash. + * + * @param item menu item this accelerator relates to + * @param keypress keypress that will dispatch a TMenuEvent + */ + public final void addAccelerator(final TMenuItem item, + final TKeypress keypress) { + /* + TODO + assert((keypress in accelerators) is null); + accelerators[keypress] = item; + */ + } + + /** + * Recompute menu x positions based on their title length. + */ + public final void recomputeMenuX() { + int x = 0; + for (TMenu menu: menus) { + menu.setX(x); + x += menu.getTitle().length() + 2; + } + } + + /** + * Post an event to process and turn off the menu. + * + * @param event new event to add to the queue + */ + public final void addMenuEvent(final TInputEvent event) { + /* + TODO - synchronize correctly + eventQueue ~= event; + */ + closeMenu(); + } + + /** + * Add a sub-menu to the list of open sub-menus. + * + * @param menu sub-menu + */ + public final void addSubMenu(final TMenu menu) { + subMenus.add(menu); + } + } diff --git a/src/jexer/TMenu.java b/src/jexer/TMenu.java deleted file mode 100644 index bc1bc35..0000000 --- a/src/jexer/TMenu.java +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Jexer - Java Text User Interface - * - * License: LGPLv3 or later - * - * This module is licensed under the GNU Lesser General Public License - * Version 3. Please see the file "COPYING" in this directory for more - * information about the GNU Lesser General Public License Version 3. - * - * Copyright (C) 2015 Kevin Lamonte - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * as published by the Free Software Foundation; either version 3 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this program; if not, see - * http://www.gnu.org/licenses/, or write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA - * - * @author Kevin Lamonte [kevin.lamonte@gmail.com] - * @version 1 - */ -package jexer; - -import jexer.bits.MnemonicString; - -/** - * TMenu is a top-level collection of TMenuItems. - */ -public class TMenu extends TWindow { - - /** - * If true, this is a sub-menu. - */ - private boolean isSubMenu = false; - - /** - * The shortcut and title. - */ - private MnemonicString mnemonic; - - /** - * Get the mnemonic string. - * - * @return the full mnemonic string - */ - public final MnemonicString getMnemonic() { - return mnemonic; - } - - // Reserved menu item IDs - public static final int MID_UNUSED = -1; - - // File menu - public static final int MID_EXIT = 1; - public static final int MID_QUIT = MID_EXIT; - public static final int MID_OPEN_FILE = 2; - public static final int MID_SHELL = 3; - - // Edit menu - public static final int MID_CUT = 10; - public static final int MID_COPY = 11; - public static final int MID_PASTE = 12; - public static final int MID_CLEAR = 13; - - // Window menu - public static final int MID_TILE = 20; - public static final int MID_CASCADE = 21; - public static final int MID_CLOSE_ALL = 22; - public static final int MID_WINDOW_MOVE = 23; - public static final int MID_WINDOW_ZOOM = 24; - public static final int MID_WINDOW_NEXT = 25; - public static final int MID_WINDOW_PREVIOUS = 26; - public static final int MID_WINDOW_CLOSE = 27; - - /** - * Public constructor. - * - * @param parent parent application - * @param x column relative to parent - * @param y row relative to parent - * @param label mnemonic menu title. Label must contain a keyboard - * shortcut (mnemonic), denoted by prefixing a letter with "&", - * e.g. "&File" - */ - public TMenu(final TApplication parent, final int x, final int y, - final String label) { - - super(parent, label, x, y, parent.getScreen().getWidth(), - parent.getScreen().getHeight()); - - // My parent constructor added me as a window, get rid of that - parent.closeWindow(this); - - // Setup the menu shortcut - mnemonic = new MnemonicString(label); - setTitle(mnemonic.getRawLabel()); - assert (mnemonic.getShortcutIdx() >= 0); - - // Recompute width and height to reflect an empty menu - setWidth(getTitle().length() + 4); - setHeight(2); - - setActive(false); - } - -} diff --git a/src/jexer/TWidget.java b/src/jexer/TWidget.java index e075949..d0c6b81 100644 --- a/src/jexer/TWidget.java +++ b/src/jexer/TWidget.java @@ -33,6 +33,7 @@ package jexer; import java.util.List; import java.util.LinkedList; +import jexer.bits.ColorTheme; import jexer.event.TCommandEvent; import jexer.event.TInputEvent; import jexer.event.TKeypressEvent; @@ -40,6 +41,7 @@ import jexer.event.TMenuEvent; import jexer.event.TMouseEvent; import jexer.event.TResizeEvent; import jexer.io.Screen; +import jexer.menu.TMenu; import static jexer.TKeypress.*; /** @@ -55,6 +57,15 @@ public abstract class TWidget { */ private TWidget parent = null; + /** + * Get parent widget. + * + * @return parent widget + */ + public final TWidget getParent() { + return parent; + } + /** * Backdoor access for TWindow's constructor. ONLY TWindow USES THIS. * @@ -75,11 +86,45 @@ public abstract class TWidget { this.height = height; } + /** + * Request full repaint on next screen refresh. + */ + protected final void setRepaint() { + window.getApplication().setRepaint(); + } + + /** + * 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. */ private List children; + /** + * Get the list of child widgets that this widget contains. + * + * @return the list of child widgets + */ + public List getChildren() { + return children; + } + /** * The currently active child widget that will receive keypress events. */ @@ -104,7 +149,7 @@ public abstract class TWidget { * * @param active if true, this widget will receive events */ - public final void setActive(boolean active) { + public final void setActive(final boolean active) { this.active = active; } @@ -358,6 +403,15 @@ public abstract class TWidget { return parent.getAbsoluteY() + y; } + /** + * Get the global color theme. + * + * @return the ColorTheme + */ + public final ColorTheme getTheme() { + return window.getApplication().getTheme(); + } + /** * Draw my specific widget. When called, the screen rectangle I draw * into is already setup (offset and clipping). @@ -571,7 +625,7 @@ public abstract class TWidget { * * @return widget that is active, or this if no children */ - public final TWidget getActiveChild() { + public TWidget getActiveChild() { if ((this instanceof THScroller) || (this instanceof TVScroller) ) { diff --git a/src/jexer/TWindow.java b/src/jexer/TWindow.java index af34559..8634b2a 100644 --- a/src/jexer/TWindow.java +++ b/src/jexer/TWindow.java @@ -39,6 +39,7 @@ import jexer.event.TMenuEvent; import jexer.event.TMouseEvent; import jexer.event.TResizeEvent; import jexer.io.Screen; +import jexer.menu.TMenu; import static jexer.TCommand.*; import static jexer.TKeypress.*; @@ -57,6 +58,7 @@ public class TWindow extends TWidget implements Comparable { * * @return this TWindow's parent TApplication */ + @Override public final TApplication getApplication() { return application; } @@ -66,6 +68,7 @@ public class TWindow extends TWidget implements Comparable { * * @return the Screen */ + @Override public final Screen getScreen() { return application.getScreen(); } @@ -162,7 +165,7 @@ public class TWindow extends TWidget implements Comparable { /** * Remember mouse state. */ - private TMouseEvent mouse; + protected TMouseEvent mouse; // For moving the window. resizing also uses moveWindowMouseX/Y private int moveWindowMouseX; @@ -369,26 +372,26 @@ public class TWindow extends TWidget implements Comparable { * * @return the background color */ - private final CellAttributes getBackground() { + private CellAttributes getBackground() { if (!isModal() && (inWindowMove || inWindowResize || inKeyboardResize) ) { assert (getActive()); - return application.getTheme().getColor("twindow.background.windowmove"); + return getTheme().getColor("twindow.background.windowmove"); } else if (isModal() && inWindowMove) { assert (getActive()); - return application.getTheme().getColor("twindow.background.modal"); + return getTheme().getColor("twindow.background.modal"); } else if (isModal()) { if (getActive()) { - return application.getTheme().getColor("twindow.background.modal"); + return getTheme().getColor("twindow.background.modal"); } - return application.getTheme().getColor("twindow.background.modal.inactive"); + return getTheme().getColor("twindow.background.modal.inactive"); } else if (getActive()) { assert (!isModal()); - return application.getTheme().getColor("twindow.background"); + return getTheme().getColor("twindow.background"); } else { assert (!isModal()); - return application.getTheme().getColor("twindow.background.inactive"); + return getTheme().getColor("twindow.background.inactive"); } } @@ -397,27 +400,27 @@ public class TWindow extends TWidget implements Comparable { * * @return the border color */ - private final CellAttributes getBorder() { + private CellAttributes getBorder() { if (!isModal() && (inWindowMove || inWindowResize || inKeyboardResize) ) { assert (getActive()); - return application.getTheme().getColor("twindow.border.windowmove"); + return getTheme().getColor("twindow.border.windowmove"); } else if (isModal() && inWindowMove) { assert (getActive()); - return application.getTheme().getColor("twindow.border.modal.windowmove"); + return getTheme().getColor("twindow.border.modal.windowmove"); } else if (isModal()) { if (getActive()) { - return application.getTheme().getColor("twindow.border.modal"); + return getTheme().getColor("twindow.border.modal"); } else { - return application.getTheme().getColor("twindow.border.modal.inactive"); + return getTheme().getColor("twindow.border.modal.inactive"); } } else if (getActive()) { assert (!isModal()); - return application.getTheme().getColor("twindow.border"); + return getTheme().getColor("twindow.border"); } else { assert (!isModal()); - return application.getTheme().getColor("twindow.border.inactive"); + return getTheme().getColor("twindow.border.inactive"); } } @@ -426,7 +429,7 @@ public class TWindow extends TWidget implements Comparable { * * @return the border line type */ - private final int getBorderType() { + private int getBorderType() { if (!isModal() && (inWindowMove || inWindowResize || inKeyboardResize) ) { @@ -483,13 +486,13 @@ public class TWindow extends TWidget implements Comparable { if (mouseOnClose() && mouse.getMouse1()) { putCharXY(3, 0, GraphicsChars.CP437[0x0F], !isModal() - ? application.getTheme().getColor("twindow.border.windowmove") - : application.getTheme().getColor("twindow.border.modal.windowmove")); + ? getTheme().getColor("twindow.border.windowmove") + : getTheme().getColor("twindow.border.modal.windowmove")); } else { putCharXY(3, 0, GraphicsChars.CP437[0xFE], !isModal() - ? application.getTheme().getColor("twindow.border.windowmove") - : application.getTheme().getColor("twindow.border.modal.windowmove")); + ? getTheme().getColor("twindow.border.windowmove") + : getTheme().getColor("twindow.border.modal.windowmove")); } // Draw the maximize button @@ -499,23 +502,25 @@ public class TWindow extends TWidget implements Comparable { putCharXY(getWidth() - 3, 0, ']', border); if (mouseOnMaximize() && mouse.getMouse1()) { putCharXY(getWidth() - 4, 0, GraphicsChars.CP437[0x0F], - application.getTheme().getColor("twindow.border.windowmove")); + getTheme().getColor("twindow.border.windowmove")); } else { if (maximized) { putCharXY(getWidth() - 4, 0, GraphicsChars.CP437[0x12], - application.getTheme().getColor("twindow.border.windowmove")); + getTheme().getColor("twindow.border.windowmove")); } else { putCharXY(getWidth() - 4, 0, GraphicsChars.UPARROW, - application.getTheme().getColor("twindow.border.windowmove")); + getTheme().getColor("twindow.border.windowmove")); } } // Draw the resize corner if ((flags & RESIZABLE) != 0) { - putCharXY(getWidth() - 2, getHeight() - 1, GraphicsChars.SINGLE_BAR, - application.getTheme().getColor("twindow.border.windowmove")); - putCharXY(getWidth() - 1, getHeight() - 1, GraphicsChars.LRCORNER, - application.getTheme().getColor("twindow.border.windowmove")); + putCharXY(getWidth() - 2, getHeight() - 1, + GraphicsChars.SINGLE_BAR, + getTheme().getColor("twindow.border.windowmove")); + putCharXY(getWidth() - 1, getHeight() - 1, + GraphicsChars.LRCORNER, + getTheme().getColor("twindow.border.windowmove")); } } } diff --git a/src/jexer/event/TMenuEvent.java b/src/jexer/event/TMenuEvent.java index 2fc8dc5..fed3e83 100644 --- a/src/jexer/event/TMenuEvent.java +++ b/src/jexer/event/TMenuEvent.java @@ -40,14 +40,14 @@ public final class TMenuEvent extends TInputEvent { /** * MenuItem ID. */ - private short id; + private int id; /** * Get the MenuItem ID. * * @return the ID */ - public short getId() { + public int getId() { return id; } @@ -56,7 +56,7 @@ public final class TMenuEvent extends TInputEvent { * * @param id the MenuItem ID */ - public TMenuEvent(final short id) { + public TMenuEvent(final int id) { this.id = id; } diff --git a/src/jexer/menu/TMenu.java b/src/jexer/menu/TMenu.java new file mode 100644 index 0000000..da349bf --- /dev/null +++ b/src/jexer/menu/TMenu.java @@ -0,0 +1,525 @@ +/** + * Jexer - Java Text User Interface + * + * License: LGPLv3 or later + * + * This module is licensed under the GNU Lesser General Public License + * Version 3. Please see the file "COPYING" in this directory for more + * information about the GNU Lesser General Public License Version 3. + * + * Copyright (C) 2015 Kevin Lamonte + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see + * http://www.gnu.org/licenses/, or write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * @author Kevin Lamonte [kevin.lamonte@gmail.com] + * @version 1 + */ +package jexer.menu; + +import jexer.TApplication; +import jexer.TKeypress; +import jexer.TWidget; +import jexer.TWindow; +import jexer.bits.CellAttributes; +import jexer.bits.GraphicsChars; +import jexer.bits.MnemonicString; +import jexer.event.TKeypressEvent; +import jexer.event.TMouseEvent; +import static jexer.TKeypress.*; + +/** + * TMenu is a top-level collection of TMenuItems. + */ +public final class TMenu extends TWindow { + + /** + * If true, this is a sub-menu. Note package private access. + */ + boolean isSubMenu = false; + + /** + * The shortcut and title. + */ + private MnemonicString mnemonic; + + /** + * Get the mnemonic string. + * + * @return the full mnemonic string + */ + public MnemonicString getMnemonic() { + return mnemonic; + } + + // Reserved menu item IDs + public static final int MID_UNUSED = -1; + + // File menu + public static final int MID_EXIT = 1; + public static final int MID_QUIT = MID_EXIT; + public static final int MID_OPEN_FILE = 2; + public static final int MID_SHELL = 3; + + // Edit menu + public static final int MID_CUT = 10; + public static final int MID_COPY = 11; + public static final int MID_PASTE = 12; + public static final int MID_CLEAR = 13; + + // Window menu + public static final int MID_TILE = 20; + public static final int MID_CASCADE = 21; + public static final int MID_CLOSE_ALL = 22; + public static final int MID_WINDOW_MOVE = 23; + public static final int MID_WINDOW_ZOOM = 24; + public static final int MID_WINDOW_NEXT = 25; + public static final int MID_WINDOW_PREVIOUS = 26; + public static final int MID_WINDOW_CLOSE = 27; + + /** + * Public constructor. + * + * @param parent parent application + * @param x column relative to parent + * @param y row relative to parent + * @param label mnemonic menu title. Label must contain a keyboard + * shortcut (mnemonic), denoted by prefixing a letter with "&", + * e.g. "&File" + */ + public TMenu(final TApplication parent, final int x, final int y, + final String label) { + + super(parent, label, x, y, parent.getScreen().getWidth(), + parent.getScreen().getHeight()); + + // My parent constructor added me as a window, get rid of that + parent.closeWindow(this); + + // Setup the menu shortcut + mnemonic = new MnemonicString(label); + setTitle(mnemonic.getRawLabel()); + assert (mnemonic.getShortcutIdx() >= 0); + + // Recompute width and height to reflect an empty menu + setWidth(getTitle().length() + 4); + setHeight(2); + + setActive(false); + } + + /** + * Draw a top-level menu with title and menu items. + */ + @Override + public void draw() { + CellAttributes menuColor; + CellAttributes background = getTheme().getColor("tmenu"); + + if (getAbsoluteActive()) { + menuColor = getTheme().getColor("tmenu.highlighted"); + } else { + menuColor = getTheme().getColor("tmenu"); + } + + assert (getAbsoluteActive()); + + // Fill in the interior background + for (int i = 0; i < getHeight(); i++) { + hLineXY(0, i, getWidth(), ' ', background); + } + + // Draw the box + char cTopLeft; + char cTopRight; + char cBottomLeft; + char cBottomRight; + char cHSide; + + cTopLeft = GraphicsChars.ULCORNER; + cTopRight = GraphicsChars.URCORNER; + cBottomLeft = GraphicsChars.LLCORNER; + cBottomRight = GraphicsChars.LRCORNER; + cHSide = GraphicsChars.SINGLE_BAR; + + // Place the corner characters + putCharXY(1, 0, cTopLeft, background); + putCharXY(getWidth() - 2, 0, cTopRight, background); + putCharXY(1, getHeight() - 1, cBottomLeft, background); + putCharXY(getWidth() - 2, getHeight() - 1, cBottomRight, background); + + // Draw the box lines + hLineXY(1 + 1, 0, getWidth() - 4, cHSide, background); + hLineXY(1 + 1, getHeight() - 1, getWidth() - 4, cHSide, background); + + // Draw a shadow + getScreen().drawBoxShadow(0, 0, getWidth(), getHeight()); + } + + /** + * Handle mouse button presses. + * + * @param mouse mouse button event + */ + @Override + public void onMouseDown(final TMouseEvent mouse) { + this.mouse = mouse; + setRepaint(); + + // Pass to children + for (TWidget widget: getChildren()) { + if (widget.mouseWouldHit(mouse)) { + // Dispatch to this child, also activate it + activate(widget); + + // Set x and y relative to the child's coordinates + mouse.setX(mouse.getAbsoluteX() - widget.getAbsoluteX()); + mouse.setY(mouse.getAbsoluteY() - widget.getAbsoluteY()); + widget.handleEvent(mouse); + return; + } + } + } + + /** + * Handle mouse button releases. + * + * @param mouse mouse button release event + */ + @Override + public void onMouseUp(final TMouseEvent mouse) { + this.mouse = mouse; + setRepaint(); + + // Pass to children + for (TWidget widget: getChildren()) { + if (widget.mouseWouldHit(mouse)) { + // Dispatch to this child, also activate it + activate(widget); + + // Set x and y relative to the child's coordinates + mouse.setX(mouse.getAbsoluteX() - widget.getAbsoluteX()); + mouse.setY(mouse.getAbsoluteY() - widget.getAbsoluteY()); + widget.handleEvent(mouse); + return; + } + } + } + + /** + * Handle mouse movements. + * + * @param mouse mouse motion event + */ + @Override + public void onMouseMotion(final TMouseEvent mouse) { + this.mouse = mouse; + setRepaint(); + + // See if we should activate a different menu item + for (TWidget widget: getChildren()) { + if ((mouse.getMouse1()) + && (widget.mouseWouldHit(mouse)) + ) { + // Activate this menu item + activate(widget); + if (widget instanceof TSubMenu) { + ((TSubMenu) widget).dispatch(); + } + return; + } + } + } + + /** + * Handle keystrokes. + * + * @param keypress keystroke event + */ + @Override + public void onKeypress(final TKeypressEvent keypress) { + if (getActiveChild() != null) { + if (getActiveChild() instanceof TSubMenu) { + getActiveChild().onKeypress(keypress); + return; + } + } + + if (keypress.equals(kbEsc)) { + getApplication().closeMenu(); + return; + } + if (keypress.equals(kbDown)) { + switchWidget(true); + return; + } + if (keypress.equals(kbUp)) { + switchWidget(false); + return; + } + if (keypress.equals(kbRight)) { + if (!isSubMenu) { + getApplication().switchMenu(true); + } + return; + } + if (keypress.equals(kbLeft)) { + if (isSubMenu) { + getApplication().closeSubMenu(); + } else { + getApplication().switchMenu(false); + } + return; + } + + // Switch to a menuItem if it has an mnemonic + if (!keypress.getKey().getIsKey() + && !keypress.getKey().getAlt() + && !keypress.getKey().getCtrl()) { + for (TWidget widget: getChildren()) { + TMenuItem item = (TMenuItem) widget; + if ((item.getMnemonic() != null) + && (Character.toLowerCase(item.getMnemonic().getShortcut()) + == Character.toLowerCase(keypress.getKey().getCh())) + ) { + // Send an enter keystroke to it + activate(item); + item.handleEvent(new TKeypressEvent(kbEnter)); + return; + } + } + } + + // Dispatch the keypress to an active widget + for (TWidget widget: getChildren()) { + if (widget.getActive()) { + setRepaint(); + widget.handleEvent(keypress); + return; + } + } + } + + /** + * Convenience function to add a custom menu item. + * + * @param id menu item ID. Must be greater than 1024. + * @param label menu item label + * @param key global keyboard accelerator + * @return the new menu item + */ + public TMenuItem addItem(final int id, final String label, + final TKeypress key) { + + assert (id >= 1024); + return addItemInternal(id, label, key); + } + + /** + * Convenience function to add a custom menu item. + * + * @param id menu item ID. Must be greater than 1024. + * @param label menu item label + * @param key global keyboard accelerator + * @return the new menu item + */ + private TMenuItem addItemInternal(final int id, final String label, + final TKeypress key) { + + int newY = getChildren().size() + 1; + assert (newY < getHeight()); + + TMenuItem menuItem = new TMenuItem(this, id, 1, newY, label); + menuItem.setKey(key); + setHeight(getHeight() + 1); + if (menuItem.getWidth() + 2 > getWidth()) { + setWidth(menuItem.getWidth() + 2); + } + for (TWidget widget: getChildren()) { + widget.setWidth(getWidth() - 2); + } + getApplication().addAccelerator(menuItem, toLower(key)); + getApplication().recomputeMenuX(); + activate(0); + return menuItem; + } + + /** + * Convenience function to add a menu item. + * + * @param id menu item ID. Must be greater than 1024. + * @param label menu item label + * @return the new menu item + */ + public TMenuItem addItem(final int id, final String label) { + assert (id >= 1024); + return addItemInternal(id, label); + } + + /** + * Convenience function to add a menu item. + * + * @param id menu item ID + * @param label menu item label + * @return the new menu item + */ + private TMenuItem addItemInternal(final int id, final String label) { + int newY = getChildren().size() + 1; + assert (newY < getHeight()); + + TMenuItem menuItem = new TMenuItem(this, id, 1, newY, label); + setHeight(getHeight() + 1); + if (menuItem.getWidth() + 2 > getWidth()) { + setWidth(menuItem.getWidth() + 2); + } + for (TWidget widget: getChildren()) { + widget.setWidth(getWidth() - 2); + } + getApplication().recomputeMenuX(); + activate(0); + return menuItem; + } + + /** + * Convenience function to add one of the default menu items. + * + * @param id menu item ID. Must be between 0 (inclusive) and 1023 + * (inclusive). + * @return the new menu item + */ + public TMenuItem addDefaultItem(final int id) { + assert (id >= 0); + assert (id < 1024); + + String label; + TKeypress key = null; + boolean hasKey = true; + + switch (id) { + + case MID_EXIT: + label = "E&xit"; + key = kbAltX; + break; + + case MID_SHELL: + label = "O&S Shell"; + hasKey = false; + break; + + case MID_OPEN_FILE: + label = "&Open"; + key = kbAltO; + break; + + case MID_CUT: + label = "Cu&t"; + key = kbCtrlX; + break; + case MID_COPY: + label = "&Copy"; + key = kbCtrlC; + break; + case MID_PASTE: + label = "&Paste"; + key = kbCtrlV; + break; + case MID_CLEAR: + label = "C&lear"; + key = kbDel; + break; + + case MID_TILE: + label = "&Tile"; + hasKey = false; + break; + case MID_CASCADE: + label = "C&ascade"; + hasKey = false; + break; + case MID_CLOSE_ALL: + label = "Cl&ose All"; + hasKey = false; + break; + case MID_WINDOW_MOVE: + label = "&Size/Move"; + key = kbCtrlF5; + break; + case MID_WINDOW_ZOOM: + label = "&Zoom"; + key = kbF5; + break; + case MID_WINDOW_NEXT: + label = "&Next"; + key = kbF6; + break; + case MID_WINDOW_PREVIOUS: + label = "&Previous"; + key = kbShiftF6; + break; + case MID_WINDOW_CLOSE: + label = "&Close"; + key = kbCtrlW; + break; + + default: + throw new IllegalArgumentException("Invalid menu ID: " + id); + } + + if (hasKey) { + return addItemInternal(id, label, key); + } + return addItemInternal(id, label); + } + + /** + * Convenience function to add a menu separator. + */ + public void addSeparator() { + int newY = getChildren().size() + 1; + assert (newY < getHeight()); + + TMenuItem menuItem = new TMenuSeparator(this, 1, newY); + setHeight(getHeight() + 1); + } + + /** + * Convenience function to add a sub-menu. + * + * @param title menu title. Title must contain a keyboard shortcut, + * denoted by prefixing a letter with "&", e.g. "&File" + * @return the new sub-menu + */ + public TSubMenu addSubMenu(final String title) { + int newY = getChildren().size() + 1; + assert (newY < getHeight()); + + TSubMenu subMenu = new TSubMenu(this, title, 1, newY); + setHeight(getHeight() + 1); + if (subMenu.getWidth() + 2 > getWidth()) { + setWidth(subMenu.getWidth() + 2); + } + for (TWidget widget: getChildren()) { + widget.setWidth(getWidth() - 2); + } + getApplication().recomputeMenuX(); + activate(0); + subMenu.menu.setX(getX() + getWidth() - 2); + + return subMenu; + } + +} diff --git a/src/jexer/menu/TMenuItem.java b/src/jexer/menu/TMenuItem.java new file mode 100644 index 0000000..d7e56e2 --- /dev/null +++ b/src/jexer/menu/TMenuItem.java @@ -0,0 +1,284 @@ +/** + * Jexer - Java Text User Interface + * + * License: LGPLv3 or later + * + * This module is licensed under the GNU Lesser General Public License + * Version 3. Please see the file "COPYING" in this directory for more + * information about the GNU Lesser General Public License Version 3. + * + * Copyright (C) 2015 Kevin Lamonte + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see + * http://www.gnu.org/licenses/, or write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * @author Kevin Lamonte [kevin.lamonte@gmail.com] + * @version 1 + */ +package jexer.menu; + +import jexer.TKeypress; +import jexer.TWidget; +import jexer.bits.CellAttributes; +import jexer.bits.GraphicsChars; +import jexer.bits.MnemonicString; +import jexer.event.TKeypressEvent; +import jexer.event.TMouseEvent; +import jexer.event.TMenuEvent; +import static jexer.TKeypress.*; + +/** + * TMenuItem implements a menu item. + */ +public class TMenuItem extends TWidget { + + /** + * Label for this menu item. + */ + private String label; + + /** + * Menu ID. IDs less than 1024 are reserved for common system + * functions. Existing ones are defined in TMenu, i.e. TMenu.MID_EXIT. + */ + private int id = TMenu.MID_UNUSED; + + /** + * When true, this item can be checked or unchecked. + */ + private boolean checkable = false; + + /** + * When true, this item is checked. + */ + private boolean checked = false; + + /** + * Global shortcut key. + */ + private TKeypress key; + + /** + * When true, a global accelerator can be used to select this item. + */ + private boolean hasKey = false; + + /** + * The title string. Use '&' to specify a mnemonic, i.e. "&File" will + * highlight the 'F' and allow 'f' or 'F' to select it. + */ + private MnemonicString mnemonic; + + /** + * Get the mnemonic string for this menu item. + * + * @return mnemonic string + */ + public final MnemonicString getMnemonic() { + return mnemonic; + } + + /** + * Set a global accelerator key for this menu item. + * + * @param key global keyboard accelerator + */ + public final void setKey(final TKeypress key) { + hasKey = true; + this.key = key; + + int newWidth = (label.length() + 4 + key.toString().length() + 2); + if (newWidth > getWidth()) { + setWidth(newWidth); + } + } + + /** + * Package private constructor. + * + * @param parent parent widget + * @param id menu id + * @param x column relative to parent + * @param y row relative to parent + * @param label menu item title + */ + TMenuItem(final TMenu parent, final int id, final int x, final int y, + final String label) { + + // Set parent and window + super(parent); + + mnemonic = new MnemonicString(label); + + setX(x); + setY(y); + setHeight(1); + this.label = mnemonic.getRawLabel(); + setWidth(label.length() + 4); + this.id = id; + + // Default state for some known menu items + switch (id) { + + case TMenu.MID_CUT: + setEnabled(false); + break; + case TMenu.MID_COPY: + setEnabled(false); + break; + case TMenu.MID_PASTE: + setEnabled(false); + break; + case TMenu.MID_CLEAR: + setEnabled(false); + break; + + case TMenu.MID_TILE: + break; + case TMenu.MID_CASCADE: + break; + case TMenu.MID_CLOSE_ALL: + break; + case TMenu.MID_WINDOW_MOVE: + break; + case TMenu.MID_WINDOW_ZOOM: + break; + case TMenu.MID_WINDOW_NEXT: + break; + case TMenu.MID_WINDOW_PREVIOUS: + break; + case TMenu.MID_WINDOW_CLOSE: + break; + default: + break; + } + + } + + /** + * Returns true if the mouse is currently on the menu item. + * + * @param mouse mouse event + */ + private boolean mouseOnMenuItem(final TMouseEvent mouse) { + if ((mouse.getY() == 0) + && (mouse.getX() >= 0) + && (mouse.getX() < getWidth()) + ) { + return true; + } + return false; + } + + /** + * Draw a menu item with label. + */ + @Override + public void draw() { + CellAttributes background = getTheme().getColor("tmenu"); + CellAttributes menuColor; + CellAttributes menuMnemonicColor; + if (getAbsoluteActive()) { + menuColor = getTheme().getColor("tmenu.highlighted"); + menuMnemonicColor = getTheme().getColor("tmenu.mnemonic.highlighted"); + } else { + if (getEnabled()) { + menuColor = getTheme().getColor("tmenu"); + menuMnemonicColor = getTheme().getColor("tmenu.mnemonic"); + } else { + menuColor = getTheme().getColor("tmenu.disabled"); + menuMnemonicColor = getTheme().getColor("tmenu.disabled"); + } + } + + char cVSide = GraphicsChars.WINDOW_SIDE; + getScreen().vLineXY(0, 0, 1, cVSide, background); + getScreen().vLineXY(getWidth() - 1, 0, 1, cVSide, background); + + getScreen().hLineXY(1, 0, getWidth() - 2, ' ', menuColor); + getScreen().putStrXY(2, 0, mnemonic.getRawLabel(), menuColor); + if (hasKey) { + String keyLabel = key.toString(); + getScreen().putStrXY((getWidth() - keyLabel.length() - 2), 0, + keyLabel, menuColor); + } + if (mnemonic.getShortcutIdx() >= 0) { + getScreen().putCharXY(2 + mnemonic.getShortcutIdx(), 0, + mnemonic.getShortcut(), menuMnemonicColor); + } + if (checked) { + assert (checkable); + getScreen().putCharXY(1, 0, GraphicsChars.CHECK, menuColor); + } + + } + + /** + * Dispatch event(s) due to selection or click. + */ + public void dispatch() { + assert (getEnabled()); + + getApplication().addMenuEvent(new TMenuEvent(id)); + if (checkable) { + checked = !checked; + } + } + + /** + * Handle mouse button presses. + * + * @param event mouse button press event + */ + /* TODO: this was commented out in d-tui, why? + @Override + public void onMouseDown(final TMouseEvent event) { + if ((mouseOnMenuItem(event)) && (event.mouse1)) { + dispatch(); + return; + } + } + */ + + /** + * Handle mouse button releases. + * + * @param mouse mouse button release event + */ + @Override + public void onMouseUp(final TMouseEvent mouse) { + if ((mouseOnMenuItem(mouse)) && (mouse.getMouse1())) { + dispatch(); + return; + } + } + + /** + * Handle keystrokes. + * + * @param keypress keystroke event + */ + @Override + public void onKeypress(final TKeypressEvent keypress) { + if (keypress.equals(kbEnter)) { + dispatch(); + return; + } + + // Pass to parent for the things we don't care about. + super.onKeypress(keypress); + } +} diff --git a/src/jexer/menu/TMenuSeparator.java b/src/jexer/menu/TMenuSeparator.java new file mode 100644 index 0000000..b9dc979 --- /dev/null +++ b/src/jexer/menu/TMenuSeparator.java @@ -0,0 +1,69 @@ +/** + * Jexer - Java Text User Interface + * + * License: LGPLv3 or later + * + * This module is licensed under the GNU Lesser General Public License + * Version 3. Please see the file "COPYING" in this directory for more + * information about the GNU Lesser General Public License Version 3. + * + * Copyright (C) 2015 Kevin Lamonte + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see + * http://www.gnu.org/licenses/, or write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * @author Kevin Lamonte [kevin.lamonte@gmail.com] + * @version 1 + */ +package jexer.menu; + +import jexer.bits.CellAttributes; +import jexer.bits.GraphicsChars; + +/** + * TMenuSeparator is a special case menu item. + */ +public class TMenuSeparator extends TMenuItem { + + /** + * Package private constructor. + * + * @param parent parent widget + * @param x column relative to parent + * @param y row relative to parent + */ + TMenuSeparator(final TMenu parent, final int x, final int y) { + super(parent, TMenu.MID_UNUSED, x, y, ""); + setEnabled(false); + setActive(false); + setWidth(parent.getWidth() - 2); + } + + /** + * Draw a menu separator. + */ + @Override + public void draw() { + CellAttributes background = getTheme().getColor("tmenu"); + + getScreen().putCharXY(0, 0, GraphicsChars.CP437[0xC3], background); + getScreen().putCharXY(getWidth() - 1, 0, GraphicsChars.CP437[0xB4], + background); + getScreen().hLineXY(1, 0, getWidth() - 2, GraphicsChars.SINGLE_BAR, + background); + } + +} diff --git a/src/jexer/menu/TSubMenu.java b/src/jexer/menu/TSubMenu.java new file mode 100644 index 0000000..24cc67d --- /dev/null +++ b/src/jexer/menu/TSubMenu.java @@ -0,0 +1,176 @@ +/** + * Jexer - Java Text User Interface + * + * License: LGPLv3 or later + * + * This module is licensed under the GNU Lesser General Public License + * Version 3. Please see the file "COPYING" in this directory for more + * information about the GNU Lesser General Public License Version 3. + * + * Copyright (C) 2015 Kevin Lamonte + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see + * http://www.gnu.org/licenses/, or write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * @author Kevin Lamonte [kevin.lamonte@gmail.com] + * @version 1 + */ +package jexer.menu; + +import jexer.TWidget; +import jexer.bits.CellAttributes; +import jexer.bits.GraphicsChars; +import jexer.event.TKeypressEvent; +import static jexer.TKeypress.*; + +/** + * TSubMenu is a special case menu item that wraps another TMenu. + */ +public class TSubMenu extends TMenuItem { + + /** + * The menu window. Note package private access. + */ + TMenu menu; + + /** + * Package private constructor. + * + * @param parent parent widget + * @param title menu title. Title must contain a keyboard shortcut, + * denoted by prefixing a letter with "&", e.g. "&File" + * @param x column relative to parent + * @param y row relative to parent + */ + TSubMenu(final TMenu parent, final String title, final int x, final int y) { + super(parent, TMenu.MID_UNUSED, x, y, title); + + setActive(false); + setEnabled(true); + + this.menu = new TMenu(parent.getApplication(), x, getAbsoluteY() - 1, + title); + setWidth(menu.getWidth() + 2); + + this.menu.isSubMenu = true; + } + + /** + * Draw the menu title. + */ + @Override + public void draw() { + super.draw(); + + CellAttributes background = getTheme().getColor("tmenu"); + CellAttributes menuColor; + CellAttributes menuMnemonicColor; + if (getAbsoluteActive()) { + menuColor = getTheme().getColor("tmenu.highlighted"); + menuMnemonicColor = getTheme().getColor("tmenu.mnemonic.highlighted"); + } else { + if (getEnabled()) { + menuColor = getTheme().getColor("tmenu"); + menuMnemonicColor = getTheme().getColor("tmenu.mnemonic"); + } else { + menuColor = getTheme().getColor("tmenu.disabled"); + menuMnemonicColor = getTheme().getColor("tmenu.disabled"); + } + } + + // Add the arrow + getScreen().putCharXY(getWidth() - 2, 0, GraphicsChars.CP437[0x10], + menuColor); + } + + /** + * Handle keystrokes. + * + * @param keypress keystroke event + */ + @Override + public void onKeypress(final TKeypressEvent keypress) { + + if (menu.getActive()) { + menu.onKeypress(keypress); + return; + } + + if (keypress.equals(kbEnter)) { + dispatch(); + return; + } + + if (keypress.equals(kbRight)) { + dispatch(); + return; + } + + if (keypress.equals(kbDown)) { + getParent().switchWidget(true); + return; + } + + if (keypress.equals(kbUp)) { + getParent().switchWidget(false); + return; + } + + if (keypress.equals(kbLeft)) { + TMenu parentMenu = (TMenu) getParent(); + if (parentMenu.isSubMenu) { + getApplication().closeSubMenu(); + } else { + getApplication().switchMenu(false); + } + return; + } + + if (keypress.equals(kbEsc)) { + getApplication().closeMenu(); + return; + } + } + + /** + * Override dispatch() to do nothing. + */ + @Override + public void dispatch() { + assert (getEnabled()); + if (getAbsoluteActive()) { + if (!menu.getActive()) { + getApplication().addSubMenu(menu); + menu.setActive(true); + } + } + } + + /** + * Returns my active widget. + * + * @return widget that is active, or this if no children + */ + @Override + public TWidget getActiveChild() { + if (menu.getActive()) { + return menu; + } + // Menu not active, return me + return this; + } + +} diff --git a/src/jexer/menu/package-info.java b/src/jexer/menu/package-info.java new file mode 100644 index 0000000..bd59e31 --- /dev/null +++ b/src/jexer/menu/package-info.java @@ -0,0 +1,35 @@ +/* + * Jexer - Java Text User Interface + * + * License: LGPLv3 or later + * + * This module is licensed under the GNU Lesser General Public License + * Version 3. Please see the file "COPYING" in this directory for more + * information about the GNU Lesser General Public License Version 3. + * + * Copyright (C) 2015 Kevin Lamonte + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see + * http://www.gnu.org/licenses/, or write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * @author Kevin Lamonte [kevin.lamonte@gmail.com] + * @version 1 + */ + +/** + * This package contains the menu bar classes. + */ +package jexer.menu; -- 2.27.0