+ /**
+ * Turn off a sub-menu.
+ */
+ public final void closeSubMenu() {
+ assert (activeMenu != null);
+ TMenu item = subMenus.get(subMenus.size() - 1);
+ assert (item != null);
+ item.setActive(false);
+ subMenus.remove(subMenus.size() - 1);
+ }
+
+ /**
+ * Switch to the next menu.
+ *
+ * @param forward if true, then switch to the next menu in the list,
+ * otherwise switch to the previous menu in the list
+ */
+ public final void switchMenu(final boolean forward) {
+ assert (activeMenu != null);
+
+ for (TMenu menu: subMenus) {
+ menu.setActive(false);
+ }
+ subMenus.clear();
+
+ for (int i = 0; i < menus.size(); i++) {
+ if (activeMenu == menus.get(i)) {
+ if (forward) {
+ if (i < menus.size() - 1) {
+ i++;
+ }
+ } else {
+ if (i > 0) {
+ i--;
+ }
+ }
+ activeMenu.setActive(false);
+ activeMenu = menus.get(i);
+ activeMenu.setActive(true);
+ return;
+ }
+ }
+ }
+
+ /**
+ * Method that TApplication subclasses can override to handle menu or
+ * posted command events.
+ *
+ * @param command command event
+ * @return if true, this event was consumed
+ */
+ protected boolean onCommand(final TCommandEvent command) {
+ // Default: handle cmExit
+ if (command.equals(cmExit)) {
+ if (messageBox("Confirmation", "Exit application?",
+ TMessageBox.Type.YESNO).getResult() == TMessageBox.Result.YES) {
+ quit = true;
+ }
+ return true;
+ }
+
+ if (command.equals(cmShell)) {
+ openTerminal(0, 0, TWindow.RESIZABLE);
+ return true;
+ }
+
+ if (command.equals(cmTile)) {
+ tileWindows();
+ return true;
+ }
+ if (command.equals(cmCascade)) {
+ cascadeWindows();
+ return true;
+ }
+ if (command.equals(cmCloseAll)) {
+ closeAllWindows();
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Method that TApplication subclasses can override to handle menu
+ * events.
+ *
+ * @param menu menu event
+ * @return if true, this event was consumed
+ */
+ protected boolean onMenu(final TMenuEvent menu) {
+
+ // Default: handle MID_EXIT
+ if (menu.getId() == TMenu.MID_EXIT) {
+ if (messageBox("Confirmation", "Exit application?",
+ TMessageBox.Type.YESNO).getResult() == TMessageBox.Result.YES) {
+ quit = true;
+ }
+ return true;
+ }
+
+ if (menu.getId() == TMenu.MID_SHELL) {
+ openTerminal(0, 0, TWindow.RESIZABLE);
+ return true;
+ }
+
+ if (menu.getId() == TMenu.MID_TILE) {
+ tileWindows();
+ return true;
+ }
+ if (menu.getId() == TMenu.MID_CASCADE) {
+ cascadeWindows();
+ return true;
+ }
+ if (menu.getId() == TMenu.MID_CLOSE_ALL) {
+ closeAllWindows();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Method that TApplication subclasses can override to handle keystrokes.
+ *
+ * @param keypress keystroke event
+ * @return if true, this event was consumed
+ */
+ protected boolean onKeypress(final TKeypressEvent keypress) {
+ // Default: only menu shortcuts
+
+ // Process Alt-F, Alt-E, etc. menu shortcut keys
+ if (!keypress.getKey().isFnKey()
+ && keypress.getKey().isAlt()
+ && !keypress.getKey().isCtrl()
+ && (activeMenu == null)
+ ) {
+
+ assert (subMenus.size() == 0);
+
+ for (TMenu menu: menus) {
+ if (Character.toLowerCase(menu.getMnemonic().getShortcut())
+ == Character.toLowerCase(keypress.getKey().getChar())
+ ) {
+ activeMenu = menu;
+ menu.setActive(true);
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Add a menu item to the global list. If it has a keyboard accelerator,
+ * that will be added the global hash.
+ *
+ * @param item the menu item
+ */
+ public final void addMenuItem(final TMenuItem item) {
+ menuItems.add(item);
+
+ TKeypress key = item.getKey();
+ if (key != null) {
+ synchronized (accelerators) {
+ assert (accelerators.get(key) == null);
+ accelerators.put(key.toLowerCase(), item);
+ }
+ }
+ }
+
+ /**
+ * Disable one menu item.
+ *
+ * @param id the menu item ID
+ */
+ public final void disableMenuItem(final int id) {
+ for (TMenuItem item: menuItems) {
+ if (item.getId() == id) {
+ item.setEnabled(false);
+ }
+ }
+ }
+
+ /**
+ * Disable the range of menu items with ID's between lower and upper,
+ * inclusive.
+ *
+ * @param lower the lowest menu item ID
+ * @param upper the highest menu item ID
+ */
+ public final void disableMenuItems(final int lower, final int upper) {
+ for (TMenuItem item: menuItems) {
+ if ((item.getId() >= lower) && (item.getId() <= upper)) {
+ item.setEnabled(false);
+ }
+ }
+ }
+
+ /**
+ * Enable one menu item.
+ *
+ * @param id the menu item ID
+ */
+ public final void enableMenuItem(final int id) {
+ for (TMenuItem item: menuItems) {
+ if (item.getId() == id) {
+ item.setEnabled(true);
+ }
+ }
+ }
+
+ /**
+ * Enable the range of menu items with ID's between lower and upper,
+ * inclusive.
+ *
+ * @param lower the lowest menu item ID
+ * @param upper the highest menu item ID
+ */
+ public final void enableMenuItems(final int lower, final int upper) {
+ for (TMenuItem item: menuItems) {
+ if ((item.getId() >= lower) && (item.getId() <= upper)) {
+ item.setEnabled(true);
+ }
+ }
+ }
+
+ /**
+ * 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) {
+ synchronized (fillEventQueue) {
+ fillEventQueue.add(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);
+ }
+
+ /**
+ * Convenience function to add a top-level menu.
+ *
+ * @param title menu title
+ * @return the new menu
+ */
+ public final TMenu addMenu(final 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
+ */
+ public final 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) {