From: Kevin Lamonte Date: Sat, 14 Mar 2015 14:12:43 +0000 (-0400) Subject: menus working X-Git-Tag: fanfix-3.0.1^2~346 X-Git-Url: https://git.nikiroo.be/?a=commitdiff_plain;h=8e688b9211599d240be67e5cf62dfe48520378f2;p=fanfix.git menus working --- diff --git a/README.md b/README.md index d39d221..f097e77 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,6 @@ version 1.0: 0.0.1: -- TMenu - TButton - TCheckbox - TDirectoryList diff --git a/demos/Demo1.java b/demos/Demo1.java index 1270dae..4c7f631 100644 --- a/demos/Demo1.java +++ b/demos/Demo1.java @@ -32,6 +32,7 @@ */ import jexer.*; +import jexer.menu.*; class DemoMainWindow extends TWindow { /* @@ -43,27 +44,27 @@ class DemoMainWindow extends TWindow { // do this kind of logic on their own. private TWindow modalWindow; private void openModalWindow() { - modalWindow = application.addWindow("Demo Modal Window", 0, 0, - 58, 15, TWindow.Flag.MODAL); - modalWindow.addLabel("This is an example of a very braindead modal window.", 1, 1); - modalWindow.addLabel("Modal windows are centered by default.", 1, 2); - modalWindow.addButton("&Close", (modalWindow.width - 8)/2, - modalWindow.height - 4, &modalWindowClose); + modalWindow = application.addWindow("Demo Modal Window", 0, 0, + 58, 15, TWindow.Flag.MODAL); + modalWindow.addLabel("This is an example of a very braindead modal window.", 1, 1); + modalWindow.addLabel("Modal windows are centered by default.", 1, 2); + modalWindow.addButton("&Close", (modalWindow.width - 8)/2, + modalWindow.height - 4, &modalWindowClose); } private void modalWindowClose() { - application.closeWindow(modalWindow); + application.closeWindow(modalWindow); } /// This is an example of having a button call a function. private void openCheckboxWindow() { - new DemoCheckboxWindow(application); + new DemoCheckboxWindow(application); } /// We need to override onClose so that the timer will no longer be /// called after we close the window. TTimers currently are completely /// unaware of the rest of the UI classes. override public void onClose() { - application.removeTimer(timer); + application.removeTimer(timer); } */ @@ -71,111 +72,111 @@ class DemoMainWindow extends TWindow { * Construct demo window. It will be centered on screen. */ public DemoMainWindow(TApplication parent) { - this(parent, CENTERED | RESIZABLE); + this(parent, CENTERED | RESIZABLE); } /** * Constructor. */ private DemoMainWindow(TApplication parent, int flags) { - // Construct a demo window. X and Y don't matter because it will be - // centered on screen. - super(parent, "Demo Window", 0, 0, 60, 23, flags); + // Construct a demo window. X and Y don't matter because it will be + // centered on screen. + super(parent, "Demo Window", 0, 0, 60, 23, flags); /* - int row = 1; - - // Add some widgets - if (!isModal) { - addLabel("Message Boxes", 1, row); - addButton("&MessageBoxes", 35, row, - { - new DemoMsgBoxWindow(application); - } - ); - } - row += 2; - - addLabel("Open me as modal", 1, row); - addButton("W&indow", 35, row, - { - new DemoMainWindow(application, Flag.MODAL); - } - ); - - row += 2; - - addLabel("Variable-width text field:", 1, row); - addField(35, row++, 15, false, "Field text"); - - addLabel("Fixed-width text field:", 1, row); - addField(35, row, 15, true); - row += 2; - - if (!isModal) { - addLabel("Radio buttons and checkboxes", 1, row); - addButton("&Checkboxes", 35, row, &openCheckboxWindow); - } - row += 2; - - if (!isModal) { - addLabel("Editor window", 1, row); - addButton("Edito&r", 35, row, - { - new TEditor(application, 0, 0, 60, 15); - } - ); - } - row += 2; - - if (!isModal) { - addLabel("Text areas", 1, row); - addButton("&Text", 35, row, - { - new DemoTextWindow(application); - } - ); - } - row += 2; - - if (!isModal) { - addLabel("Tree views", 1, row); - addButton("Tree&View", 35, row, - { - new DemoTreeViewWindow(application); - } - ); - } - row += 2; - - version(Posix) { - if (!isModal) { - addLabel("Terminal", 1, row); - addButton("Termi&nal", 35, row, - { - application.openTerminal(0, 0); - } - ); - } - row += 2; - } - - TProgressBar bar = addProgressBar(1, row, 22); - row++; - TLabel timerLabel = addLabel("Timer", 1, row); - timer = parent.addTimer(100, - { - static int i = 0; - auto writer = appender!dstring(); - formattedWrite(writer, "Timer: %d", i); - timerLabel.text = writer.data; - timerLabel.width = cast(uint)timerLabel.text.length; - if (i < 100) { - i++; - } - bar.value = i; - parent.repaint = true; - }, true); + int row = 1; + + // Add some widgets + if (!isModal) { + addLabel("Message Boxes", 1, row); + addButton("&MessageBoxes", 35, row, + { + new DemoMsgBoxWindow(application); + } + ); + } + row += 2; + + addLabel("Open me as modal", 1, row); + addButton("W&indow", 35, row, + { + new DemoMainWindow(application, Flag.MODAL); + } + ); + + row += 2; + + addLabel("Variable-width text field:", 1, row); + addField(35, row++, 15, false, "Field text"); + + addLabel("Fixed-width text field:", 1, row); + addField(35, row, 15, true); + row += 2; + + if (!isModal) { + addLabel("Radio buttons and checkboxes", 1, row); + addButton("&Checkboxes", 35, row, &openCheckboxWindow); + } + row += 2; + + if (!isModal) { + addLabel("Editor window", 1, row); + addButton("Edito&r", 35, row, + { + new TEditor(application, 0, 0, 60, 15); + } + ); + } + row += 2; + + if (!isModal) { + addLabel("Text areas", 1, row); + addButton("&Text", 35, row, + { + new DemoTextWindow(application); + } + ); + } + row += 2; + + if (!isModal) { + addLabel("Tree views", 1, row); + addButton("Tree&View", 35, row, + { + new DemoTreeViewWindow(application); + } + ); + } + row += 2; + + version(Posix) { + if (!isModal) { + addLabel("Terminal", 1, row); + addButton("Termi&nal", 35, row, + { + application.openTerminal(0, 0); + } + ); + } + row += 2; + } + + TProgressBar bar = addProgressBar(1, row, 22); + row++; + TLabel timerLabel = addLabel("Timer", 1, row); + timer = parent.addTimer(100, + { + static int i = 0; + auto writer = appender!dstring(); + formattedWrite(writer, "Timer: %d", i); + timerLabel.text = writer.data; + timerLabel.width = cast(uint)timerLabel.text.length; + if (i < 100) { + i++; + } + bar.value = i; + parent.repaint = true; + }, true); */ } } @@ -188,13 +189,45 @@ class DemoApplication extends TApplication { * Public constructor */ public DemoApplication() throws Exception { - super(null, null); - new DemoMainWindow(this); - TWindow window2 = new DemoMainWindow(this); + super(null, null); + new DemoMainWindow(this); + + // TEMPORARY + TWindow window2 = new DemoMainWindow(this); window2.setHeight(5); window2.setWidth(25); window2.setX(17); window2.setY(6); + // TEMPORARY + + // Add the menus + addFileMenu(); + addEditMenu(); + + TMenu demoMenu = addMenu("&Demo"); + TMenuItem item = demoMenu.addItem(2000, "&Checkable"); + item.setCheckable(true); + item = demoMenu.addItem(2001, "Disabled"); + item.setEnabled(false); + item = demoMenu.addItem(2002, "&Normal"); + TSubMenu subMenu = demoMenu.addSubMenu("Sub-&Menu"); + item = demoMenu.addItem(2010, "N&ormal A&&D"); + + item = subMenu.addItem(2000, "&Checkable (sub)"); + item.setCheckable(true); + item = subMenu.addItem(2001, "Disabled (sub)"); + item.setEnabled(false); + item = subMenu.addItem(2002, "&Normal (sub)"); + + subMenu = subMenu.addSubMenu("Sub-&Menu"); + item = subMenu.addItem(2000, "&Checkable (sub)"); + item.setCheckable(true); + item = subMenu.addItem(2001, "Disabled (sub)"); + item.setEnabled(false); + item = subMenu.addItem(2002, "&Normal (sub)"); + + addWindowMenu(); + } } @@ -208,12 +241,12 @@ public class Demo1 { * @param args Command line arguments */ public static void main(String [] args) { - try { - DemoApplication app = new DemoApplication(); - app.run(); - } catch (Exception e) { - e.printStackTrace(); - } + try { + DemoApplication app = new DemoApplication(); + app.run(); + } catch (Exception e) { + e.printStackTrace(); + } } } diff --git a/src/jexer/TApplication.java b/src/jexer/TApplication.java index b2ee507..35aa32d 100644 --- a/src/jexer/TApplication.java +++ b/src/jexer/TApplication.java @@ -84,9 +84,15 @@ public class TApplication { private int mouseY; /** - * Event queue that will be drained by either primary or secondary Fiber. + * Event queue that is filled by run(). */ - private List eventQueue; + private List fillEventQueue; + + /** + * Event queue that will be drained by either primary or secondary + * Thread. + */ + private List drainEventQueue; /** * Top-level menus in this application. @@ -190,13 +196,14 @@ public class TApplication { public TApplication(final InputStream input, final OutputStream output) throws UnsupportedEncodingException { - backend = new ECMA48Backend(input, output); - theme = new ColorTheme(); - desktopBottom = getScreen().getHeight() - 1; - eventQueue = new LinkedList(); - windows = new LinkedList(); - menus = new LinkedList(); - subMenus = new LinkedList(); + backend = new ECMA48Backend(input, output); + theme = new ColorTheme(); + desktopBottom = getScreen().getHeight() - 1; + fillEventQueue = new LinkedList(); + drainEventQueue = new LinkedList(); + windows = new LinkedList(); + menus = new LinkedList(); + subMenus = new LinkedList(); } /** @@ -315,23 +322,39 @@ public class TApplication { * Run this application until it exits. */ public final void run() { - List events = new LinkedList(); - while (!quit) { // Timeout is in milliseconds, so default timeout after 1 second // of inactivity. int timeout = getSleepTime(1000); - if (eventQueue.size() > 0) { - // Do not wait if there are definitely events waiting to be - // processed or a screen redraw to do. - timeout = 0; + // See if there are any definitely events waiting to be processed + // or a screen redraw to do. If so, do not wait if there is no + // I/O coming in. + synchronized (drainEventQueue) { + if (drainEventQueue.size() > 0) { + timeout = 0; + } + } + synchronized (fillEventQueue) { + if (fillEventQueue.size() > 0) { + timeout = 0; + } } - // Pull any pending input events - backend.getEvents(events, timeout); - metaHandleEvents(events); - events.clear(); + // Pull any pending I/O events + backend.getEvents(fillEventQueue, timeout); + + // Dispatch each event to the appropriate handler, one at a time. + for (;;) { + TInputEvent event = null; + synchronized (fillEventQueue) { + if (fillEventQueue.size() == 0) { + break; + } + event = fillEventQueue.remove(0); + } + metaHandleEvent(event); + } // Process timers and call doIdle()'s doIdle(); @@ -364,92 +387,88 @@ public class TApplication { /** * Peek at certain application-level events, add to eventQueue, and wake - * up the consuming Fiber. + * up the consuming Thread. * - * @param events the input events to consume + * @param event the input event to consume */ - private void metaHandleEvents(final List events) { + private void metaHandleEvent(final TInputEvent event) { - for (TInputEvent event: events) { + /* + System.err.printf(String.format("metaHandleEvents event: %s\n", + event)); System.err.flush(); + */ - /* - System.err.printf(String.format("metaHandleEvents event: %s\n", - event)); System.err.flush(); - */ + if (quit) { + // Do no more processing if the application is already trying + // to exit. + return; + } - if (quit) { - // Do no more processing if the application is already trying - // to exit. + // DEBUG + if (event instanceof TKeypressEvent) { + TKeypressEvent keypress = (TKeypressEvent) event; + if (keypress.equals(kbAltX)) { + quit = true; return; } + } + // DEBUG - // DEBUG - if (event instanceof TKeypressEvent) { - TKeypressEvent keypress = (TKeypressEvent) event; - if (keypress.equals(kbAltX)) { - quit = true; - return; - } - } - // DEBUG - - // Special application-wide events ------------------------------- + // Special application-wide events ------------------------------- - // Abort everything - if (event instanceof TCommandEvent) { - TCommandEvent command = (TCommandEvent) event; - if (command.getCmd().equals(cmAbort)) { - quit = true; - return; - } + // Abort everything + if (event instanceof TCommandEvent) { + TCommandEvent command = (TCommandEvent) event; + if (command.getCmd().equals(cmAbort)) { + quit = true; + return; } + } - // Screen resize - if (event instanceof TResizeEvent) { - TResizeEvent resize = (TResizeEvent) event; - getScreen().setDimensions(resize.getWidth(), - resize.getHeight()); - desktopBottom = getScreen().getHeight() - 1; - repaint = true; - mouseX = 0; - mouseY = 0; - continue; - } + // Screen resize + if (event instanceof TResizeEvent) { + TResizeEvent resize = (TResizeEvent) event; + getScreen().setDimensions(resize.getWidth(), + resize.getHeight()); + desktopBottom = getScreen().getHeight() - 1; + repaint = true; + mouseX = 0; + mouseY = 0; + return; + } - // Peek at the mouse position - if (event instanceof TMouseEvent) { - TMouseEvent mouse = (TMouseEvent) event; - if ((mouseX != mouse.getX()) || (mouseY != mouse.getY())) { - mouseX = mouse.getX(); - mouseY = mouse.getY(); - drawMouse(); - } + // Peek at the mouse position + if (event instanceof TMouseEvent) { + TMouseEvent mouse = (TMouseEvent) event; + if ((mouseX != mouse.getX()) || (mouseY != mouse.getY())) { + mouseX = mouse.getX(); + mouseY = mouse.getY(); + drawMouse(); } + } - // TODO: change to two separate threads - primaryHandleEvent(event); - - /* + // TODO: change to two separate threads + primaryHandleEvent(event); - // Put into the main queue - addEvent(event); + /* - // Have one of the two consumer Fibers peel the events off - // the queue. - if (secondaryEventFiber !is null) { - assert(secondaryEventFiber.state == Fiber.State.HOLD); + // Put into the main queue + addEvent(event); - // Wake up the secondary handler for these events - secondaryEventFiber.call(); - } else { - assert(primaryEventFiber.state == Fiber.State.HOLD); + // Have one of the two consumer Fibers peel the events off + // the queue. + if (secondaryEventFiber !is null) { + assert(secondaryEventFiber.state == Fiber.State.HOLD); - // Wake up the primary handler for these events - primaryEventFiber.call(); - } - */ + // Wake up the secondary handler for these events + secondaryEventFiber.call(); + } else { + assert(primaryEventFiber.state == Fiber.State.HOLD); - } // for (TInputEvent event: events) + // Wake up the primary handler for these events + primaryEventFiber.call(); + } + */ } @@ -1022,10 +1041,10 @@ public class TApplication { */ protected boolean onMenu(final TMenuEvent menu) { - /* - TODO // Default: handle MID_EXIT - if (menu.id == TMenu.MID_EXIT) { + if (menu.getId() == TMenu.MID_EXIT) { + /* + TODO if (messageBox("Confirmation", "Exit application?", TMessageBox.Type.YESNO).result == TMessageBox.Result.YES) { quit = true; @@ -1033,30 +1052,36 @@ public class TApplication { // System.err.printf("onMenu MID_EXIT result: quit = %s\n", quit); repaint = true; return true; + */ + quit = true; + repaint = true; + return true; } + /* + TODO if (menu.id == TMenu.MID_SHELL) { openTerminal(0, 0, TWindow.Flag.RESIZABLE); repaint = true; return true; } + */ - if (menu.id == TMenu.MID_TILE) { + if (menu.getId() == TMenu.MID_TILE) { tileWindows(); repaint = true; return true; } - if (menu.id == TMenu.MID_CASCADE) { + if (menu.getId() == TMenu.MID_CASCADE) { cascadeWindows(); repaint = true; return true; } - if (menu.id == TMenu.MID_CLOSE_ALL) { + if (menu.getId() == TMenu.MID_CLOSE_ALL) { closeAllWindows(); repaint = true; return true; } - */ return false; } @@ -1125,10 +1150,9 @@ public class TApplication { * @param event new event to add to the queue */ public final void addMenuEvent(final TInputEvent event) { - /* - TODO - synchronize correctly - eventQueue ~= event; - */ + synchronized (fillEventQueue) { + fillEventQueue.add(event); + } closeMenu(); } @@ -1141,4 +1165,163 @@ public class TApplication { subMenus.add(menu); } + /** + * Convenience function to add a top-level menu. + * + * @param title menu title + * @return the new menu + */ + public final TMenu addMenu(String title) { + int x = 0; + int y = 0; + TMenu menu = new TMenu(this, x, y, title); + menus.add(menu); + recomputeMenuX(); + return menu; + } + + /** + * Convenience function to add a default "File" menu. + * + * @return the new menu + */ + public final TMenu addFileMenu() { + TMenu fileMenu = addMenu("&File"); + fileMenu.addDefaultItem(TMenu.MID_OPEN_FILE); + fileMenu.addSeparator(); + fileMenu.addDefaultItem(TMenu.MID_SHELL); + fileMenu.addDefaultItem(TMenu.MID_EXIT); + return fileMenu; + } + + /** + * Convenience function to add a default "Edit" menu. + * + * @return the new menu + */ + public final TMenu addEditMenu() { + TMenu editMenu = addMenu("&Edit"); + editMenu.addDefaultItem(TMenu.MID_CUT); + editMenu.addDefaultItem(TMenu.MID_COPY); + editMenu.addDefaultItem(TMenu.MID_PASTE); + editMenu.addDefaultItem(TMenu.MID_CLEAR); + return editMenu; + } + + /** + * Convenience function to add a default "Window" menu. + * + * @return the new menu + */ + final public TMenu addWindowMenu() { + TMenu windowMenu = addMenu("&Window"); + windowMenu.addDefaultItem(TMenu.MID_TILE); + windowMenu.addDefaultItem(TMenu.MID_CASCADE); + windowMenu.addDefaultItem(TMenu.MID_CLOSE_ALL); + windowMenu.addSeparator(); + windowMenu.addDefaultItem(TMenu.MID_WINDOW_MOVE); + windowMenu.addDefaultItem(TMenu.MID_WINDOW_ZOOM); + windowMenu.addDefaultItem(TMenu.MID_WINDOW_NEXT); + windowMenu.addDefaultItem(TMenu.MID_WINDOW_PREVIOUS); + windowMenu.addDefaultItem(TMenu.MID_WINDOW_CLOSE); + return windowMenu; + } + + /** + * Close all open windows. + */ + private void closeAllWindows() { + // Don't do anything if we are in the menu + if (activeMenu != null) { + return; + } + for (TWindow window: windows) { + closeWindow(window); + } + } + + /** + * Re-layout the open windows as non-overlapping tiles. This produces + * almost the same results as Turbo Pascal 7.0's IDE. + */ + private void tileWindows() { + // Don't do anything if we are in the menu + if (activeMenu != null) { + return; + } + int z = windows.size(); + if (z == 0) { + return; + } + int a = 0; + int b = 0; + a = (int)(Math.sqrt(z)); + int c = 0; + while (c < a) { + b = (z - c) / a; + if (((a * b) + c) == z) { + break; + } + c++; + } + assert (a > 0); + assert (b > 0); + assert (c < a); + int newWidth = (getScreen().getWidth() / a); + int newHeight1 = ((getScreen().getHeight() - 1) / b); + int newHeight2 = ((getScreen().getHeight() - 1) / (b + c)); + // System.err.printf("Z %s a %s b %s c %s newWidth %s newHeight1 %s newHeight2 %s", + // z, a, b, c, newWidth, newHeight1, newHeight2); + + List sorted = new LinkedList(windows); + Collections.sort(sorted); + Collections.reverse(sorted); + for (int i = 0; i < sorted.size(); i++) { + int logicalX = i / b; + int logicalY = i % b; + if (i >= ((a - 1) * b)) { + logicalX = a - 1; + logicalY = i - ((a - 1) * b); + } + + TWindow w = sorted.get(i); + w.setX(logicalX * newWidth); + w.setWidth(newWidth); + if (i >= ((a - 1) * b)) { + w.setY((logicalY * newHeight2) + 1); + w.setHeight(newHeight2); + } else { + w.setY((logicalY * newHeight1) + 1); + w.setHeight(newHeight1); + } + } + } + + /** + * Re-layout the open windows as overlapping cascaded windows. + */ + private void cascadeWindows() { + // Don't do anything if we are in the menu + if (activeMenu != null) { + return; + } + int x = 0; + int y = 1; + List sorted = new LinkedList(windows); + Collections.sort(sorted); + Collections.reverse(sorted); + for (TWindow w: sorted) { + w.setX(x); + w.setY(y); + x++; + y++; + if (x > getScreen().getWidth()) { + x = 0; + } + if (y >= getScreen().getHeight()) { + y = 1; + } + } + } + } diff --git a/src/jexer/io/ECMA48Terminal.java b/src/jexer/io/ECMA48Terminal.java index b608a69..687d7a2 100644 --- a/src/jexer/io/ECMA48Terminal.java +++ b/src/jexer/io/ECMA48Terminal.java @@ -634,7 +634,9 @@ public class ECMA48Terminal implements Runnable { public void getEvents(final List queue) { synchronized (eventQueue) { if (eventQueue.size() > 0) { - queue.addAll(eventQueue); + synchronized (queue) { + queue.addAll(eventQueue); + } eventQueue.clear(); } } @@ -665,7 +667,9 @@ public class ECMA48Terminal implements Runnable { synchronized (eventQueue) { if (eventQueue.size() > 0) { - queue.addAll(eventQueue); + synchronized (queue) { + queue.addAll(eventQueue); + } eventQueue.clear(); } } diff --git a/src/jexer/menu/TMenu.java b/src/jexer/menu/TMenu.java index da349bf..75871c0 100644 --- a/src/jexer/menu/TMenu.java +++ b/src/jexer/menu/TMenu.java @@ -321,7 +321,7 @@ public final class TMenu extends TWindow { * @param key global keyboard accelerator * @return the new menu item */ - public TMenuItem addItem(final int id, final String label, + public final TMenuItem addItem(final int id, final String label, final TKeypress key) { assert (id >= 1024); @@ -364,7 +364,7 @@ public final class TMenu extends TWindow { * @param label menu item label * @return the new menu item */ - public TMenuItem addItem(final int id, final String label) { + public final TMenuItem addItem(final int id, final String label) { assert (id >= 1024); return addItemInternal(id, label); } @@ -400,7 +400,7 @@ public final class TMenu extends TWindow { * (inclusive). * @return the new menu item */ - public TMenuItem addDefaultItem(final int id) { + public final TMenuItem addDefaultItem(final int id) { assert (id >= 0); assert (id < 1024); @@ -488,7 +488,7 @@ public final class TMenu extends TWindow { /** * Convenience function to add a menu separator. */ - public void addSeparator() { + public final void addSeparator() { int newY = getChildren().size() + 1; assert (newY < getHeight()); @@ -503,7 +503,7 @@ public final class TMenu extends TWindow { * denoted by prefixing a letter with "&", e.g. "&File" * @return the new sub-menu */ - public TSubMenu addSubMenu(final String title) { + public final TSubMenu addSubMenu(final String title) { int newY = getChildren().size() + 1; assert (newY < getHeight()); diff --git a/src/jexer/menu/TMenuItem.java b/src/jexer/menu/TMenuItem.java index d7e56e2..607341b 100644 --- a/src/jexer/menu/TMenuItem.java +++ b/src/jexer/menu/TMenuItem.java @@ -61,6 +61,15 @@ public class TMenuItem extends TWidget { */ private boolean checkable = false; + /** + * Set checkable flag. + * + * @param checkable if true, this menu item can be checked/unchecked + */ + public final void setCheckable(final boolean checkable) { + this.checkable = checkable; + } + /** * When true, this item is checked. */ diff --git a/src/jexer/menu/TSubMenu.java b/src/jexer/menu/TSubMenu.java index 24cc67d..c37bf3a 100644 --- a/src/jexer/menu/TSubMenu.java +++ b/src/jexer/menu/TSubMenu.java @@ -30,6 +30,7 @@ */ package jexer.menu; +import jexer.TKeypress; import jexer.TWidget; import jexer.bits.CellAttributes; import jexer.bits.GraphicsChars; @@ -173,4 +174,59 @@ public class TSubMenu extends TMenuItem { return this; } + /** + * 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 final TMenuItem addItem(final int id, final String label, + final TKeypress key) { + + return menu.addItem(id, label, key); + } + + /** + * 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 final TMenuItem addItem(final int id, final String label) { + return menu.addItem(id, label); + } + + /** + * 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 final TMenuItem addDefaultItem(final int id) { + return menu.addDefaultItem(id); + } + + /** + * Convenience function to add a menu separator. + */ + public final void addSeparator() { + menu.addSeparator(); + } + + /** + * 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 final TSubMenu addSubMenu(final String title) { + return menu.addSubMenu(title); + } + + }