X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fjexer%2FTApplication.java;h=91d2e98d7c039da4aeb9d01c84bb33385f762772;hb=55d2b2c2b29ce51f4f910448a115073371deeae8;hp=6bec9006c9a2e95ee057a7d042b61666e354b322;hpb=cf9af8df455c0ea5390e4c0ed2555506a051a379;p=fanfix.git diff --git a/src/jexer/TApplication.java b/src/jexer/TApplication.java index 6bec900..91d2e98 100644 --- a/src/jexer/TApplication.java +++ b/src/jexer/TApplication.java @@ -1,29 +1,27 @@ -/** +/* * 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. + * The MIT License (MIT) * - * Copyright (C) 2015 Kevin Lamonte + * Copyright (C) 2016 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. + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: * - * 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. + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * 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 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. * * @author Kevin Lamonte [kevin.lamonte@gmail.com] * @version 1 @@ -31,7 +29,10 @@ package jexer; import java.io.InputStream; +import java.io.IOException; import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.Reader; import java.io.UnsupportedEncodingException; import java.util.Collections; import java.util.Date; @@ -51,7 +52,7 @@ import jexer.event.TMenuEvent; import jexer.event.TMouseEvent; import jexer.event.TResizeEvent; import jexer.backend.Backend; -import jexer.backend.AWTBackend; +import jexer.backend.SwingBackend; import jexer.backend.ECMA48Backend; import jexer.io.Screen; import jexer.menu.TMenu; @@ -61,7 +62,7 @@ import static jexer.TCommand.*; /** * TApplication sets up a full Text User Interface application. */ -public class TApplication { +public class TApplication implements Runnable { /** * If true, emit thread stuff to System.err. @@ -73,6 +74,26 @@ public class TApplication { */ private static final boolean debugEvents = false; + /** + * Two backend types are available. + */ + public static enum BackendType { + /** + * A Swing JFrame. + */ + SWING, + + /** + * An ECMA48 / ANSI X3.64 / XTERM style terminal. + */ + ECMA48, + + /** + * Synonym for ECMA48. + */ + XTERM + } + /** * WidgetEventHandler is the main event consumer loop. There are at most * two such threads in existence: the primary for normal case and a @@ -154,6 +175,11 @@ public class TApplication { } } + // Wait for drawAll() or doIdle() to be done, then handle the + // events. + boolean oldLock = lockHandleEvent(); + assert (oldLock == false); + // Pull all events off the queue for (;;) { TInputEvent event = null; @@ -163,10 +189,6 @@ public class TApplication { } event = application.drainEventQueue.remove(0); } - // Wait for drawAll() or doIdle() to be done, then handle - // the event. - boolean oldLock = lockHandleEvent(); - assert (oldLock == false); application.repaint = true; if (primary) { primaryHandleEvent(event); @@ -190,14 +212,14 @@ public class TApplication { // All done! return; - } else { - // Unlock. Either I am primary thread, or I am - // secondary thread and still running. - oldLock = unlockHandleEvent(); - assert (oldLock == true); } } // for (;;) + // Unlock. Either I am primary thread, or I am secondary + // thread and still running. + oldLock = unlockHandleEvent(); + assert (oldLock == true); + // I have done some work of some kind. Tell the main run() // loop to wake up now. synchronized (application) { @@ -261,7 +283,14 @@ public class TApplication { synchronized (this) { // Wait for TApplication.run() to finish using the global state // before allowing further event processing. - while (lockoutHandleEvent == true) {} + while (lockoutHandleEvent == true) { + try { + // Backoff so that the backend can finish its work. + Thread.sleep(5); + } catch (InterruptedException e) { + // SQUASH + } + } oldValue = insideHandleEvent; insideHandleEvent = true; @@ -310,7 +339,14 @@ public class TApplication { lockoutHandleEvent = true; // Wait for the last event to finish processing before returning // control to TApplication.run(). - while (insideHandleEvent == true) {} + while (insideHandleEvent == true) { + try { + // Backoff so that the event handler can finish its work. + Thread.sleep(1); + } catch (InterruptedException e) { + // SQUASH + } + } if (debugThreads) { System.err.printf(" XXX\n"); @@ -334,6 +370,15 @@ public class TApplication { */ private Backend backend; + /** + * Get the Backend. + * + * @return the Backend + */ + public final Backend getBackend() { + return backend; + } + /** * Get the Screen. * @@ -394,6 +439,11 @@ public class TApplication { */ private Map accelerators; + /** + * All menu items. + */ + private List menuItems; + /** * Windows and widgets pull colors from this ColorTheme. */ @@ -461,6 +511,33 @@ public class TApplication { /** * Public constructor. * + * @param backendType BackendType.XTERM, BackendType.ECMA48 or + * BackendType.SWING + * @throws UnsupportedEncodingException if an exception is thrown when + * creating the InputStreamReader + */ + public TApplication(final BackendType backendType) + throws UnsupportedEncodingException { + + switch (backendType) { + case SWING: + backend = new SwingBackend(this); + break; + case XTERM: + // Fall through... + case ECMA48: + backend = new ECMA48Backend(this, null, null); + break; + default: + throw new IllegalArgumentException("Invalid backend type: " + + backendType); + } + TApplicationImpl(); + } + + /** + * Public constructor. The backend type will be BackendType.ECMA48. + * * @param input an InputStream connected to the remote user, or null for * System.in. If System.in is used, then on non-Windows systems it will * be put in raw mode; shutdown() will (blindly!) put System.in in cooked @@ -474,26 +551,59 @@ public class TApplication { public TApplication(final InputStream input, final OutputStream output) throws UnsupportedEncodingException { - // AWT is the default backend on Windows unless explicitly overridden - // by jexer.AWT. - boolean useAWT = false; - if (System.getProperty("os.name").startsWith("Windows")) { - useAWT = true; - } - if (System.getProperty("jexer.AWT") != null) { - if (System.getProperty("jexer.AWT", "false").equals("true")) { - useAWT = true; - } else { - useAWT = false; - } - } + backend = new ECMA48Backend(this, input, output); + TApplicationImpl(); + } + /** + * Public constructor. The backend type will be BackendType.ECMA48. + * + * @param input the InputStream underlying 'reader'. Its available() + * method is used to determine if reader.read() will block or not. + * @param reader a Reader connected to the remote user. + * @param writer a PrintWriter connected to the remote user. + * @param setRawMode if true, set System.in into raw mode with stty. + * This should in general not be used. It is here solely for Demo3, + * which uses System.in. + * @throws IllegalArgumentException if input, reader, or writer are null. + */ + public TApplication(final InputStream input, final Reader reader, + final PrintWriter writer, final boolean setRawMode) { + + backend = new ECMA48Backend(this, input, reader, writer, setRawMode); + TApplicationImpl(); + } - if (useAWT) { - backend = new AWTBackend(this); - } else { - backend = new ECMA48Backend(this, input, output); - } + /** + * Public constructor. The backend type will be BackendType.ECMA48. + * + * @param input the InputStream underlying 'reader'. Its available() + * method is used to determine if reader.read() will block or not. + * @param reader a Reader connected to the remote user. + * @param writer a PrintWriter connected to the remote user. + * @throws IllegalArgumentException if input, reader, or writer are null. + */ + public TApplication(final InputStream input, final Reader reader, + final PrintWriter writer) { + + this(input, reader, writer, false); + } + + /** + * Public constructor. This hook enables use with new non-Jexer + * backends. + * + * @param backend a Backend that is already ready to go. + */ + public TApplication(final Backend backend) { + this.backend = backend; + TApplicationImpl(); + } + + /** + * Finish construction once the backend is set. + */ + private void TApplicationImpl() { theme = new ColorTheme(); desktopBottom = getScreen().getHeight() - 1; fillEventQueue = new ArrayList(); @@ -503,6 +613,7 @@ public class TApplication { subMenus = new LinkedList(); timers = new LinkedList(); accelerators = new HashMap(); + menuItems = new ArrayList(); // Setup the main consumer thread primaryEventHandler = new WidgetEventHandler(this, true); @@ -516,23 +627,27 @@ public class TApplication { * @param y row position */ private void invertCell(final int x, final int y) { - synchronized (getScreen()) { - CellAttributes attr = getScreen().getAttrXY(x, y); - attr.setForeColor(attr.getForeColor().invert()); - attr.setBackColor(attr.getBackColor().invert()); - getScreen().putAttrXY(x, y, attr, false); + if (debugThreads) { + System.err.printf("invertCell() %d %d\n", x, y); } + CellAttributes attr = getScreen().getAttrXY(x, y); + attr.setForeColor(attr.getForeColor().invert()); + attr.setBackColor(attr.getBackColor().invert()); + getScreen().putAttrXY(x, y, attr, false); } /** * Draw everything. */ - public final void drawAll() { + private void drawAll() { if (debugThreads) { System.err.printf("drawAll() enter\n"); } if (!repaint) { + if (debugThreads) { + System.err.printf("drawAll() !repaint\n"); + } synchronized (getScreen()) { if ((oldMouseX != mouseX) || (oldMouseY != mouseY)) { // The only thing that has happened is the mouse moved. @@ -542,11 +657,11 @@ public class TApplication { oldMouseX = mouseX; oldMouseY = mouseY; } + if (getScreen().isDirty()) { + backend.flushScreen(); + } + return; } - if (getScreen().isDirty()) { - backend.flushScreen(); - } - return; } if (debugThreads) { @@ -581,7 +696,7 @@ public class TApplication { for (TMenu menu: menus) { CellAttributes menuColor; CellAttributes menuMnemonicColor; - if (menu.getActive()) { + if (menu.isActive()) { menuColor = theme.getColor("tmenu.highlighted"); menuMnemonicColor = theme.getColor("tmenu.mnemonic.highlighted"); } else { @@ -591,12 +706,12 @@ public class TApplication { // Draw the menu title getScreen().hLineXY(x, 0, menu.getTitle().length() + 2, ' ', menuColor); - getScreen().putStrXY(x + 1, 0, menu.getTitle(), menuColor); + getScreen().putStringXY(x + 1, 0, menu.getTitle(), menuColor); // Draw the highlight character getScreen().putCharXY(x + 1 + menu.getMnemonic().getShortcutIdx(), 0, menu.getMnemonic().getShortcut(), menuMnemonicColor); - if (menu.getActive()) { + if (menu.isActive()) { menu.drawChildren(); // Reset the screen clipping so we can draw the next title. getScreen().resetClipping(); @@ -612,12 +727,14 @@ public class TApplication { // Draw the mouse pointer invertCell(mouseX, mouseY); + oldMouseX = mouseX; + oldMouseY = mouseY; // Place the cursor if it is visible TWidget activeWidget = null; if (sorted.size() > 0) { activeWidget = sorted.get(sorted.size() - 1).getActiveChild(); - if (activeWidget.visibleCursor()) { + if (activeWidget.isCursorVisible()) { getScreen().putCursor(true, activeWidget.getCursorAbsoluteX(), activeWidget.getCursorAbsoluteY()); cursor = true; @@ -630,7 +747,9 @@ public class TApplication { } // Flush the screen contents - backend.flushScreen(); + if (getScreen().isDirty()) { + backend.flushScreen(); + } repaint = false; } @@ -638,7 +757,7 @@ public class TApplication { /** * Run this application until it exits. */ - public final void run() { + public void run() { while (!quit) { // Timeout is in milliseconds, so default timeout after 1 second // of inactivity. @@ -649,24 +768,11 @@ public class TApplication { if (!repaint && ((mouseX == oldMouseX) && (mouseY == oldMouseY)) ) { - // Never sleep longer than 100 millis, to get windows with - // background tasks an opportunity to update the display. - timeout = getSleepTime(100); - - // See if there are any definitely events waiting to be - // processed. If so, do not wait -- either there is I/O - // coming in or the primary/secondary threads are still - // working. - synchronized (drainEventQueue) { - if (drainEventQueue.size() > 0) { - timeout = 0; - } - } - synchronized (fillEventQueue) { - if (fillEventQueue.size() > 0) { - timeout = 0; - } - } + // Never sleep longer than 50 millis. We need time for + // windows with background tasks to update the display, and + // still flip buffers reasonably quickly in + // backend.flushPhysical(). + timeout = getSleepTime(50); } if (timeout > 0) { @@ -675,6 +781,9 @@ public class TApplication { // wait until either the backend or the consumer threads have // something to do. try { + if (debugThreads) { + System.err.println("sleep " + timeout + " millis"); + } synchronized (this) { this.wait(timeout); } @@ -685,31 +794,27 @@ public class TApplication { repaint = true; } + // Prevent stepping on the primary or secondary event handler. + stopEventHandlers(); + // Pull any pending I/O events backend.getEvents(fillEventQueue); // 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); + if (fillEventQueue.size() == 0) { + break; } + event = fillEventQueue.remove(0); metaHandleEvent(event); } // Wake a consumer thread if we have any pending events. - synchronized (drainEventQueue) { - if (drainEventQueue.size() > 0) { - wakeEventHandler(); - } + if (drainEventQueue.size() > 0) { + wakeEventHandler(); } - // Prevent stepping on the primary or secondary event handler. - stopEventHandlers(); - // Process timers and call doIdle()'s doIdle(); @@ -803,9 +908,7 @@ public class TApplication { } // Put into the main queue - synchronized (drainEventQueue) { - drainEventQueue.add(event); - } + drainEventQueue.add(event); } /** @@ -843,11 +946,11 @@ public class TApplication { break; } if ((mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) - && (!mouse.getMouse1()) - && (!mouse.getMouse2()) - && (!mouse.getMouse3()) - && (!mouse.getMouseWheelUp()) - && (!mouse.getMouseWheelDown()) + && (!mouse.isMouse1()) + && (!mouse.isMouse2()) + && (!mouse.isMouse3()) + && (!mouse.isMouseWheelUp()) + && (!mouse.isMouseWheelDown()) ) { break; } @@ -871,20 +974,35 @@ public class TApplication { if (event instanceof TKeypressEvent) { TKeypressEvent keypress = (TKeypressEvent) event; - // See if this key matches an accelerator, and if so dispatch the - // menu event. - TKeypress keypressLowercase = keypress.getKey().toLowerCase(); - TMenuItem item = null; - synchronized (accelerators) { - item = accelerators.get(keypressLowercase); + // See if this key matches an accelerator, and is not being + // shortcutted by the active window, and if so dispatch the menu + // event. + boolean windowWillShortcut = false; + for (TWindow window: windows) { + if (window.isActive()) { + if (window.isShortcutKeypress(keypress.getKey())) { + // We do not process this key, it will be passed to + // the window instead. + windowWillShortcut = true; + } + } } - if (item != null) { - if (item.getEnabled()) { - // Let the menu item dispatch - item.dispatch(); - return; + + if (!windowWillShortcut) { + TKeypress keypressLowercase = keypress.getKey().toLowerCase(); + TMenuItem item = null; + synchronized (accelerators) { + item = accelerators.get(keypressLowercase); + } + if (item != null) { + if (item.isEnabled()) { + // Let the menu item dispatch + item.dispatch(); + return; + } } } + // Handle the keypress if (onKeypress(keypress)) { return; @@ -905,7 +1023,7 @@ public class TApplication { // Dispatch events to the active window ------------------------------- for (TWindow window: windows) { - if (window.getActive()) { + if (window.isActive()) { if (event instanceof TMouseEvent) { TMouseEvent mouse = (TMouseEvent) event; // Convert the mouse relative x/y to window coordinates @@ -943,7 +1061,8 @@ public class TApplication { public final void enableSecondaryEventReceiver(final TWidget widget) { assert (secondaryEventReceiver == null); assert (secondaryEventHandler == null); - assert (widget instanceof TMessageBox); + assert ((widget instanceof TMessageBox) + || (widget instanceof TFileOpenBox)); secondaryEventReceiver = widget; secondaryEventHandler = new WidgetEventHandler(this, false); (new Thread(secondaryEventHandler)).start(); @@ -959,7 +1078,7 @@ public class TApplication { // secondary thread locks again. When it gives up, we have the // single lock back. boolean oldLock = unlockHandleEvent(); - assert (oldLock == true); + assert (oldLock); while (secondaryEventReceiver != null) { synchronized (primaryEventHandler) { @@ -1037,6 +1156,7 @@ public class TApplication { synchronized (windows) { int z = window.getZ(); window.setZ(-1); + window.onUnfocus(); Collections.sort(windows); windows.remove(0); TWindow activeWindow = null; @@ -1045,10 +1165,14 @@ public class TApplication { w.setZ(w.getZ() - 1); if (w.getZ() == 0) { w.setActive(true); + w.onFocus(); assert (activeWindow == null); activeWindow = w; } else { - w.setActive(false); + if (w.isActive()) { + w.setActive(false); + w.onUnfocus(); + } } } } @@ -1090,7 +1214,7 @@ public class TApplication { // Swap z/active between active window and the next in the list int activeWindowI = -1; for (int i = 0; i < windows.size(); i++) { - if (windows.get(i).getActive()) { + if (windows.get(i).isActive()) { activeWindowI = i; break; } @@ -1114,8 +1238,10 @@ public class TApplication { } windows.get(activeWindowI).setActive(false); windows.get(activeWindowI).setZ(windows.get(nextWindowI).getZ()); + windows.get(activeWindowI).onUnfocus(); windows.get(nextWindowI).setZ(0); windows.get(nextWindowI).setActive(true); + windows.get(nextWindowI).onFocus(); } // synchronized (windows) @@ -1133,12 +1259,16 @@ public class TApplication { assert (window.isModal()); } for (TWindow w: windows) { - w.setActive(false); + if (w.isActive()) { + w.setActive(false); + w.onUnfocus(); + } w.setZ(w.getZ() + 1); } windows.add(window); - window.setActive(true); window.setZ(0); + window.setActive(true); + window.onFocus(); } } @@ -1199,7 +1329,7 @@ public class TApplication { // See if they hit the menu bar if ((mouse.getType() == TMouseEvent.Type.MOUSE_DOWN) - && (mouse.getMouse1()) + && (mouse.isMouse1()) && (!modalWindowActive()) && (mouse.getAbsoluteY() == 0) ) { @@ -1226,7 +1356,7 @@ public class TApplication { // See if they hit the menu bar if ((mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) - && (mouse.getMouse1()) + && (mouse.isMouse1()) && (activeMenu != null) && (mouse.getAbsoluteY() == 0) ) { @@ -1280,12 +1410,14 @@ public class TApplication { } // We will be switching to another window - assert (windows.get(0).getActive()); - assert (!window.getActive()); + assert (windows.get(0).isActive()); + assert (!window.isActive()); + windows.get(0).onUnfocus(); windows.get(0).setActive(false); windows.get(0).setZ(window.getZ()); window.setZ(0); window.setActive(true); + window.onFocus(); return; } } @@ -1391,6 +1523,15 @@ public class TApplication { return false; } + /** + * Display the about dialog. + */ + protected void showAboutDialog() { + messageBox("About", "Jexer Version " + + this.getClass().getPackage().getImplementationVersion(), + TMessageBox.Type.OK); + } + /** * Method that TApplication subclasses can override to handle menu * events. @@ -1426,6 +1567,10 @@ public class TApplication { closeAllWindows(); return true; } + if (menu.getId() == TMenu.MID_ABOUT) { + showAboutDialog(); + return true; + } return false; } @@ -1439,9 +1584,9 @@ public class TApplication { // Default: only menu shortcuts // Process Alt-F, Alt-E, etc. menu shortcut keys - if (!keypress.getKey().getIsKey() - && keypress.getKey().getAlt() - && !keypress.getKey().getCtrl() + if (!keypress.getKey().isFnKey() + && keypress.getKey().isAlt() + && !keypress.getKey().isCtrl() && (activeMenu == null) ) { @@ -1449,7 +1594,7 @@ public class TApplication { for (TMenu menu: menus) { if (Character.toLowerCase(menu.getMnemonic().getShortcut()) - == Character.toLowerCase(keypress.getKey().getCh()) + == Character.toLowerCase(keypress.getKey().getChar()) ) { activeMenu = menu; menu.setActive(true); @@ -1462,17 +1607,76 @@ public class TApplication { } /** - * Add a keyboard accelerator to the global hash. + * 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 item menu item this accelerator relates to - * @param keypress keypress that will dispatch a TMenuEvent + * @param id the menu item ID */ - public final void addAccelerator(final TMenuItem item, - final TKeypress keypress) { + public final void enableMenuItem(final int id) { + for (TMenuItem item: menuItems) { + if (item.getId() == id) { + item.setEnabled(true); + } + } + } - synchronized (accelerators) { - assert (accelerators.get(keypress) == null); - accelerators.put(keypress, item); + /** + * 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); + } } } @@ -1492,7 +1696,7 @@ public class TApplication { * * @param event new event to add to the queue */ - public final void addMenuEvent(final TInputEvent event) { + public final void postMenuEvent(final TInputEvent event) { synchronized (fillEventQueue) { fillEventQueue.add(event); } @@ -1570,6 +1774,24 @@ public class TApplication { return windowMenu; } + /** + * Convenience function to add a default "Help" menu. + * + * @return the new menu + */ + public final TMenu addHelpMenu() { + TMenu helpMenu = addMenu("&Help"); + helpMenu.addDefaultItem(TMenu.MID_HELP_CONTENTS); + helpMenu.addDefaultItem(TMenu.MID_HELP_INDEX); + helpMenu.addDefaultItem(TMenu.MID_HELP_SEARCH); + helpMenu.addDefaultItem(TMenu.MID_HELP_PREVIOUS); + helpMenu.addDefaultItem(TMenu.MID_HELP_HELP); + helpMenu.addDefaultItem(TMenu.MID_HELP_ACTIVE_FILE); + helpMenu.addSeparator(); + helpMenu.addDefaultItem(TMenu.MID_ABOUT); + return helpMenu; + } + /** * Close all open windows. */ @@ -1578,11 +1800,8 @@ public class TApplication { if (activeMenu != null) { return; } - - synchronized (windows) { - for (TWindow window: windows) { - closeWindow(window); - } + while (windows.size() > 0) { + closeWindow(windows.get(0)); } } @@ -1784,4 +2003,32 @@ public class TApplication { return new TTerminalWindow(this, x, y, flags); } + /** + * Convenience function to spawn an file open box. + * + * @param path path of selected file + * @return the result of the new file open box + * @throws IOException if java.io operation throws + */ + public final String fileOpenBox(final String path) throws IOException { + + TFileOpenBox box = new TFileOpenBox(this, path, TFileOpenBox.Type.OPEN); + return box.getFilename(); + } + + /** + * Convenience function to spawn an file open box. + * + * @param path path of selected file + * @param type one of the Type constants + * @return the result of the new file open box + * @throws IOException if java.io operation throws + */ + public final String fileOpenBox(final String path, + final TFileOpenBox.Type type) throws IOException { + + TFileOpenBox box = new TFileOpenBox(this, path, type); + return box.getFilename(); + } + }