menu system compiles
authorKevin Lamonte <kevin.lamonte@gmail.com>
Sat, 14 Mar 2015 12:33:54 +0000 (08:33 -0400)
committerKevin Lamonte <kevin.lamonte@gmail.com>
Sat, 14 Mar 2015 12:33:54 +0000 (08:33 -0400)
src/jexer/TApplication.java
src/jexer/TMenu.java [deleted file]
src/jexer/TWidget.java
src/jexer/TWindow.java
src/jexer/event/TMenuEvent.java
src/jexer/menu/TMenu.java [new file with mode: 0644]
src/jexer/menu/TMenuItem.java [new file with mode: 0644]
src/jexer/menu/TMenuSeparator.java [new file with mode: 0644]
src/jexer/menu/TSubMenu.java [new file with mode: 0644]
src/jexer/menu/package-info.java [new file with mode: 0644]

index 2d2c1009fd347d6ebd8db6823b93fd66b07ea38d..b2ee5071a74284b42a6068efed531c4dc3c90039 100644 (file)
@@ -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 (file)
index bc1bc35..0000000
+++ /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);
-    }
-
-}
index e07594997d1b208a4824191030f429a849457dc8..d0c6b8177dac3a818ea8a0554f86d813fb356d3b 100644 (file)
@@ -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<TWidget> children;
 
+    /**
+     * Get the list of child widgets that this widget contains.
+     *
+     * @return the list of child widgets
+     */
+    public List<TWidget> 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)
         ) {
index af345597609aed3c1c2438a66429c30fe4b95cf3..8634b2a9227c2e1bb17ab0ae01b08fcbd5268fff 100644 (file)
@@ -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<TWindow> {
      *
      * @return this TWindow's parent TApplication
      */
+    @Override
     public final TApplication getApplication() {
         return application;
     }
@@ -66,6 +68,7 @@ public class TWindow extends TWidget implements Comparable<TWindow> {
      *
      * @return the Screen
      */
+    @Override
     public final Screen getScreen() {
         return application.getScreen();
     }
@@ -162,7 +165,7 @@ public class TWindow extends TWidget implements Comparable<TWindow> {
     /**
      * 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<TWindow> {
      *
      * @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<TWindow> {
      *
      * @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<TWindow> {
      *
      * @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<TWindow> {
             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<TWindow> {
                 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"));
                 }
             }
         }
index 2fc8dc56e6befe63d72adc9e8a1d76b4e3240494..fed3e8345c3dbc202c7249de9bb4a5c193151ea7 100644 (file)
@@ -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 (file)
index 0000000..da349bf
--- /dev/null
@@ -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 (file)
index 0000000..d7e56e2
--- /dev/null
@@ -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 (file)
index 0000000..b9dc979
--- /dev/null
@@ -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 (file)
index 0000000..24cc67d
--- /dev/null
@@ -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 (file)
index 0000000..bd59e31
--- /dev/null
@@ -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;