PMD code sweep, #6 don't add MyWindow twice to MyApplication
authorKevin Lamonte <kevin.lamonte@gmail.com>
Sun, 3 Dec 2017 19:55:39 +0000 (14:55 -0500)
committerKevin Lamonte <kevin.lamonte@gmail.com>
Sun, 3 Dec 2017 19:55:39 +0000 (14:55 -0500)
65 files changed:
README.md
build.xml
docs/worklog.md
resources/jexer_logo_128.png [new file with mode: 0644]
src/jexer/TApplication.java
src/jexer/TButton.java
src/jexer/TCheckbox.java
src/jexer/TCommand.java
src/jexer/TEditColorThemeWindow.java
src/jexer/TField.java
src/jexer/TFileOpenBox.java
src/jexer/THScroller.java
src/jexer/TKeypress.java
src/jexer/TLabel.java
src/jexer/TList.java
src/jexer/TProgressBar.java
src/jexer/TRadioButton.java
src/jexer/TStatusBar.java
src/jexer/TText.java
src/jexer/TTimer.java
src/jexer/TVScroller.java
src/jexer/TWidget.java
src/jexer/TWindow.java
src/jexer/backend/Backend.java
src/jexer/backend/ECMA48Backend.java
src/jexer/backend/ECMA48Terminal.java
src/jexer/backend/GenericBackend.java
src/jexer/backend/LogicalScreen.java
src/jexer/backend/MultiBackend.java
src/jexer/backend/MultiScreen.java
src/jexer/backend/SwingBackend.java
src/jexer/backend/SwingComponent.java
src/jexer/backend/SwingSessionInfo.java
src/jexer/backend/SwingTerminal.java
src/jexer/backend/TSessionInfo.java
src/jexer/backend/TTYSessionInfo.java
src/jexer/backend/TWindowBackend.java
src/jexer/bits/Cell.java
src/jexer/bits/CellAttributes.java
src/jexer/bits/Color.java
src/jexer/bits/ColorTheme.java
src/jexer/bits/GraphicsChars.java
src/jexer/bits/MnemonicString.java
src/jexer/bits/StringUtils.java [moved from src/jexer/bits/StringJustifier.java with 85% similarity]
src/jexer/demos/DemoTreeViewWindow.java
src/jexer/event/TMouseEvent.java
src/jexer/event/TResizeEvent.java
src/jexer/menu/TMenu.java
src/jexer/menu/TMenu.properties
src/jexer/menu/TMenuItem.java
src/jexer/menu/TMenuSeparator.java
src/jexer/menu/TSubMenu.java
src/jexer/net/TelnetInputStream.java
src/jexer/net/TelnetOutputStream.java
src/jexer/net/TelnetServerSocket.java
src/jexer/net/TelnetSocket.java
src/jexer/tterminal/DECCharacterSets.java
src/jexer/tterminal/DisplayLine.java
src/jexer/tterminal/ECMA48.java
src/jexer/ttree/TDirectoryTreeItem.java [moved from src/jexer/TDirectoryTreeItem.java with 80% similarity]
src/jexer/ttree/TTreeItem.java [moved from src/jexer/TTreeItem.java with 76% similarity]
src/jexer/ttree/TTreeView.java [new file with mode: 0644]
src/jexer/ttree/TTreeViewWidget.java [moved from src/jexer/TTreeView.java with 58% similarity]
src/jexer/ttree/TTreeViewWindow.java [new file with mode: 0644]
src/jexer/ttree/package-info.java [new file with mode: 0644]

index 1e2b7e9d4f2f1e7abbbf7efe4c9f64a5ceb6dd90..dc976599bf6cffeb4d37a2d48a3d0bc0932d5d77 100644 (file)
--- a/README.md
+++ b/README.md
@@ -73,8 +73,9 @@ class MyApplication extends TApplication {
         addFileMenu();
         addWindowMenu();
 
         addFileMenu();
         addWindowMenu();
 
-        // Add a custom window, see below for its code.
-        addWindow(new MyWindow(this));
+        // Add a custom window, see below for its code.  The TWindow
+        // constructor will add it to this application.
+        new MyWindow(this);
     }
 
     public static void main(String [] args) {
     }
 
     public static void main(String [] args) {
index f21f321d703367c558c6472ef0533473709da021..4e7abb8a8d0662376fde4e2187879ec3b5d3150b 100644 (file)
--- a/build.xml
+++ b/build.xml
 
 <project name="jexer" basedir="." default="jar">
 
 
 <project name="jexer" basedir="." default="jar">
 
-  <!-- I test with gcj because it is Java 1.5 and has better warnings
-       for unused imports. -->
-  <property name="build.compiler" value="gcj"/>
-
   <property name="version"       value="0.0.6"/>
   <property name="src.dir"       value="src"/>
   <property name="resources.dir" value="resources"/>
   <property name="version"       value="0.0.6"/>
   <property name="src.dir"       value="src"/>
   <property name="resources.dir" value="resources"/>
@@ -94,7 +90,6 @@
         version="true"
         use="true"
         access="protected"
         version="true"
         use="true"
         access="protected"
-        failonwarning="true"
         windowtitle="Jexer - Java Text User Interface - API docs">
 
       <fileset dir="${src.dir}" defaultexcludes="yes">
         windowtitle="Jexer - Java Text User Interface - API docs">
 
       <fileset dir="${src.dir}" defaultexcludes="yes">
index f4a3a36d535b530f32c8210b43f3c4b00462d7c3..05d4f15cb79b8948cd055f7835f3c12db0cfcc79 100644 (file)
@@ -1,6 +1,17 @@
 Jexer Work Log
 ==============
 
 Jexer Work Log
 ==============
 
+October 17, 2017
+
+I finally gave up the ghost on using gcj as the default compiler due
+to its awesome unused imports messages, and learned how to get PMD to
+do that job.  Which promptly created 1000+ warning messages related to
+class item order (variables, constructors, methods), nested ifs,
+useless checks, and so on.  So now we go on a code sweep to fix those,
+and along the way set a new class template.  Since this is so large
+and invasive, I will bite the bullet now and get it done before the
+next release which will get it out on Maven finally.
+
 August 16, 2017
 
 Holy balls this has gotten so much faster!  It is FINALLY visibly
 August 16, 2017
 
 Holy balls this has gotten so much faster!  It is FINALLY visibly
diff --git a/resources/jexer_logo_128.png b/resources/jexer_logo_128.png
new file mode 100644 (file)
index 0000000..5c3a813
Binary files /dev/null and b/resources/jexer_logo_128.png differ
index e61cea28d631afba3c84964c7a2a13803c597aca..6dd503f547a141d1a05f0511652c14e038a25f42 100644 (file)
@@ -77,7 +77,7 @@ public class TApplication implements Runnable {
     private static final ResourceBundle i18n = ResourceBundle.getBundle(TApplication.class.getName());
 
     // ------------------------------------------------------------------------
     private static final ResourceBundle i18n = ResourceBundle.getBundle(TApplication.class.getName());
 
     // ------------------------------------------------------------------------
-    // Public constants -------------------------------------------------------
+    // Constants --------------------------------------------------------------
     // ------------------------------------------------------------------------
 
     /**
     // ------------------------------------------------------------------------
 
     /**
@@ -117,9 +117,155 @@ public class TApplication implements Runnable {
     }
 
     // ------------------------------------------------------------------------
     }
 
     // ------------------------------------------------------------------------
-    // Primary/secondary event handlers ---------------------------------------
+    // Variables --------------------------------------------------------------
     // ------------------------------------------------------------------------
 
     // ------------------------------------------------------------------------
 
+    /**
+     * The primary event handler thread.
+     */
+    private volatile WidgetEventHandler primaryEventHandler;
+
+    /**
+     * The secondary event handler thread.
+     */
+    private volatile WidgetEventHandler secondaryEventHandler;
+
+    /**
+     * The widget receiving events from the secondary event handler thread.
+     */
+    private volatile TWidget secondaryEventReceiver;
+
+    /**
+     * Access to the physical screen, keyboard, and mouse.
+     */
+    private Backend backend;
+
+    /**
+     * Actual mouse coordinate X.
+     */
+    private int mouseX;
+
+    /**
+     * Actual mouse coordinate Y.
+     */
+    private int mouseY;
+
+    /**
+     * Old version of mouse coordinate X.
+     */
+    private int oldMouseX;
+
+    /**
+     * Old version mouse coordinate Y.
+     */
+    private int oldMouseY;
+
+    /**
+     * The last mouse up click time, used to determine if this is a mouse
+     * double-click.
+     */
+    private long lastMouseUpTime;
+
+    /**
+     * The amount of millis between mouse up events to assume a double-click.
+     */
+    private long doubleClickTime = 250;
+
+    /**
+     * Event queue that is filled by run().
+     */
+    private List<TInputEvent> fillEventQueue;
+
+    /**
+     * Event queue that will be drained by either primary or secondary
+     * Thread.
+     */
+    private List<TInputEvent> drainEventQueue;
+
+    /**
+     * Top-level menus in this application.
+     */
+    private List<TMenu> menus;
+
+    /**
+     * Stack of activated sub-menus in this application.
+     */
+    private List<TMenu> subMenus;
+
+    /**
+     * The currently active menu.
+     */
+    private TMenu activeMenu = null;
+
+    /**
+     * Active keyboard accelerators.
+     */
+    private Map<TKeypress, TMenuItem> accelerators;
+
+    /**
+     * All menu items.
+     */
+    private List<TMenuItem> menuItems;
+
+    /**
+     * Windows and widgets pull colors from this ColorTheme.
+     */
+    private ColorTheme theme;
+
+    /**
+     * The top-level windows (but not menus).
+     */
+    private List<TWindow> windows;
+
+    /**
+     * The currently acive window.
+     */
+    private TWindow activeWindow = null;
+
+    /**
+     * Timers that are being ticked.
+     */
+    private List<TTimer> timers;
+
+    /**
+     * When true, the application has been started.
+     */
+    private volatile boolean started = false;
+
+    /**
+     * When true, exit the application.
+     */
+    private volatile boolean quit = false;
+
+    /**
+     * When true, repaint the entire screen.
+     */
+    private volatile boolean repaint = true;
+
+    /**
+     * Y coordinate of the top edge of the desktop.  For now this is a
+     * constant.  Someday it would be nice to have a multi-line menu or
+     * toolbars.
+     */
+    private static final int desktopTop = 1;
+
+    /**
+     * Y coordinate of the bottom edge of the desktop.
+     */
+    private int desktopBottom;
+
+    /**
+     * An optional TDesktop background window that is drawn underneath
+     * everything else.
+     */
+    private TDesktop desktop;
+
+    /**
+     * If true, focus follows mouse: windows automatically raised if the
+     * mouse passes over them.
+     */
+    private boolean focusFollowsMouse = false;
+
     /**
      * WidgetEventHandler is the main event consumer loop.  There are at most
      * two such threads in existence: the primary for normal case and a
     /**
      * WidgetEventHandler is the main event consumer loop.  There are at most
      * two such threads in existence: the primary for normal case and a
@@ -264,1163 +410,1153 @@ public class TApplication implements Runnable {
         }
     }
 
         }
     }
 
-    /**
-     * The primary event handler thread.
-     */
-    private volatile WidgetEventHandler primaryEventHandler;
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
 
     /**
-     * The secondary event handler thread.
+     * Public constructor.
+     *
+     * @param backendType BackendType.XTERM, BackendType.ECMA48 or
+     * BackendType.SWING
+     * @param windowWidth the number of text columns to start with
+     * @param windowHeight the number of text rows to start with
+     * @param fontSize the size in points
+     * @throws UnsupportedEncodingException if an exception is thrown when
+     * creating the InputStreamReader
      */
      */
-    private volatile WidgetEventHandler secondaryEventHandler;
+    public TApplication(final BackendType backendType, final int windowWidth,
+        final int windowHeight, final int fontSize)
+        throws UnsupportedEncodingException {
 
 
-    /**
-     * The widget receiving events from the secondary event handler thread.
-     */
-    private volatile TWidget secondaryEventReceiver;
+        switch (backendType) {
+        case SWING:
+            backend = new SwingBackend(this, windowWidth, windowHeight,
+                fontSize);
+            break;
+        case XTERM:
+            // Fall through...
+        case ECMA48:
+            backend = new ECMA48Backend(this, null, null, windowWidth,
+                windowHeight, fontSize);
+            break;
+        default:
+            throw new IllegalArgumentException("Invalid backend type: "
+                + backendType);
+        }
+        TApplicationImpl();
+    }
 
     /**
 
     /**
-     * Wake the sleeping active event handler.
+     * Public constructor.
+     *
+     * @param backendType BackendType.XTERM, BackendType.ECMA48 or
+     * BackendType.SWING
+     * @throws UnsupportedEncodingException if an exception is thrown when
+     * creating the InputStreamReader
      */
      */
-    private void wakeEventHandler() {
-        if (!started) {
-            return;
-        }
+    public TApplication(final BackendType backendType)
+        throws UnsupportedEncodingException {
 
 
-        if (secondaryEventHandler != null) {
-            synchronized (secondaryEventHandler) {
-                secondaryEventHandler.notify();
-            }
-        } else {
-            assert (primaryEventHandler != null);
-            synchronized (primaryEventHandler) {
-                primaryEventHandler.notify();
-            }
+        switch (backendType) {
+        case SWING:
+            // The default SwingBackend is 80x25, 20 pt font.  If you want to
+            // change that, you can pass the extra arguments to the
+            // SwingBackend constructor here.  For example, if you wanted
+            // 90x30, 16 pt font:
+            //
+            // backend = new SwingBackend(this, 90, 30, 16);
+            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();
     }
 
     }
 
-    // ------------------------------------------------------------------------
-    // TApplication attributes ------------------------------------------------
-    // ------------------------------------------------------------------------
-
     /**
     /**
-     * Access to the physical screen, keyboard, and mouse.
-     */
-    private Backend backend;
-
-    /**
-     * Get the Backend.
+     * Public constructor.  The backend type will be BackendType.ECMA48.
      *
      *
-     * @return the Backend
+     * @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
+     * mode.  input is always converted to a Reader with UTF-8 encoding.
+     * @param output an OutputStream connected to the remote user, or null
+     * for System.out.  output is always converted to a Writer with UTF-8
+     * encoding.
+     * @throws UnsupportedEncodingException if an exception is thrown when
+     * creating the InputStreamReader
      */
      */
-    public final Backend getBackend() {
-        return backend;
+    public TApplication(final InputStream input,
+        final OutputStream output) throws UnsupportedEncodingException {
+
+        backend = new ECMA48Backend(this, input, output);
+        TApplicationImpl();
     }
 
     /**
     }
 
     /**
-     * Get the Screen.
+     * Public constructor.  The backend type will be BackendType.ECMA48.
      *
      *
-     * @return the Screen
+     * @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 final Screen getScreen() {
-        if (backend instanceof TWindowBackend) {
-            // We are being rendered to a TWindow.  We can't use its
-            // getScreen() method because that is how it is rendering to a
-            // hardware backend somewhere.  Instead use its getOtherScreen()
-            // method.
-            return ((TWindowBackend) backend).getOtherScreen();
-        } else {
-            return backend.getScreen();
-        }
+    public TApplication(final InputStream input, final Reader reader,
+        final PrintWriter writer, final boolean setRawMode) {
+
+        backend = new ECMA48Backend(this, input, reader, writer, setRawMode);
+        TApplicationImpl();
     }
 
     /**
     }
 
     /**
-     * Actual mouse coordinate X.
+     * 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.
      */
      */
-    private int mouseX;
+    public TApplication(final InputStream input, final Reader reader,
+        final PrintWriter writer) {
 
 
-    /**
-     * Actual mouse coordinate Y.
-     */
-    private int mouseY;
+        this(input, reader, writer, false);
+    }
 
     /**
 
     /**
-     * Old version of mouse coordinate X.
+     * Public constructor.  This hook enables use with new non-Jexer
+     * backends.
+     *
+     * @param backend a Backend that is already ready to go.
      */
      */
-    private int oldMouseX;
+    public TApplication(final Backend backend) {
+        this.backend = backend;
+        backend.setListener(this);
+        TApplicationImpl();
+    }
 
     /**
 
     /**
-     * Old version mouse coordinate Y.
+     * Finish construction once the backend is set.
      */
      */
-    private int oldMouseY;
+    private void TApplicationImpl() {
+        theme           = new ColorTheme();
+        desktopBottom   = getScreen().getHeight() - 1;
+        fillEventQueue  = new ArrayList<TInputEvent>();
+        drainEventQueue = new ArrayList<TInputEvent>();
+        windows         = new LinkedList<TWindow>();
+        menus           = new LinkedList<TMenu>();
+        subMenus        = new LinkedList<TMenu>();
+        timers          = new LinkedList<TTimer>();
+        accelerators    = new HashMap<TKeypress, TMenuItem>();
+        menuItems       = new ArrayList<TMenuItem>();
+        desktop         = new TDesktop(this);
 
 
-    /**
-     * The last mouse up click time, used to determine if this is a mouse
-     * double-click.
-     */
-    private long lastMouseUpTime;
+        // Special case: the Swing backend needs to have a timer to drive its
+        // blink state.
+        if ((backend instanceof SwingBackend)
+            || (backend instanceof MultiBackend)
+        ) {
+            // Default to 500 millis, unless a SwingBackend has its own
+            // value.
+            long millis = 500;
+            if (backend instanceof SwingBackend) {
+                millis = ((SwingBackend) backend).getBlinkMillis();
+            }
+            if (millis > 0) {
+                addTimer(millis, true,
+                    new TAction() {
+                        public void DO() {
+                            TApplication.this.doRepaint();
+                        }
+                    }
+                );
+            }
+        }
+    }
 
 
-    /**
-     * The amount of millis between mouse up events to assume a double-click.
-     */
-    private long doubleClickTime = 250;
+    // ------------------------------------------------------------------------
+    // Runnable ---------------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
 
     /**
-     * Event queue that is filled by run().
+     * Run this application until it exits.
      */
      */
-    private List<TInputEvent> fillEventQueue;
+    public void run() {
+        // Start the main consumer thread
+        primaryEventHandler = new WidgetEventHandler(this, true);
+        (new Thread(primaryEventHandler)).start();
 
 
-    /**
-     * Event queue that will be drained by either primary or secondary
-     * Thread.
-     */
-    private List<TInputEvent> drainEventQueue;
+        started = true;
 
 
-    /**
-     * Top-level menus in this application.
-     */
-    private List<TMenu> menus;
+        while (!quit) {
+            synchronized (this) {
+                boolean doWait = false;
 
 
-    /**
-     * Stack of activated sub-menus in this application.
-     */
-    private List<TMenu> subMenus;
+                if (!backend.hasEvents()) {
+                    synchronized (fillEventQueue) {
+                        if (fillEventQueue.size() == 0) {
+                            doWait = true;
+                        }
+                    }
+                }
 
 
-    /**
-     * The currently active menu.
-     */
-    private TMenu activeMenu = null;
+                if (doWait) {
+                    // No I/O to dispatch, so wait until the backend
+                    // provides new I/O.
+                    try {
+                        if (debugThreads) {
+                            System.err.println(System.currentTimeMillis() +
+                                " MAIN sleep");
+                        }
 
 
-    /**
-     * Active keyboard accelerators.
-     */
-    private Map<TKeypress, TMenuItem> accelerators;
+                        this.wait();
 
 
-    /**
-     * All menu items.
-     */
-    private List<TMenuItem> menuItems;
+                        if (debugThreads) {
+                            System.err.println(System.currentTimeMillis() +
+                                " MAIN AWAKE");
+                        }
+                    } catch (InterruptedException e) {
+                        // I'm awake and don't care why, let's see what's
+                        // going on out there.
+                    }
+                }
 
 
-    /**
-     * Windows and widgets pull colors from this ColorTheme.
-     */
-    private ColorTheme theme;
+            } // synchronized (this)
 
 
-    /**
-     * Get the color theme.
-     *
-     * @return the theme
-     */
-    public final ColorTheme getTheme() {
-        return theme;
-    }
+            synchronized (fillEventQueue) {
+                // Pull any pending I/O events
+                backend.getEvents(fillEventQueue);
 
 
-    /**
-     * The top-level windows (but not menus).
-     */
-    private List<TWindow> windows;
+                // Dispatch each event to the appropriate handler, one at a
+                // time.
+                for (;;) {
+                    TInputEvent event = null;
+                    if (fillEventQueue.size() == 0) {
+                        break;
+                    }
+                    event = fillEventQueue.remove(0);
+                    metaHandleEvent(event);
+                }
+            }
 
 
-    /**
-     * The currently acive window.
-     */
-    private TWindow activeWindow = null;
+            // Wake a consumer thread if we have any pending events.
+            if (drainEventQueue.size() > 0) {
+                wakeEventHandler();
+            }
 
 
-    /**
-     * Timers that are being ticked.
-     */
-    private List<TTimer> timers;
+        } // while (!quit)
 
 
-    /**
-     * When true, the application has been started.
-     */
-    private volatile boolean started = false;
+        // Shutdown the event consumer threads
+        if (secondaryEventHandler != null) {
+            synchronized (secondaryEventHandler) {
+                secondaryEventHandler.notify();
+            }
+        }
+        if (primaryEventHandler != null) {
+            synchronized (primaryEventHandler) {
+                primaryEventHandler.notify();
+            }
+        }
 
 
-    /**
-     * When true, exit the application.
-     */
-    private volatile boolean quit = false;
+        // Shutdown the user I/O thread(s)
+        backend.shutdown();
 
 
-    /**
-     * When true, repaint the entire screen.
-     */
-    private volatile boolean repaint = true;
+        // Close all the windows.  This gives them an opportunity to release
+        // resources.
+        closeAllWindows();
 
 
-    /**
-     * Repaint the screen on the next update.
-     */
-    public void doRepaint() {
-        repaint = true;
-        wakeEventHandler();
     }
 
     }
 
-    /**
-     * Y coordinate of the top edge of the desktop.  For now this is a
-     * constant.  Someday it would be nice to have a multi-line menu or
-     * toolbars.
-     */
-    private static final int desktopTop = 1;
+    // ------------------------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
 
     /**
-     * Get Y coordinate of the top edge of the desktop.
+     * Method that TApplication subclasses can override to handle menu or
+     * posted command events.
      *
      *
-     * @return Y coordinate of the top edge of the desktop
+     * @param command command event
+     * @return if true, this event was consumed
      */
      */
-    public final int getDesktopTop() {
-        return desktopTop;
-    }
-
-    /**
-     * Y coordinate of the bottom edge of the desktop.
-     */
-    private int desktopBottom;
+    protected boolean onCommand(final TCommandEvent command) {
+        // Default: handle cmExit
+        if (command.equals(cmExit)) {
+            if (messageBox(i18n.getString("exitDialogTitle"),
+                    i18n.getString("exitDialogText"),
+                    TMessageBox.Type.YESNO).getResult() == TMessageBox.Result.YES) {
+                exit();
+            }
+            return true;
+        }
 
 
-    /**
-     * Get Y coordinate of the bottom edge of the desktop.
-     *
-     * @return Y coordinate of the bottom edge of the desktop
-     */
-    public final int getDesktopBottom() {
-        return desktopBottom;
-    }
+        if (command.equals(cmShell)) {
+            openTerminal(0, 0, TWindow.RESIZABLE);
+            return true;
+        }
 
 
-    /**
-     * An optional TDesktop background window that is drawn underneath
-     * everything else.
-     */
-    private TDesktop desktop;
+        if (command.equals(cmTile)) {
+            tileWindows();
+            return true;
+        }
+        if (command.equals(cmCascade)) {
+            cascadeWindows();
+            return true;
+        }
+        if (command.equals(cmCloseAll)) {
+            closeAllWindows();
+            return true;
+        }
 
 
-    /**
-     * Set the TDesktop instance.
-     *
-     * @param desktop a TDesktop instance, or null to remove the one that is
-     * set
-     */
-    public final void setDesktop(final TDesktop desktop) {
-        if (this.desktop != null) {
-            this.desktop.onClose();
+        if (command.equals(cmMenu)) {
+            if (!modalWindowActive() && (activeMenu == null)) {
+                if (menus.size() > 0) {
+                    menus.get(0).setActive(true);
+                    activeMenu = menus.get(0);
+                    return true;
+                }
+            }
         }
         }
-        this.desktop = desktop;
-    }
 
 
-    /**
-     * Get the TDesktop instance.
-     *
-     * @return the desktop, or null if it is not set
-     */
-    public final TDesktop getDesktop() {
-        return desktop;
+        return false;
     }
 
     /**
     }
 
     /**
-     * Get the current active window.
+     * Method that TApplication subclasses can override to handle menu
+     * events.
      *
      *
-     * @return the active window, or null if it is not set
+     * @param menu menu event
+     * @return if true, this event was consumed
      */
      */
-    public final TWindow getActiveWindow() {
-        return activeWindow;
-    }
+    protected boolean onMenu(final TMenuEvent menu) {
 
 
-    /**
-     * Get a (shallow) copy of the window list.
-     *
-     * @return a copy of the list of windows for this application
-     */
-    public final List<TWindow> getAllWindows() {
-        List<TWindow> result = new LinkedList<TWindow>();
-        result.addAll(windows);
-        return result;
-    }
+        // Default: handle MID_EXIT
+        if (menu.getId() == TMenu.MID_EXIT) {
+            if (messageBox(i18n.getString("exitDialogTitle"),
+                    i18n.getString("exitDialogText"),
+                    TMessageBox.Type.YESNO).getResult() == TMessageBox.Result.YES) {
+                exit();
+            }
+            return true;
+        }
 
 
-    /**
-     * If true, focus follows mouse: windows automatically raised if the
-     * mouse passes over them.
-     */
-    private boolean focusFollowsMouse = false;
+        if (menu.getId() == TMenu.MID_SHELL) {
+            openTerminal(0, 0, TWindow.RESIZABLE);
+            return true;
+        }
 
 
-    /**
-     * Get focusFollowsMouse flag.
-     *
-     * @return true if focus follows mouse: windows automatically raised if
-     * the mouse passes over them
-     */
-    public boolean getFocusFollowsMouse() {
-        return focusFollowsMouse;
+        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;
+        }
+        if (menu.getId() == TMenu.MID_ABOUT) {
+            showAboutDialog();
+            return true;
+        }
+        if (menu.getId() == TMenu.MID_REPAINT) {
+            doRepaint();
+            return true;
+        }
+        return false;
     }
 
     /**
     }
 
     /**
-     * Set focusFollowsMouse flag.
+     * Method that TApplication subclasses can override to handle keystrokes.
      *
      *
-     * @param focusFollowsMouse if true, focus follows mouse: windows
-     * automatically raised if the mouse passes over them
+     * @param keypress keystroke event
+     * @return if true, this event was consumed
      */
      */
-    public void setFocusFollowsMouse(final boolean focusFollowsMouse) {
-        this.focusFollowsMouse = focusFollowsMouse;
-    }
+    protected boolean onKeypress(final TKeypressEvent keypress) {
+        // Default: only menu shortcuts
 
 
-    // ------------------------------------------------------------------------
-    // General behavior -------------------------------------------------------
-    // ------------------------------------------------------------------------
+        // Process Alt-F, Alt-E, etc. menu shortcut keys
+        if (!keypress.getKey().isFnKey()
+            && keypress.getKey().isAlt()
+            && !keypress.getKey().isCtrl()
+            && (activeMenu == null)
+            && !modalWindowActive()
+        ) {
 
 
-    /**
-     * Display the about dialog.
-     */
-    protected void showAboutDialog() {
-        messageBox(i18n.getString("aboutDialogTitle"),
-            MessageFormat.format(i18n.getString("aboutDialogText"),
-                this.getClass().getPackage().getImplementationVersion()),
-            TMessageBox.Type.OK);
-    }
+            assert (subMenus.size() == 0);
 
 
-    // ------------------------------------------------------------------------
-    // Constructors -----------------------------------------------------------
-    // ------------------------------------------------------------------------
+            for (TMenu menu: menus) {
+                if (Character.toLowerCase(menu.getMnemonic().getShortcut())
+                    == Character.toLowerCase(keypress.getKey().getChar())
+                ) {
+                    activeMenu = menu;
+                    menu.setActive(true);
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
 
     /**
 
     /**
-     * Public constructor.
-     *
-     * @param backendType BackendType.XTERM, BackendType.ECMA48 or
-     * BackendType.SWING
-     * @param windowWidth the number of text columns to start with
-     * @param windowHeight the number of text rows to start with
-     * @param fontSize the size in points
-     * @throws UnsupportedEncodingException if an exception is thrown when
-     * creating the InputStreamReader
+     * Process background events, and update the screen.
      */
      */
-    public TApplication(final BackendType backendType, final int windowWidth,
-        final int windowHeight, final int fontSize)
-        throws UnsupportedEncodingException {
+    private void finishEventProcessing() {
+        if (debugThreads) {
+            System.err.printf(System.currentTimeMillis() + " " +
+                Thread.currentThread() + " finishEventProcessing()\n");
+        }
 
 
-        switch (backendType) {
-        case SWING:
-            backend = new SwingBackend(this, windowWidth, windowHeight,
-                fontSize);
-            break;
-        case XTERM:
-            // Fall through...
-        case ECMA48:
-            backend = new ECMA48Backend(this, null, null, windowWidth,
-                windowHeight, fontSize);
-            break;
-        default:
-            throw new IllegalArgumentException("Invalid backend type: "
-                + backendType);
+        // Process timers and call doIdle()'s
+        doIdle();
+
+        // Update the screen
+        synchronized (getScreen()) {
+            drawAll();
+        }
+
+        if (debugThreads) {
+            System.err.printf(System.currentTimeMillis() + " " +
+                Thread.currentThread() + " finishEventProcessing() END\n");
         }
         }
-        TApplicationImpl();
     }
 
     /**
     }
 
     /**
-     * Public constructor.
+     * Peek at certain application-level events, add to eventQueue, and wake
+     * up the consuming Thread.
      *
      *
-     * @param backendType BackendType.XTERM, BackendType.ECMA48 or
-     * BackendType.SWING
-     * @throws UnsupportedEncodingException if an exception is thrown when
-     * creating the InputStreamReader
+     * @param event the input event to consume
      */
      */
-    public TApplication(final BackendType backendType)
-        throws UnsupportedEncodingException {
+    private void metaHandleEvent(final TInputEvent event) {
 
 
-        switch (backendType) {
-        case SWING:
-            // The default SwingBackend is 80x25, 20 pt font.  If you want to
-            // change that, you can pass the extra arguments to the
-            // SwingBackend constructor here.  For example, if you wanted
-            // 90x30, 16 pt font:
-            //
-            // backend = new SwingBackend(this, 90, 30, 16);
-            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);
+        if (debugEvents) {
+            System.err.printf(String.format("metaHandleEvents event: %s\n",
+                    event)); System.err.flush();
         }
         }
-        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
-     * mode.  input is always converted to a Reader with UTF-8 encoding.
-     * @param output an OutputStream connected to the remote user, or null
-     * for System.out.  output is always converted to a Writer with UTF-8
-     * encoding.
-     * @throws UnsupportedEncodingException if an exception is thrown when
-     * creating the InputStreamReader
-     */
-    public TApplication(final InputStream input,
-        final OutputStream output) throws UnsupportedEncodingException {
-
-        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();
-    }
-
-    /**
-     * 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;
-        backend.setListener(this);
-        TApplicationImpl();
-    }
+        if (quit) {
+            // Do no more processing if the application is already trying
+            // to exit.
+            return;
+        }
 
 
-    /**
-     * Finish construction once the backend is set.
-     */
-    private void TApplicationImpl() {
-        theme           = new ColorTheme();
-        desktopBottom   = getScreen().getHeight() - 1;
-        fillEventQueue  = new ArrayList<TInputEvent>();
-        drainEventQueue = new ArrayList<TInputEvent>();
-        windows         = new LinkedList<TWindow>();
-        menus           = new LinkedList<TMenu>();
-        subMenus        = new LinkedList<TMenu>();
-        timers          = new LinkedList<TTimer>();
-        accelerators    = new HashMap<TKeypress, TMenuItem>();
-        menuItems       = new ArrayList<TMenuItem>();
-        desktop         = new TDesktop(this);
+        // Special application-wide events -------------------------------
 
 
-        // Special case: the Swing backend needs to have a timer to drive its
-        // blink state.
-        if ((backend instanceof SwingBackend)
-            || (backend instanceof MultiBackend)
-        ) {
-            // Default to 500 millis, unless a SwingBackend has its own
-            // value.
-            long millis = 500;
-            if (backend instanceof SwingBackend) {
-                millis = ((SwingBackend) backend).getBlinkMillis();
-            }
-            if (millis > 0) {
-                addTimer(millis, true,
-                    new TAction() {
-                        public void DO() {
-                            TApplication.this.doRepaint();
-                        }
-                    }
-                );
+        // Abort everything
+        if (event instanceof TCommandEvent) {
+            TCommandEvent command = (TCommandEvent) event;
+            if (command.getCmd().equals(cmAbort)) {
+                exit();
+                return;
             }
         }
             }
         }
-    }
 
 
-    // ------------------------------------------------------------------------
-    // Screen refresh loop ----------------------------------------------------
-    // ------------------------------------------------------------------------
+        synchronized (drainEventQueue) {
+            // Screen resize
+            if (event instanceof TResizeEvent) {
+                TResizeEvent resize = (TResizeEvent) event;
+                synchronized (getScreen()) {
+                    getScreen().setDimensions(resize.getWidth(),
+                        resize.getHeight());
+                    desktopBottom = getScreen().getHeight() - 1;
+                    mouseX = 0;
+                    mouseY = 0;
+                    oldMouseX = 0;
+                    oldMouseY = 0;
+                }
+                if (desktop != null) {
+                    desktop.setDimensions(0, 0, resize.getWidth(),
+                        resize.getHeight() - 1);
+                }
 
 
-    /**
-     * Process background events, and update the screen.
-     */
-    private void finishEventProcessing() {
-        if (debugThreads) {
-            System.err.printf(System.currentTimeMillis() + " " +
-                Thread.currentThread() + " finishEventProcessing()\n");
-        }
+                // Change menu edges if needed.
+                recomputeMenuX();
 
 
-        // Process timers and call doIdle()'s
-        doIdle();
+                // We are dirty, redraw the screen.
+                doRepaint();
 
 
-        // Update the screen
-        synchronized (getScreen()) {
-            drawAll();
-        }
+                /*
+                System.err.println("New screen: " + resize.getWidth() +
+                    " x " + resize.getHeight());
+                */
+                return;
+            }
 
 
-        if (debugThreads) {
-            System.err.printf(System.currentTimeMillis() + " " +
-                Thread.currentThread() + " finishEventProcessing() END\n");
+            // Put into the main queue
+            drainEventQueue.add(event);
         }
     }
 
     /**
         }
     }
 
     /**
-     * Invert the cell color at a position.  This is used to track the mouse.
+     * Dispatch one event to the appropriate widget or application-level
+     * event handler.  This is the primary event handler, it has the normal
+     * application-wide event handling.
      *
      *
-     * @param x column position
-     * @param y row position
+     * @param event the input event to consume
+     * @see #secondaryHandleEvent(TInputEvent event)
      */
      */
-    private void invertCell(final int x, final int y) {
-        if (debugThreads) {
-            System.err.printf("%d %s invertCell() %d %d\n",
-                System.currentTimeMillis(), Thread.currentThread(), x, y);
+    private void primaryHandleEvent(final TInputEvent event) {
+
+        if (debugEvents) {
+            System.err.printf("Handle event: %s\n", event);
         }
         }
-        CellAttributes attr = getScreen().getAttrXY(x, y);
-        attr.setForeColor(attr.getForeColor().invert());
-        attr.setBackColor(attr.getBackColor().invert());
-        getScreen().putAttrXY(x, y, attr, false);
-    }
+        TMouseEvent doubleClick = null;
 
 
-    /**
-     * Draw everything.
-     */
-    private void drawAll() {
-        boolean menuIsActive = false;
+        // Special application-wide events -----------------------------------
 
 
-        if (debugThreads) {
-            System.err.printf("%d %s drawAll() enter\n",
-                System.currentTimeMillis(), Thread.currentThread());
-        }
+        // Peek at the mouse position
+        if (event instanceof TMouseEvent) {
+            TMouseEvent mouse = (TMouseEvent) event;
+            if ((mouseX != mouse.getX()) || (mouseY != mouse.getY())) {
+                oldMouseX = mouseX;
+                oldMouseY = mouseY;
+                mouseX = mouse.getX();
+                mouseY = mouse.getY();
+            } else {
+                if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
+                    if ((mouse.getTime().getTime() - lastMouseUpTime) <
+                        doubleClickTime) {
 
 
-        if (!repaint) {
-            if (debugThreads) {
-                System.err.printf("%d %s drawAll() !repaint\n",
-                    System.currentTimeMillis(), Thread.currentThread());
-            }
-            synchronized (getScreen()) {
-                if ((oldMouseX != mouseX) || (oldMouseY != mouseY)) {
-                    // The only thing that has happened is the mouse moved.
-                    // Clear the old position and draw the new position.
-                    invertCell(oldMouseX, oldMouseY);
-                    invertCell(mouseX, mouseY);
-                    oldMouseX = mouseX;
-                    oldMouseY = mouseY;
-                }
-                if (getScreen().isDirty()) {
-                    backend.flushScreen();
+                        // This is a double-click.
+                        doubleClick = new TMouseEvent(TMouseEvent.Type.
+                            MOUSE_DOUBLE_CLICK,
+                            mouse.getX(), mouse.getY(),
+                            mouse.getAbsoluteX(), mouse.getAbsoluteY(),
+                            mouse.isMouse1(), mouse.isMouse2(),
+                            mouse.isMouse3(),
+                            mouse.isMouseWheelUp(), mouse.isMouseWheelDown());
+
+                    } else {
+                        // The first click of a potential double-click.
+                        lastMouseUpTime = mouse.getTime().getTime();
+                    }
                 }
                 }
-                return;
             }
             }
-        }
 
 
-        if (debugThreads) {
-            System.err.printf("%d %s drawAll() REDRAW\n",
-                System.currentTimeMillis(), Thread.currentThread());
+            // See if we need to switch focus to another window or the menu
+            checkSwitchFocus((TMouseEvent) event);
         }
 
         }
 
-        // If true, the cursor is not visible
-        boolean cursor = false;
-
-        // Start with a clean screen
-        getScreen().clear();
+        // Handle menu events
+        if ((activeMenu != null) && !(event instanceof TCommandEvent)) {
+            TMenu menu = activeMenu;
 
 
-        // Draw the desktop
-        if (desktop != null) {
-            desktop.drawChildren();
-        }
+            if (event instanceof TMouseEvent) {
+                TMouseEvent mouse = (TMouseEvent) event;
 
 
-        // Draw each window in reverse Z order
-        List<TWindow> sorted = new LinkedList<TWindow>(windows);
-        Collections.sort(sorted);
-        TWindow topLevel = null;
-        if (sorted.size() > 0) {
-            topLevel = sorted.get(0);
-        }
-        Collections.reverse(sorted);
-        for (TWindow window: sorted) {
-            if (window.isShown()) {
-                window.drawChildren();
-            }
-        }
-
-        // Draw the blank menubar line - reset the screen clipping first so
-        // it won't trim it out.
-        getScreen().resetClipping();
-        getScreen().hLineXY(0, 0, getScreen().getWidth(), ' ',
-            theme.getColor("tmenu"));
-        // Now draw the menus.
-        int x = 1;
-        for (TMenu menu: menus) {
-            CellAttributes menuColor;
-            CellAttributes menuMnemonicColor;
-            if (menu.isActive()) {
-                menuIsActive = true;
-                menuColor = theme.getColor("tmenu.highlighted");
-                menuMnemonicColor = theme.getColor("tmenu.mnemonic.highlighted");
-                topLevel = menu;
-            } else {
-                menuColor = theme.getColor("tmenu");
-                menuMnemonicColor = theme.getColor("tmenu.mnemonic");
-            }
-            // Draw the menu title
-            getScreen().hLineXY(x, 0, menu.getTitle().length() + 2, ' ',
-                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);
+                while (subMenus.size() > 0) {
+                    TMenu subMenu = subMenus.get(subMenus.size() - 1);
+                    if (subMenu.mouseWouldHit(mouse)) {
+                        break;
+                    }
+                    if ((mouse.getType() == TMouseEvent.Type.MOUSE_MOTION)
+                        && (!mouse.isMouse1())
+                        && (!mouse.isMouse2())
+                        && (!mouse.isMouse3())
+                        && (!mouse.isMouseWheelUp())
+                        && (!mouse.isMouseWheelDown())
+                    ) {
+                        break;
+                    }
+                    // We navigated away from a sub-menu, so close it
+                    closeSubMenu();
+                }
 
 
-            if (menu.isActive()) {
-                menu.drawChildren();
-                // Reset the screen clipping so we can draw the next title.
-                getScreen().resetClipping();
+                // Convert the mouse relative x/y to menu coordinates
+                assert (mouse.getX() == mouse.getAbsoluteX());
+                assert (mouse.getY() == mouse.getAbsoluteY());
+                if (subMenus.size() > 0) {
+                    menu = subMenus.get(subMenus.size() - 1);
+                }
+                mouse.setX(mouse.getX() - menu.getX());
+                mouse.setY(mouse.getY() - menu.getY());
             }
             }
-            x += menu.getTitle().length() + 2;
-        }
-
-        for (TMenu menu: subMenus) {
-            // Reset the screen clipping so we can draw the next sub-menu.
-            getScreen().resetClipping();
-            menu.drawChildren();
+            menu.handleEvent(event);
+            return;
         }
 
         }
 
-        // Draw the status bar of the top-level window
-        TStatusBar statusBar = null;
-        if (topLevel != null) {
-            statusBar = topLevel.getStatusBar();
-        }
-        if (statusBar != null) {
-            getScreen().resetClipping();
-            statusBar.setWidth(getScreen().getWidth());
-            statusBar.setY(getScreen().getHeight() - topLevel.getY());
-            statusBar.draw();
-        } else {
-            CellAttributes barColor = new CellAttributes();
-            barColor.setTo(getTheme().getColor("tstatusbar.text"));
-            getScreen().hLineXY(0, desktopBottom, getScreen().getWidth(), ' ',
-                barColor);
-        }
+        if (event instanceof TKeypressEvent) {
+            TKeypressEvent keypress = (TKeypressEvent) event;
 
 
-        // Draw the mouse pointer
-        invertCell(mouseX, mouseY);
-        oldMouseX = mouseX;
-        oldMouseY = mouseY;
+            // 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;
+            if (activeWindow != null) {
+                assert (activeWindow.isShown());
+                if (activeWindow.isShortcutKeypress(keypress.getKey())) {
+                    // We do not process this key, it will be passed to the
+                    // window instead.
+                    windowWillShortcut = true;
+                }
+            }
 
 
-        // Place the cursor if it is visible
-        if (!menuIsActive) {
-            TWidget activeWidget = null;
-            if (sorted.size() > 0) {
-                activeWidget = sorted.get(sorted.size() - 1).getActiveChild();
-                if (activeWidget.isCursorVisible()) {
-                    if ((activeWidget.getCursorAbsoluteY() < desktopBottom)
-                        && (activeWidget.getCursorAbsoluteY() > desktopTop)
-                    ) {
-                        getScreen().putCursor(true,
-                            activeWidget.getCursorAbsoluteX(),
-                            activeWidget.getCursorAbsoluteY());
-                        cursor = true;
-                    } else {
-                        getScreen().putCursor(false,
-                            activeWidget.getCursorAbsoluteX(),
-                            activeWidget.getCursorAbsoluteY());
-                        cursor = false;
+            if (!windowWillShortcut && !modalWindowActive()) {
+                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;
+                }
             }
         }
 
             }
         }
 
-        // Kill the cursor
-        if (!cursor) {
-            getScreen().hideCursor();
+        if (event instanceof TCommandEvent) {
+            if (onCommand((TCommandEvent) event)) {
+                return;
+            }
         }
 
         }
 
-        // Flush the screen contents
-        if (getScreen().isDirty()) {
-            backend.flushScreen();
+        if (event instanceof TMenuEvent) {
+            if (onMenu((TMenuEvent) event)) {
+                return;
+            }
         }
 
         }
 
-        repaint = false;
-    }
+        // Dispatch events to the active window -------------------------------
+        boolean dispatchToDesktop = true;
+        TWindow window = activeWindow;
+        if (window != null) {
+            assert (window.isActive());
+            assert (window.isShown());
+            if (event instanceof TMouseEvent) {
+                TMouseEvent mouse = (TMouseEvent) event;
+                // Convert the mouse relative x/y to window coordinates
+                assert (mouse.getX() == mouse.getAbsoluteX());
+                assert (mouse.getY() == mouse.getAbsoluteY());
+                mouse.setX(mouse.getX() - window.getX());
+                mouse.setY(mouse.getY() - window.getY());
 
 
-    // ------------------------------------------------------------------------
-    // Main loop --------------------------------------------------------------
-    // ------------------------------------------------------------------------
+                if (doubleClick != null) {
+                    doubleClick.setX(doubleClick.getX() - window.getX());
+                    doubleClick.setY(doubleClick.getY() - window.getY());
+                }
 
 
-    /**
-     * Force this application to exit.
-     */
-    public void exit() {
-        quit = true;
-        synchronized (this) {
-            this.notify();
+                if (window.mouseWouldHit(mouse)) {
+                    dispatchToDesktop = false;
+                }
+            } else if (event instanceof TKeypressEvent) {
+                dispatchToDesktop = false;
+            }
+
+            if (debugEvents) {
+                System.err.printf("TApplication dispatch event: %s\n",
+                    event);
+            }
+            window.handleEvent(event);
+            if (doubleClick != null) {
+                window.handleEvent(doubleClick);
+            }
+        }
+        if (dispatchToDesktop) {
+            // This event is fair game for the desktop to process.
+            if (desktop != null) {
+                desktop.handleEvent(event);
+                if (doubleClick != null) {
+                    desktop.handleEvent(doubleClick);
+                }
+            }
         }
     }
 
     /**
         }
     }
 
     /**
-     * Run this application until it exits.
+     * Dispatch one event to the appropriate widget or application-level
+     * event handler.  This is the secondary event handler used by certain
+     * special dialogs (currently TMessageBox and TFileOpenBox).
+     *
+     * @param event the input event to consume
+     * @see #primaryHandleEvent(TInputEvent event)
      */
      */
-    public void run() {
-        // Start the main consumer thread
-        primaryEventHandler = new WidgetEventHandler(this, true);
-        (new Thread(primaryEventHandler)).start();
+    private void secondaryHandleEvent(final TInputEvent event) {
+        TMouseEvent doubleClick = null;
 
 
-        started = true;
+        // Peek at the mouse position
+        if (event instanceof TMouseEvent) {
+            TMouseEvent mouse = (TMouseEvent) event;
+            if ((mouseX != mouse.getX()) || (mouseY != mouse.getY())) {
+                oldMouseX = mouseX;
+                oldMouseY = mouseY;
+                mouseX = mouse.getX();
+                mouseY = mouse.getY();
+            } else {
+                if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
+                    if ((mouse.getTime().getTime() - lastMouseUpTime) <
+                        doubleClickTime) {
 
 
-        while (!quit) {
-            synchronized (this) {
-                boolean doWait = false;
+                        // This is a double-click.
+                        doubleClick = new TMouseEvent(TMouseEvent.Type.
+                            MOUSE_DOUBLE_CLICK,
+                            mouse.getX(), mouse.getY(),
+                            mouse.getAbsoluteX(), mouse.getAbsoluteY(),
+                            mouse.isMouse1(), mouse.isMouse2(),
+                            mouse.isMouse3(),
+                            mouse.isMouseWheelUp(), mouse.isMouseWheelDown());
 
 
-                synchronized (fillEventQueue) {
-                    if (fillEventQueue.size() == 0) {
-                        doWait = true;
+                    } else {
+                        // The first click of a potential double-click.
+                        lastMouseUpTime = mouse.getTime().getTime();
                     }
                 }
                     }
                 }
+            }
+        }
 
 
-                if (doWait) {
-                    // No I/O to dispatch, so wait until the backend
-                    // provides new I/O.
-                    try {
-                        if (debugThreads) {
-                            System.err.println(System.currentTimeMillis() +
-                                " MAIN sleep");
-                        }
+        secondaryEventReceiver.handleEvent(event);
+        if (doubleClick != null) {
+            secondaryEventReceiver.handleEvent(doubleClick);
+        }
+    }
 
 
-                        this.wait();
+    /**
+     * Enable a widget to override the primary event thread.
+     *
+     * @param widget widget that will receive events
+     */
+    public final void enableSecondaryEventReceiver(final TWidget widget) {
+        if (debugThreads) {
+            System.err.println(System.currentTimeMillis() +
+                " enableSecondaryEventReceiver()");
+        }
 
 
-                        if (debugThreads) {
-                            System.err.println(System.currentTimeMillis() +
-                                " MAIN AWAKE");
-                        }
-                    } catch (InterruptedException e) {
-                        // I'm awake and don't care why, let's see what's
-                        // going on out there.
-                    }
+        assert (secondaryEventReceiver == null);
+        assert (secondaryEventHandler == null);
+        assert ((widget instanceof TMessageBox)
+            || (widget instanceof TFileOpenBox));
+        secondaryEventReceiver = widget;
+        secondaryEventHandler = new WidgetEventHandler(this, false);
+
+        (new Thread(secondaryEventHandler)).start();
+    }
+
+    /**
+     * Yield to the secondary thread.
+     */
+    public final void yield() {
+        assert (secondaryEventReceiver != null);
+
+        while (secondaryEventReceiver != null) {
+            synchronized (primaryEventHandler) {
+                try {
+                    primaryEventHandler.wait();
+                } catch (InterruptedException e) {
+                    // SQUASH
                 }
                 }
+            }
+        }
+    }
 
 
-            } // synchronized (this)
+    /**
+     * Do stuff when there is no user input.
+     */
+    private void doIdle() {
+        if (debugThreads) {
+            System.err.printf(System.currentTimeMillis() + " " +
+                Thread.currentThread() + " doIdle()\n");
+        }
 
 
-            synchronized (fillEventQueue) {
-                // Pull any pending I/O events
-                backend.getEvents(fillEventQueue);
+        synchronized (timers) {
 
 
-                // Dispatch each event to the appropriate handler, one at a
-                // time.
-                for (;;) {
-                    TInputEvent event = null;
-                    if (fillEventQueue.size() == 0) {
-                        break;
+            if (debugThreads) {
+                System.err.printf(System.currentTimeMillis() + " " +
+                    Thread.currentThread() + " doIdle() 2\n");
+            }
+
+            // Run any timers that have timed out
+            Date now = new Date();
+            List<TTimer> keepTimers = new LinkedList<TTimer>();
+            for (TTimer timer: timers) {
+                if (timer.getNextTick().getTime() <= now.getTime()) {
+                    // Something might change, so repaint the screen.
+                    repaint = true;
+                    timer.tick();
+                    if (timer.recurring) {
+                        keepTimers.add(timer);
                     }
                     }
-                    event = fillEventQueue.remove(0);
-                    metaHandleEvent(event);
+                } else {
+                    keepTimers.add(timer);
                 }
             }
                 }
             }
+            timers = keepTimers;
+        }
 
 
-            // Wake a consumer thread if we have any pending events.
-            if (drainEventQueue.size() > 0) {
-                wakeEventHandler();
-            }
+        // Call onIdle's
+        for (TWindow window: windows) {
+            window.onIdle();
+        }
+        if (desktop != null) {
+            desktop.onIdle();
+        }
+    }
 
 
-        } // while (!quit)
+    /**
+     * Wake the sleeping active event handler.
+     */
+    private void wakeEventHandler() {
+        if (!started) {
+            return;
+        }
 
 
-        // Shutdown the event consumer threads
         if (secondaryEventHandler != null) {
             synchronized (secondaryEventHandler) {
                 secondaryEventHandler.notify();
             }
         if (secondaryEventHandler != null) {
             synchronized (secondaryEventHandler) {
                 secondaryEventHandler.notify();
             }
-        }
-        if (primaryEventHandler != null) {
+        } else {
+            assert (primaryEventHandler != null);
             synchronized (primaryEventHandler) {
                 primaryEventHandler.notify();
             }
         }
             synchronized (primaryEventHandler) {
                 primaryEventHandler.notify();
             }
         }
+    }
 
 
-        // Shutdown the user I/O thread(s)
-        backend.shutdown();
-
-        // Close all the windows.  This gives them an opportunity to release
-        // resources.
-        closeAllWindows();
+    // ------------------------------------------------------------------------
+    // TApplication -----------------------------------------------------------
+    // ------------------------------------------------------------------------
 
 
+    /**
+     * Get the Backend.
+     *
+     * @return the Backend
+     */
+    public final Backend getBackend() {
+        return backend;
     }
 
     /**
     }
 
     /**
-     * Peek at certain application-level events, add to eventQueue, and wake
-     * up the consuming Thread.
+     * Get the Screen.
      *
      *
-     * @param event the input event to consume
+     * @return the Screen
      */
      */
-    private void metaHandleEvent(final TInputEvent event) {
-
-        if (debugEvents) {
-            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;
+    public final Screen getScreen() {
+        if (backend instanceof TWindowBackend) {
+            // We are being rendered to a TWindow.  We can't use its
+            // getScreen() method because that is how it is rendering to a
+            // hardware backend somewhere.  Instead use its getOtherScreen()
+            // method.
+            return ((TWindowBackend) backend).getOtherScreen();
+        } else {
+            return backend.getScreen();
         }
         }
+    }
 
 
-        // Special application-wide events -------------------------------
-
-        // Abort everything
-        if (event instanceof TCommandEvent) {
-            TCommandEvent command = (TCommandEvent) event;
-            if (command.getCmd().equals(cmAbort)) {
-                exit();
-                return;
-            }
-        }
+    /**
+     * Get the color theme.
+     *
+     * @return the theme
+     */
+    public final ColorTheme getTheme() {
+        return theme;
+    }
 
 
-        synchronized (drainEventQueue) {
-            // Screen resize
-            if (event instanceof TResizeEvent) {
-                TResizeEvent resize = (TResizeEvent) event;
-                synchronized (getScreen()) {
-                    getScreen().setDimensions(resize.getWidth(),
-                        resize.getHeight());
-                    desktopBottom = getScreen().getHeight() - 1;
-                    mouseX = 0;
-                    mouseY = 0;
-                    oldMouseX = 0;
-                    oldMouseY = 0;
-                }
-                if (desktop != null) {
-                    desktop.setDimensions(0, 0, resize.getWidth(),
-                        resize.getHeight() - 1);
-                }
+    /**
+     * Repaint the screen on the next update.
+     */
+    public void doRepaint() {
+        repaint = true;
+        wakeEventHandler();
+    }
 
 
-                // Change menu edges if needed.
-                recomputeMenuX();
+    /**
+     * Get Y coordinate of the top edge of the desktop.
+     *
+     * @return Y coordinate of the top edge of the desktop
+     */
+    public final int getDesktopTop() {
+        return desktopTop;
+    }
 
 
-                // We are dirty, redraw the screen.
-                doRepaint();
-                return;
-            }
+    /**
+     * Get Y coordinate of the bottom edge of the desktop.
+     *
+     * @return Y coordinate of the bottom edge of the desktop
+     */
+    public final int getDesktopBottom() {
+        return desktopBottom;
+    }
 
 
-            // Put into the main queue
-            drainEventQueue.add(event);
+    /**
+     * Set the TDesktop instance.
+     *
+     * @param desktop a TDesktop instance, or null to remove the one that is
+     * set
+     */
+    public final void setDesktop(final TDesktop desktop) {
+        if (this.desktop != null) {
+            this.desktop.onClose();
         }
         }
+        this.desktop = desktop;
     }
 
     /**
     }
 
     /**
-     * Dispatch one event to the appropriate widget or application-level
-     * event handler.  This is the primary event handler, it has the normal
-     * application-wide event handling.
+     * Get the TDesktop instance.
      *
      *
-     * @param event the input event to consume
-     * @see #secondaryHandleEvent(TInputEvent event)
+     * @return the desktop, or null if it is not set
      */
      */
-    private void primaryHandleEvent(final TInputEvent event) {
-
-        if (debugEvents) {
-            System.err.printf("Handle event: %s\n", event);
-        }
-        TMouseEvent doubleClick = null;
+    public final TDesktop getDesktop() {
+        return desktop;
+    }
 
 
-        // Special application-wide events -----------------------------------
+    /**
+     * Get the current active window.
+     *
+     * @return the active window, or null if it is not set
+     */
+    public final TWindow getActiveWindow() {
+        return activeWindow;
+    }
 
 
-        // Peek at the mouse position
-        if (event instanceof TMouseEvent) {
-            TMouseEvent mouse = (TMouseEvent) event;
-            if ((mouseX != mouse.getX()) || (mouseY != mouse.getY())) {
-                oldMouseX = mouseX;
-                oldMouseY = mouseY;
-                mouseX = mouse.getX();
-                mouseY = mouse.getY();
-            } else {
-                if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
-                    if ((mouse.getTime().getTime() - lastMouseUpTime) <
-                        doubleClickTime) {
+    /**
+     * Get a (shallow) copy of the window list.
+     *
+     * @return a copy of the list of windows for this application
+     */
+    public final List<TWindow> getAllWindows() {
+        List<TWindow> result = new LinkedList<TWindow>();
+        result.addAll(windows);
+        return result;
+    }
 
 
-                        // This is a double-click.
-                        doubleClick = new TMouseEvent(TMouseEvent.Type.
-                            MOUSE_DOUBLE_CLICK,
-                            mouse.getX(), mouse.getY(),
-                            mouse.getAbsoluteX(), mouse.getAbsoluteY(),
-                            mouse.isMouse1(), mouse.isMouse2(),
-                            mouse.isMouse3(),
-                            mouse.isMouseWheelUp(), mouse.isMouseWheelDown());
+    /**
+     * Get focusFollowsMouse flag.
+     *
+     * @return true if focus follows mouse: windows automatically raised if
+     * the mouse passes over them
+     */
+    public boolean getFocusFollowsMouse() {
+        return focusFollowsMouse;
+    }
 
 
-                    } else {
-                        // The first click of a potential double-click.
-                        lastMouseUpTime = mouse.getTime().getTime();
-                    }
-                }
-            }
+    /**
+     * Set focusFollowsMouse flag.
+     *
+     * @param focusFollowsMouse if true, focus follows mouse: windows
+     * automatically raised if the mouse passes over them
+     */
+    public void setFocusFollowsMouse(final boolean focusFollowsMouse) {
+        this.focusFollowsMouse = focusFollowsMouse;
+    }
 
 
-            // See if we need to switch focus to another window or the menu
-            checkSwitchFocus((TMouseEvent) event);
-        }
+    /**
+     * Display the about dialog.
+     */
+    protected void showAboutDialog() {
+        messageBox(i18n.getString("aboutDialogTitle"),
+            MessageFormat.format(i18n.getString("aboutDialogText"),
+                this.getClass().getPackage().getImplementationVersion()),
+            TMessageBox.Type.OK);
+    }
 
 
-        // Handle menu events
-        if ((activeMenu != null) && !(event instanceof TCommandEvent)) {
-            TMenu menu = activeMenu;
+    // ------------------------------------------------------------------------
+    // Screen refresh loop ----------------------------------------------------
+    // ------------------------------------------------------------------------
 
 
-            if (event instanceof TMouseEvent) {
-                TMouseEvent mouse = (TMouseEvent) event;
+    /**
+     * Invert the cell color at a position.  This is used to track the mouse.
+     *
+     * @param x column position
+     * @param y row position
+     */
+    private void invertCell(final int x, final int y) {
+        if (debugThreads) {
+            System.err.printf("%d %s invertCell() %d %d\n",
+                System.currentTimeMillis(), Thread.currentThread(), x, y);
+        }
+        CellAttributes attr = getScreen().getAttrXY(x, y);
+        attr.setForeColor(attr.getForeColor().invert());
+        attr.setBackColor(attr.getBackColor().invert());
+        getScreen().putAttrXY(x, y, attr, false);
+    }
 
 
-                while (subMenus.size() > 0) {
-                    TMenu subMenu = subMenus.get(subMenus.size() - 1);
-                    if (subMenu.mouseWouldHit(mouse)) {
-                        break;
-                    }
-                    if ((mouse.getType() == TMouseEvent.Type.MOUSE_MOTION)
-                        && (!mouse.isMouse1())
-                        && (!mouse.isMouse2())
-                        && (!mouse.isMouse3())
-                        && (!mouse.isMouseWheelUp())
-                        && (!mouse.isMouseWheelDown())
-                    ) {
-                        break;
-                    }
-                    // We navigated away from a sub-menu, so close it
-                    closeSubMenu();
-                }
+    /**
+     * Draw everything.
+     */
+    private void drawAll() {
+        boolean menuIsActive = false;
 
 
-                // Convert the mouse relative x/y to menu coordinates
-                assert (mouse.getX() == mouse.getAbsoluteX());
-                assert (mouse.getY() == mouse.getAbsoluteY());
-                if (subMenus.size() > 0) {
-                    menu = subMenus.get(subMenus.size() - 1);
-                }
-                mouse.setX(mouse.getX() - menu.getX());
-                mouse.setY(mouse.getY() - menu.getY());
-            }
-            menu.handleEvent(event);
-            return;
+        if (debugThreads) {
+            System.err.printf("%d %s drawAll() enter\n",
+                System.currentTimeMillis(), Thread.currentThread());
         }
 
         }
 
-        if (event instanceof TKeypressEvent) {
-            TKeypressEvent keypress = (TKeypressEvent) event;
-
-            // 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;
-            if (activeWindow != null) {
-                assert (activeWindow.isShown());
-                if (activeWindow.isShortcutKeypress(keypress.getKey())) {
-                    // We do not process this key, it will be passed to the
-                    // window instead.
-                    windowWillShortcut = true;
-                }
+        if (!repaint) {
+            if (debugThreads) {
+                System.err.printf("%d %s drawAll() !repaint\n",
+                    System.currentTimeMillis(), Thread.currentThread());
             }
             }
-
-            if (!windowWillShortcut && !modalWindowActive()) {
-                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;
-                    }
+            synchronized (getScreen()) {
+                if ((oldMouseX != mouseX) || (oldMouseY != mouseY)) {
+                    // The only thing that has happened is the mouse moved.
+                    // Clear the old position and draw the new position.
+                    invertCell(oldMouseX, oldMouseY);
+                    invertCell(mouseX, mouseY);
+                    oldMouseX = mouseX;
+                    oldMouseY = mouseY;
                 }
                 }
-
-                // Handle the keypress
-                if (onKeypress(keypress)) {
-                    return;
+                if (getScreen().isDirty()) {
+                    backend.flushScreen();
                 }
                 }
-            }
-        }
-
-        if (event instanceof TCommandEvent) {
-            if (onCommand((TCommandEvent) event)) {
                 return;
             }
         }
 
                 return;
             }
         }
 
-        if (event instanceof TMenuEvent) {
-            if (onMenu((TMenuEvent) event)) {
-                return;
-            }
+        if (debugThreads) {
+            System.err.printf("%d %s drawAll() REDRAW\n",
+                System.currentTimeMillis(), Thread.currentThread());
         }
 
         }
 
-        // Dispatch events to the active window -------------------------------
-        boolean dispatchToDesktop = true;
-        TWindow window = activeWindow;
-        if (window != null) {
-            assert (window.isActive());
-            assert (window.isShown());
-            if (event instanceof TMouseEvent) {
-                TMouseEvent mouse = (TMouseEvent) event;
-                // Convert the mouse relative x/y to window coordinates
-                assert (mouse.getX() == mouse.getAbsoluteX());
-                assert (mouse.getY() == mouse.getAbsoluteY());
-                mouse.setX(mouse.getX() - window.getX());
-                mouse.setY(mouse.getY() - window.getY());
+        // If true, the cursor is not visible
+        boolean cursor = false;
 
 
-                if (doubleClick != null) {
-                    doubleClick.setX(doubleClick.getX() - window.getX());
-                    doubleClick.setY(doubleClick.getY() - window.getY());
-                }
+        // Start with a clean screen
+        getScreen().clear();
 
 
-                if (window.mouseWouldHit(mouse)) {
-                    dispatchToDesktop = false;
-                }
-            } else if (event instanceof TKeypressEvent) {
-                dispatchToDesktop = false;
-            }
+        // Draw the desktop
+        if (desktop != null) {
+            desktop.drawChildren();
+        }
 
 
-            if (debugEvents) {
-                System.err.printf("TApplication dispatch event: %s\n",
-                    event);
-            }
-            window.handleEvent(event);
-            if (doubleClick != null) {
-                window.handleEvent(doubleClick);
+        // Draw each window in reverse Z order
+        List<TWindow> sorted = new LinkedList<TWindow>(windows);
+        Collections.sort(sorted);
+        TWindow topLevel = null;
+        if (sorted.size() > 0) {
+            topLevel = sorted.get(0);
+        }
+        Collections.reverse(sorted);
+        for (TWindow window: sorted) {
+            if (window.isShown()) {
+                window.drawChildren();
             }
         }
             }
         }
-        if (dispatchToDesktop) {
-            // This event is fair game for the desktop to process.
-            if (desktop != null) {
-                desktop.handleEvent(event);
-                if (doubleClick != null) {
-                    desktop.handleEvent(doubleClick);
-                }
+
+        // Draw the blank menubar line - reset the screen clipping first so
+        // it won't trim it out.
+        getScreen().resetClipping();
+        getScreen().hLineXY(0, 0, getScreen().getWidth(), ' ',
+            theme.getColor("tmenu"));
+        // Now draw the menus.
+        int x = 1;
+        for (TMenu menu: menus) {
+            CellAttributes menuColor;
+            CellAttributes menuMnemonicColor;
+            if (menu.isActive()) {
+                menuIsActive = true;
+                menuColor = theme.getColor("tmenu.highlighted");
+                menuMnemonicColor = theme.getColor("tmenu.mnemonic.highlighted");
+                topLevel = menu;
+            } else {
+                menuColor = theme.getColor("tmenu");
+                menuMnemonicColor = theme.getColor("tmenu.mnemonic");
+            }
+            // Draw the menu title
+            getScreen().hLineXY(x, 0, menu.getTitle().length() + 2, ' ',
+                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.isActive()) {
+                menu.drawChildren();
+                // Reset the screen clipping so we can draw the next title.
+                getScreen().resetClipping();
             }
             }
+            x += menu.getTitle().length() + 2;
         }
         }
-    }
 
 
-    /**
-     * Dispatch one event to the appropriate widget or application-level
-     * event handler.  This is the secondary event handler used by certain
-     * special dialogs (currently TMessageBox and TFileOpenBox).
-     *
-     * @param event the input event to consume
-     * @see #primaryHandleEvent(TInputEvent event)
-     */
-    private void secondaryHandleEvent(final TInputEvent event) {
-        TMouseEvent doubleClick = null;
+        for (TMenu menu: subMenus) {
+            // Reset the screen clipping so we can draw the next sub-menu.
+            getScreen().resetClipping();
+            menu.drawChildren();
+        }
 
 
-        // Peek at the mouse position
-        if (event instanceof TMouseEvent) {
-            TMouseEvent mouse = (TMouseEvent) event;
-            if ((mouseX != mouse.getX()) || (mouseY != mouse.getY())) {
-                oldMouseX = mouseX;
-                oldMouseY = mouseY;
-                mouseX = mouse.getX();
-                mouseY = mouse.getY();
-            } else {
-                if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
-                    if ((mouse.getTime().getTime() - lastMouseUpTime) <
-                        doubleClickTime) {
+        // Draw the status bar of the top-level window
+        TStatusBar statusBar = null;
+        if (topLevel != null) {
+            statusBar = topLevel.getStatusBar();
+        }
+        if (statusBar != null) {
+            getScreen().resetClipping();
+            statusBar.setWidth(getScreen().getWidth());
+            statusBar.setY(getScreen().getHeight() - topLevel.getY());
+            statusBar.draw();
+        } else {
+            CellAttributes barColor = new CellAttributes();
+            barColor.setTo(getTheme().getColor("tstatusbar.text"));
+            getScreen().hLineXY(0, desktopBottom, getScreen().getWidth(), ' ',
+                barColor);
+        }
 
 
-                        // This is a double-click.
-                        doubleClick = new TMouseEvent(TMouseEvent.Type.
-                            MOUSE_DOUBLE_CLICK,
-                            mouse.getX(), mouse.getY(),
-                            mouse.getAbsoluteX(), mouse.getAbsoluteY(),
-                            mouse.isMouse1(), mouse.isMouse2(),
-                            mouse.isMouse3(),
-                            mouse.isMouseWheelUp(), mouse.isMouseWheelDown());
+        // Draw the mouse pointer
+        invertCell(mouseX, mouseY);
+        oldMouseX = mouseX;
+        oldMouseY = mouseY;
 
 
+        // Place the cursor if it is visible
+        if (!menuIsActive) {
+            TWidget activeWidget = null;
+            if (sorted.size() > 0) {
+                activeWidget = sorted.get(sorted.size() - 1).getActiveChild();
+                if (activeWidget.isCursorVisible()) {
+                    if ((activeWidget.getCursorAbsoluteY() < desktopBottom)
+                        && (activeWidget.getCursorAbsoluteY() > desktopTop)
+                    ) {
+                        getScreen().putCursor(true,
+                            activeWidget.getCursorAbsoluteX(),
+                            activeWidget.getCursorAbsoluteY());
+                        cursor = true;
                     } else {
                     } else {
-                        // The first click of a potential double-click.
-                        lastMouseUpTime = mouse.getTime().getTime();
+                        getScreen().putCursor(false,
+                            activeWidget.getCursorAbsoluteX(),
+                            activeWidget.getCursorAbsoluteY());
+                        cursor = false;
                     }
                 }
             }
         }
 
                     }
                 }
             }
         }
 
-        secondaryEventReceiver.handleEvent(event);
-        if (doubleClick != null) {
-            secondaryEventReceiver.handleEvent(doubleClick);
+        // Kill the cursor
+        if (!cursor) {
+            getScreen().hideCursor();
         }
         }
-    }
 
 
-    /**
-     * Enable a widget to override the primary event thread.
-     *
-     * @param widget widget that will receive events
-     */
-    public final void enableSecondaryEventReceiver(final TWidget widget) {
-        if (debugThreads) {
-            System.err.println(System.currentTimeMillis() +
-                " enableSecondaryEventReceiver()");
+        // Flush the screen contents
+        if (getScreen().isDirty()) {
+            backend.flushScreen();
         }
 
         }
 
-        assert (secondaryEventReceiver == null);
-        assert (secondaryEventHandler == null);
-        assert ((widget instanceof TMessageBox)
-            || (widget instanceof TFileOpenBox));
-        secondaryEventReceiver = widget;
-        secondaryEventHandler = new WidgetEventHandler(this, false);
-
-        (new Thread(secondaryEventHandler)).start();
-    }
-
-    /**
-     * Yield to the secondary thread.
-     */
-    public final void yield() {
-        assert (secondaryEventReceiver != null);
-
-        while (secondaryEventReceiver != null) {
-            synchronized (primaryEventHandler) {
-                try {
-                    primaryEventHandler.wait();
-                } catch (InterruptedException e) {
-                    // SQUASH
-                }
-            }
-        }
+        repaint = false;
     }
 
     /**
     }
 
     /**
-     * Do stuff when there is no user input.
+     * Force this application to exit.
      */
      */
-    private void doIdle() {
-        if (debugThreads) {
-            System.err.printf(System.currentTimeMillis() + " " +
-                Thread.currentThread() + " doIdle()\n");
-        }
-
-        synchronized (timers) {
-
-            if (debugThreads) {
-                System.err.printf(System.currentTimeMillis() + " " +
-                    Thread.currentThread() + " doIdle() 2\n");
-            }
-
-            // Run any timers that have timed out
-            Date now = new Date();
-            List<TTimer> keepTimers = new LinkedList<TTimer>();
-            for (TTimer timer: timers) {
-                if (timer.getNextTick().getTime() <= now.getTime()) {
-                    // Something might change, so repaint the screen.
-                    repaint = true;
-                    timer.tick();
-                    if (timer.recurring) {
-                        keepTimers.add(timer);
-                    }
-                } else {
-                    keepTimers.add(timer);
-                }
-            }
-            timers = keepTimers;
-        }
-
-        // Call onIdle's
-        for (TWindow window: windows) {
-            window.onIdle();
-        }
-        if (desktop != null) {
-            desktop.onIdle();
+    public void exit() {
+        quit = true;
+        synchronized (this) {
+            this.notify();
         }
     }
 
         }
     }
 
@@ -1770,7 +1906,7 @@ public class TApplication implements Runnable {
      *
      * @param window new window to add
      */
      *
      * @param window new window to add
      */
-    public final void addWindow(final TWindow window) {
+    public final void addWindowToApplication(final TWindow window) {
 
         // Do not add menu windows to the window list.
         if (window instanceof TMenu) {
 
         // Do not add menu windows to the window list.
         if (window instanceof TMenu) {
@@ -1816,7 +1952,9 @@ public class TApplication implements Runnable {
             }
 
             if (((window.flags & TWindow.CENTERED) == 0)
             }
 
             if (((window.flags & TWindow.CENTERED) == 0)
-                && smartWindowPlacement) {
+                && ((window.flags & TWindow.ABSOLUTEXY) == 0)
+                && (smartWindowPlacement == true)
+            ) {
 
                 doSmartPlacement(window);
             }
 
                 doSmartPlacement(window);
             }
@@ -2577,139 +2715,6 @@ public class TApplication implements Runnable {
         return helpMenu;
     }
 
         return helpMenu;
     }
 
-    // ------------------------------------------------------------------------
-    // Event handlers ---------------------------------------------------------
-    // ------------------------------------------------------------------------
-
-    /**
-     * 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(i18n.getString("exitDialogTitle"),
-                    i18n.getString("exitDialogText"),
-                    TMessageBox.Type.YESNO).getResult() == TMessageBox.Result.YES) {
-                exit();
-            }
-            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;
-        }
-
-        if (command.equals(cmMenu)) {
-            if (!modalWindowActive() && (activeMenu == null)) {
-                if (menus.size() > 0) {
-                    menus.get(0).setActive(true);
-                    activeMenu = menus.get(0);
-                    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(i18n.getString("exitDialogTitle"),
-                    i18n.getString("exitDialogText"),
-                    TMessageBox.Type.YESNO).getResult() == TMessageBox.Result.YES) {
-                exit();
-            }
-            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;
-        }
-        if (menu.getId() == TMenu.MID_ABOUT) {
-            showAboutDialog();
-            return true;
-        }
-        if (menu.getId() == TMenu.MID_REPAINT) {
-            doRepaint();
-            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)
-            && !modalWindowActive()
-        ) {
-
-            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;
-    }
-
     // ------------------------------------------------------------------------
     // TTimer management ------------------------------------------------------
     // ------------------------------------------------------------------------
     // ------------------------------------------------------------------------
     // TTimer management ------------------------------------------------------
     // ------------------------------------------------------------------------
index 255dd990738d369490a288d993dd24dd24890c49..d4e7c8952a208682febdf59457476807cc8910d5 100644 (file)
@@ -44,20 +44,15 @@ import static jexer.TKeypress.*;
  */
 public final class TButton extends TWidget {
 
  */
 public final class TButton extends TWidget {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * The shortcut and button text.
      */
     private MnemonicString mnemonic;
 
     /**
      * The shortcut and button text.
      */
     private MnemonicString mnemonic;
 
-    /**
-     * Get the mnemonic string for this button.
-     *
-     * @return mnemonic string
-     */
-    public MnemonicString getMnemonic() {
-        return mnemonic;
-    }
-
     /**
      * Remember mouse state.
      */
     /**
      * Remember mouse state.
      */
@@ -73,16 +68,9 @@ public final class TButton extends TWidget {
      */
     private TAction action;
 
      */
     private TAction action;
 
-    /**
-     * Act as though the button was pressed.  This is useful for other UI
-     * elements to get the same action as if the user clicked the button.
-     */
-    public void dispatch() {
-        if (action != null) {
-            action.DO();
-            inButtonPress = false;
-        }
-    }
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Private constructor.
 
     /**
      * Private constructor.
@@ -122,6 +110,10 @@ public final class TButton extends TWidget {
         this.action = action;
     }
 
         this.action = action;
     }
 
+    // ------------------------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Returns true if the mouse is currently on the button.
      *
     /**
      * Returns true if the mouse is currently on the button.
      *
@@ -142,55 +134,6 @@ public final class TButton extends TWidget {
         return false;
     }
 
         return false;
     }
 
-    /**
-     * Draw a button with a shadow.
-     */
-    @Override
-    public void draw() {
-        CellAttributes buttonColor;
-        CellAttributes menuMnemonicColor;
-        CellAttributes shadowColor = new CellAttributes();
-        shadowColor.setTo(getWindow().getBackground());
-        shadowColor.setForeColor(Color.BLACK);
-        shadowColor.setBold(false);
-
-        if (!isEnabled()) {
-            buttonColor = getTheme().getColor("tbutton.disabled");
-            menuMnemonicColor = getTheme().getColor("tbutton.disabled");
-        } else if (isAbsoluteActive()) {
-            buttonColor = getTheme().getColor("tbutton.active");
-            menuMnemonicColor = getTheme().getColor("tbutton.mnemonic.highlighted");
-        } else {
-            buttonColor = getTheme().getColor("tbutton.inactive");
-            menuMnemonicColor = getTheme().getColor("tbutton.mnemonic");
-        }
-
-        if (inButtonPress) {
-            getScreen().putCharXY(1, 0, ' ', buttonColor);
-            getScreen().putStringXY(2, 0, mnemonic.getRawLabel(), buttonColor);
-            getScreen().putCharXY(getWidth() - 1, 0, ' ', buttonColor);
-        } else {
-            getScreen().putCharXY(0, 0, ' ', buttonColor);
-            getScreen().putStringXY(1, 0, mnemonic.getRawLabel(), buttonColor);
-            getScreen().putCharXY(getWidth() - 2, 0, ' ', buttonColor);
-
-            getScreen().putCharXY(getWidth() - 1, 0,
-                GraphicsChars.CP437[0xDC], shadowColor);
-            getScreen().hLineXY(1, 1, getWidth() - 1,
-                GraphicsChars.CP437[0xDF], shadowColor);
-        }
-        if (mnemonic.getShortcutIdx() >= 0) {
-            if (inButtonPress) {
-                getScreen().putCharXY(2 + mnemonic.getShortcutIdx(), 0,
-                    mnemonic.getShortcut(), menuMnemonicColor);
-            } else {
-                getScreen().putCharXY(1 + mnemonic.getShortcutIdx(), 0,
-                    mnemonic.getShortcut(), menuMnemonicColor);
-            }
-
-        }
-    }
-
     /**
      * Handle mouse button presses.
      *
     /**
      * Handle mouse button presses.
      *
@@ -255,4 +198,81 @@ public final class TButton extends TWidget {
         super.onKeypress(keypress);
     }
 
         super.onKeypress(keypress);
     }
 
+    // ------------------------------------------------------------------------
+    // TWidget ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Draw a button with a shadow.
+     */
+    @Override
+    public void draw() {
+        CellAttributes buttonColor;
+        CellAttributes menuMnemonicColor;
+        CellAttributes shadowColor = new CellAttributes();
+        shadowColor.setTo(getWindow().getBackground());
+        shadowColor.setForeColor(Color.BLACK);
+        shadowColor.setBold(false);
+
+        if (!isEnabled()) {
+            buttonColor = getTheme().getColor("tbutton.disabled");
+            menuMnemonicColor = getTheme().getColor("tbutton.disabled");
+        } else if (isAbsoluteActive()) {
+            buttonColor = getTheme().getColor("tbutton.active");
+            menuMnemonicColor = getTheme().getColor("tbutton.mnemonic.highlighted");
+        } else {
+            buttonColor = getTheme().getColor("tbutton.inactive");
+            menuMnemonicColor = getTheme().getColor("tbutton.mnemonic");
+        }
+
+        if (inButtonPress) {
+            getScreen().putCharXY(1, 0, ' ', buttonColor);
+            getScreen().putStringXY(2, 0, mnemonic.getRawLabel(), buttonColor);
+            getScreen().putCharXY(getWidth() - 1, 0, ' ', buttonColor);
+        } else {
+            getScreen().putCharXY(0, 0, ' ', buttonColor);
+            getScreen().putStringXY(1, 0, mnemonic.getRawLabel(), buttonColor);
+            getScreen().putCharXY(getWidth() - 2, 0, ' ', buttonColor);
+
+            getScreen().putCharXY(getWidth() - 1, 0,
+                GraphicsChars.CP437[0xDC], shadowColor);
+            getScreen().hLineXY(1, 1, getWidth() - 1,
+                GraphicsChars.CP437[0xDF], shadowColor);
+        }
+        if (mnemonic.getShortcutIdx() >= 0) {
+            if (inButtonPress) {
+                getScreen().putCharXY(2 + mnemonic.getShortcutIdx(), 0,
+                    mnemonic.getShortcut(), menuMnemonicColor);
+            } else {
+                getScreen().putCharXY(1 + mnemonic.getShortcutIdx(), 0,
+                    mnemonic.getShortcut(), menuMnemonicColor);
+            }
+
+        }
+    }
+
+    // ------------------------------------------------------------------------
+    // TButton ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Get the mnemonic string for this button.
+     *
+     * @return mnemonic string
+     */
+    public MnemonicString getMnemonic() {
+        return mnemonic;
+    }
+
+    /**
+     * Act as though the button was pressed.  This is useful for other UI
+     * elements to get the same action as if the user clicked the button.
+     */
+    public void dispatch() {
+        if (action != null) {
+            action.DO();
+            inButtonPress = false;
+        }
+    }
+
 }
 }
index 78c83c83cfe59ba17e245cf1db2878d97b8c820a..86933a73afd147519393c98757abe027929f6760 100644 (file)
@@ -39,34 +39,24 @@ import jexer.event.TMouseEvent;
  */
 public final class TCheckbox extends TWidget {
 
  */
 public final class TCheckbox extends TWidget {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Checkbox state, true means checked.
      */
     private boolean checked = false;
 
     /**
      * Checkbox state, true means checked.
      */
     private boolean checked = false;
 
-    /**
-     * Get checked value.
-     *
-     * @return if true, this is checked
-     */
-    public boolean isChecked() {
-        return checked;
-    }
-
-    /**
-     * Set checked value.
-     *
-     * @param checked new checked value.
-     */
-    public void setChecked(final boolean checked) {
-        this.checked = checked;
-    }
-
     /**
      * Label for this checkbox.
      */
     private String label;
 
     /**
      * Label for this checkbox.
      */
     private String label;
 
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Public constructor.
      *
     /**
      * Public constructor.
      *
@@ -77,7 +67,7 @@ public final class TCheckbox extends TWidget {
      * @param checked initial check state
      */
     public TCheckbox(final TWidget parent, final int x, final int y,
      * @param checked initial check state
      */
     public TCheckbox(final TWidget parent, final int x, final int y,
-            final String label, final boolean checked) {
+        final String label, final boolean checked) {
 
         // Set parent and window
         super(parent, x, y, label.length() + 4, 1);
 
         // Set parent and window
         super(parent, x, y, label.length() + 4, 1);
@@ -89,6 +79,10 @@ public final class TCheckbox extends TWidget {
         setCursorX(1);
     }
 
         setCursorX(1);
     }
 
+    // ------------------------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Returns true if the mouse is currently on the checkbox.
      *
     /**
      * Returns true if the mouse is currently on the checkbox.
      *
@@ -97,14 +91,47 @@ public final class TCheckbox extends TWidget {
      */
     private boolean mouseOnCheckbox(final TMouseEvent mouse) {
         if ((mouse.getY() == 0)
      */
     private boolean mouseOnCheckbox(final TMouseEvent mouse) {
         if ((mouse.getY() == 0)
-                && (mouse.getX() >= 0)
-                && (mouse.getX() <= 2)
-                ) {
+            && (mouse.getX() >= 0)
+            && (mouse.getX() <= 2)
+        ) {
             return true;
         }
         return false;
     }
 
             return true;
         }
         return false;
     }
 
+    /**
+     * Handle mouse checkbox presses.
+     *
+     * @param mouse mouse button down event
+     */
+    @Override
+    public void onMouseDown(final TMouseEvent mouse) {
+        if ((mouseOnCheckbox(mouse)) && (mouse.isMouse1())) {
+            // Switch state
+            checked = !checked;
+        }
+    }
+
+    /**
+     * Handle keystrokes.
+     *
+     * @param keypress keystroke event
+     */
+    @Override
+    public void onKeypress(final TKeypressEvent keypress) {
+        if (keypress.equals(kbSpace)) {
+            checked = !checked;
+            return;
+        }
+
+        // Pass to parent for the things we don't care about.
+        super.onKeypress(keypress);
+    }
+
+    // ------------------------------------------------------------------------
+    // TWidget ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Draw a checkbox with label.
      */
     /**
      * Draw a checkbox with label.
      */
@@ -128,33 +155,26 @@ public final class TCheckbox extends TWidget {
         getScreen().putStringXY(4, 0, label, checkboxColor);
     }
 
         getScreen().putStringXY(4, 0, label, checkboxColor);
     }
 
+    // ------------------------------------------------------------------------
+    // TCheckbox --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
     /**
-     * Handle mouse checkbox presses.
+     * Get checked value.
      *
      *
-     * @param mouse mouse button down event
+     * @return if true, this is checked
      */
      */
-    @Override
-    public void onMouseDown(final TMouseEvent mouse) {
-        if ((mouseOnCheckbox(mouse)) && (mouse.isMouse1())) {
-            // Switch state
-            checked = !checked;
-        }
+    public boolean isChecked() {
+        return checked;
     }
 
     /**
     }
 
     /**
-     * Handle keystrokes.
+     * Set checked value.
      *
      *
-     * @param keypress keystroke event
+     * @param checked new checked value.
      */
      */
-    @Override
-    public void onKeypress(final TKeypressEvent keypress) {
-        if (keypress.equals(kbSpace)) {
-            checked = !checked;
-            return;
-        }
-
-        // Pass to parent for the things we don't care about.
-        super.onKeypress(keypress);
+    public void setChecked(final boolean checked) {
+        this.checked = checked;
     }
 
 }
     }
 
 }
index a814fae7c15d79ee2f201d547be83e32e95a3382..b6a0411c099018cf78d69690c8e5041c7c41a0fc 100644 (file)
@@ -35,6 +35,10 @@ package jexer;
  */
 public class TCommand {
 
  */
 public class TCommand {
 
+    // ------------------------------------------------------------------------
+    // Constants --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Immediately abort the application (e.g. remote side closed
      * connection).
     /**
      * Immediately abort the application (e.g. remote side closed
      * connection).
@@ -131,11 +135,40 @@ public class TCommand {
      */
     public static final int SAVE                = 30;
 
      */
     public static final int SAVE                = 30;
 
+    public static final TCommand cmAbort        = new TCommand(ABORT);
+    public static final TCommand cmExit         = new TCommand(EXIT);
+    public static final TCommand cmQuit         = new TCommand(EXIT);
+    public static final TCommand cmOpen         = new TCommand(OPEN);
+    public static final TCommand cmShell        = new TCommand(SHELL);
+    public static final TCommand cmCut          = new TCommand(CUT);
+    public static final TCommand cmCopy         = new TCommand(COPY);
+    public static final TCommand cmPaste        = new TCommand(PASTE);
+    public static final TCommand cmClear        = new TCommand(CLEAR);
+    public static final TCommand cmTile         = new TCommand(TILE);
+    public static final TCommand cmCascade      = new TCommand(CASCADE);
+    public static final TCommand cmCloseAll     = new TCommand(CLOSE_ALL);
+    public static final TCommand cmWindowMove   = new TCommand(WINDOW_MOVE);
+    public static final TCommand cmWindowZoom   = new TCommand(WINDOW_ZOOM);
+    public static final TCommand cmWindowNext   = new TCommand(WINDOW_NEXT);
+    public static final TCommand cmWindowPrevious = new TCommand(WINDOW_PREVIOUS);
+    public static final TCommand cmWindowClose  = new TCommand(WINDOW_CLOSE);
+    public static final TCommand cmHelp         = new TCommand(HELP);
+    public static final TCommand cmSave         = new TCommand(SAVE);
+    public static final TCommand cmMenu         = new TCommand(MENU);
+
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Type of command, one of EXIT, CASCADE, etc.
      */
     private int type;
 
     /**
      * Type of command, one of EXIT, CASCADE, etc.
      */
     private int type;
 
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Protected constructor.  Subclasses can be used to define new commands.
      *
     /**
      * Protected constructor.  Subclasses can be used to define new commands.
      *
@@ -145,6 +178,10 @@ public class TCommand {
         this.type = type;
     }
 
         this.type = type;
     }
 
+    // ------------------------------------------------------------------------
+    // TCommand ---------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Make human-readable description of this TCommand.
      *
     /**
      * Make human-readable description of this TCommand.
      *
@@ -181,25 +218,4 @@ public class TCommand {
         return type;
     }
 
         return type;
     }
 
-    public static final TCommand cmAbort        = new TCommand(ABORT);
-    public static final TCommand cmExit         = new TCommand(EXIT);
-    public static final TCommand cmQuit         = new TCommand(EXIT);
-    public static final TCommand cmOpen         = new TCommand(OPEN);
-    public static final TCommand cmShell        = new TCommand(SHELL);
-    public static final TCommand cmCut          = new TCommand(CUT);
-    public static final TCommand cmCopy         = new TCommand(COPY);
-    public static final TCommand cmPaste        = new TCommand(PASTE);
-    public static final TCommand cmClear        = new TCommand(CLEAR);
-    public static final TCommand cmTile         = new TCommand(TILE);
-    public static final TCommand cmCascade      = new TCommand(CASCADE);
-    public static final TCommand cmCloseAll     = new TCommand(CLOSE_ALL);
-    public static final TCommand cmWindowMove   = new TCommand(WINDOW_MOVE);
-    public static final TCommand cmWindowZoom   = new TCommand(WINDOW_ZOOM);
-    public static final TCommand cmWindowNext   = new TCommand(WINDOW_NEXT);
-    public static final TCommand cmWindowPrevious = new TCommand(WINDOW_PREVIOUS);
-    public static final TCommand cmWindowClose  = new TCommand(WINDOW_CLOSE);
-    public static final TCommand cmHelp         = new TCommand(HELP);
-    public static final TCommand cmSave         = new TCommand(SAVE);
-    public static final TCommand cmMenu         = new TCommand(MENU);
-
 }
 }
index 5b4abf39f467a80ecfaf4203cdfcb5bfd53cbffe..bc3712b7890b3d8d46be563b01d4cfb8bd172fa8 100644 (file)
@@ -51,6 +51,30 @@ public class TEditColorThemeWindow extends TWindow {
      */
     private static final ResourceBundle i18n = ResourceBundle.getBundle(TEditColorThemeWindow.class.getName());
 
      */
     private static final ResourceBundle i18n = ResourceBundle.getBundle(TEditColorThemeWindow.class.getName());
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * The current editing theme.
+     */
+    private ColorTheme editTheme;
+
+    /**
+     * The left-side list of colors pane.
+     */
+    private TList colorNames;
+
+    /**
+     * The foreground color.
+     */
+    private ForegroundPicker foreground;
+
+    /**
+     * The background color.
+     */
+    private BackgroundPicker background;
+
     /**
      * The foreground color picker.
      */
     /**
      * The foreground color picker.
      */
@@ -602,53 +626,9 @@ public class TEditColorThemeWindow extends TWindow {
 
     }
 
 
     }
 
-    /**
-     * The current editing theme.
-     */
-    private ColorTheme editTheme;
-
-    /**
-     * The left-side list of colors pane.
-     */
-    private TList colorNames;
-
-    /**
-     * The foreground color.
-     */
-    private ForegroundPicker foreground;
-
-    /**
-     * The background color.
-     */
-    private BackgroundPicker background;
-
-    /**
-     * Set various widgets/values to the editing theme color.
-     *
-     * @param colorName name of color from theme
-     */
-    private void refreshFromTheme(final String colorName) {
-        CellAttributes attr = editTheme.getColor(colorName);
-        foreground.color = attr.getForeColor();
-        foreground.bold = attr.isBold();
-        background.color = attr.getBackColor();
-    }
-
-    /**
-     * Examines foreground, background, and colorNames and sets the color in
-     * editTheme.
-     */
-    private void saveToEditTheme() {
-        String colorName = colorNames.getSelected();
-        if (colorName == null) {
-            return;
-        }
-        CellAttributes attr = editTheme.getColor(colorName);
-        attr.setForeColor(foreground.color);
-        attr.setBold(foreground.bold);
-        attr.setBackColor(background.color);
-        editTheme.setColor(colorName, attr);
-    }
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Public constructor.  The file open box will be centered on screen.
 
     /**
      * Public constructor.  The file open box will be centered on screen.
@@ -720,6 +700,31 @@ public class TEditColorThemeWindow extends TWindow {
         newStatusBar(i18n.getString("statusBar"));
     }
 
         newStatusBar(i18n.getString("statusBar"));
     }
 
+    // ------------------------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Handle keystrokes.
+     *
+     * @param keypress keystroke event
+     */
+    @Override
+    public void onKeypress(final TKeypressEvent keypress) {
+        // Escape - behave like cancel
+        if (keypress.equals(kbEsc)) {
+            getApplication().closeWindow(this);
+            return;
+        }
+
+        // Pass to my parent
+        super.onKeypress(keypress);
+    }
+
+    // ------------------------------------------------------------------------
+    // TWindow ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Draw me on screen.
      */
     /**
      * Draw me on screen.
      */
@@ -747,21 +752,36 @@ public class TEditColorThemeWindow extends TWindow {
             i18n.getString("textTextText"), attr);
     }
 
             i18n.getString("textTextText"), attr);
     }
 
+    // ------------------------------------------------------------------------
+    // TEditColorThemeWindow --------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
     /**
-     * Handle keystrokes.
+     * Set various widgets/values to the editing theme color.
      *
      *
-     * @param keypress keystroke event
+     * @param colorName name of color from theme
      */
      */
-    @Override
-    public void onKeypress(final TKeypressEvent keypress) {
-        // Escape - behave like cancel
-        if (keypress.equals(kbEsc)) {
-            getApplication().closeWindow(this);
+    private void refreshFromTheme(final String colorName) {
+        CellAttributes attr = editTheme.getColor(colorName);
+        foreground.color = attr.getForeColor();
+        foreground.bold = attr.isBold();
+        background.color = attr.getBackColor();
+    }
+
+    /**
+     * Examines foreground, background, and colorNames and sets the color in
+     * editTheme.
+     */
+    private void saveToEditTheme() {
+        String colorName = colorNames.getSelected();
+        if (colorName == null) {
             return;
         }
             return;
         }
-
-        // Pass to my parent
-        super.onKeypress(keypress);
+        CellAttributes attr = editTheme.getColor(colorName);
+        attr.setForeColor(foreground.color);
+        attr.setBold(foreground.bold);
+        attr.setBackColor(background.color);
+        editTheme.setColor(colorName, attr);
     }
 
 }
     }
 
 }
index c93cecbcbf97670babe1d07d26508498467a3030..8f693f2123539abfc8b4ca85ba923441cfada52c 100644 (file)
@@ -39,31 +39,15 @@ import static jexer.TKeypress.*;
  */
 public class TField extends TWidget {
 
  */
 public class TField extends TWidget {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Field text.
      */
     protected String text = "";
 
     /**
      * Field text.
      */
     protected String text = "";
 
-    /**
-     * Get field text.
-     *
-     * @return field text
-     */
-    public final String getText() {
-        return text;
-    }
-
-    /**
-     * Set field text.
-     *
-     * @param text the new field text
-     */
-    public final void setText(String text) {
-        this.text = text;
-        position = 0;
-        windowStart = 0;
-    }
-
     /**
      * If true, only allow enough characters that will fit in the width.  If
      * false, allow the field to scroll to the right.
     /**
      * If true, only allow enough characters that will fit in the width.  If
      * false, allow the field to scroll to the right.
@@ -100,6 +84,10 @@ public class TField extends TWidget {
      */
     protected TAction updateAction;
 
      */
     protected TAction updateAction;
 
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Public constructor.
      *
     /**
      * Public constructor.
      *
@@ -157,6 +145,10 @@ public class TField extends TWidget {
         this.updateAction = updateAction;
     }
 
         this.updateAction = updateAction;
     }
 
+    // ------------------------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Returns true if the mouse is currently on the field.
      *
     /**
      * Returns true if the mouse is currently on the field.
      *
@@ -174,80 +166,6 @@ public class TField extends TWidget {
         return false;
     }
 
         return false;
     }
 
-    /**
-     * Dispatch to the action function.
-     *
-     * @param enter if true, the user pressed Enter, else this was an update
-     * to the text.
-     */
-    protected void dispatch(final boolean enter) {
-        if (enter) {
-            if (enterAction != null) {
-                enterAction.DO();
-            }
-        } else {
-            if (updateAction != null) {
-                updateAction.DO();
-            }
-        }
-    }
-
-    /**
-     * Draw the text field.
-     */
-    @Override
-    public void draw() {
-        CellAttributes fieldColor;
-
-        if (isAbsoluteActive()) {
-            fieldColor = getTheme().getColor("tfield.active");
-        } else {
-            fieldColor = getTheme().getColor("tfield.inactive");
-        }
-
-        int end = windowStart + getWidth();
-        if (end > text.length()) {
-            end = text.length();
-        }
-        getScreen().hLineXY(0, 0, getWidth(), GraphicsChars.HATCH, fieldColor);
-        getScreen().putStringXY(0, 0, text.substring(windowStart, end),
-            fieldColor);
-
-        // Fix the cursor, it will be rendered by TApplication.drawAll().
-        updateCursor();
-    }
-
-    /**
-     * Update the visible cursor position to match the location of position
-     * and windowStart.
-     */
-    protected void updateCursor() {
-        if ((position > getWidth()) && fixed) {
-            setCursorX(getWidth());
-        } else if ((position - windowStart == getWidth()) && !fixed) {
-            setCursorX(getWidth() - 1);
-        } else {
-            setCursorX(position - windowStart);
-        }
-    }
-
-    /**
-     * Normalize windowStart such that most of the field data if visible.
-     */
-    protected void normalizeWindowStart() {
-        if (fixed) {
-            // windowStart had better be zero, there is nothing to do here.
-            assert (windowStart == 0);
-            return;
-        }
-        windowStart = position - (getWidth() - 1);
-        if (windowStart < 0) {
-            windowStart = 0;
-        }
-
-        updateCursor();
-    }
-
     /**
      * Handle mouse button presses.
      *
     /**
      * Handle mouse button presses.
      *
@@ -419,6 +337,108 @@ public class TField extends TWidget {
         super.onKeypress(keypress);
     }
 
         super.onKeypress(keypress);
     }
 
+    // ------------------------------------------------------------------------
+    // TWidget ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Draw the text field.
+     */
+    @Override
+    public void draw() {
+        CellAttributes fieldColor;
+
+        if (isAbsoluteActive()) {
+            fieldColor = getTheme().getColor("tfield.active");
+        } else {
+            fieldColor = getTheme().getColor("tfield.inactive");
+        }
+
+        int end = windowStart + getWidth();
+        if (end > text.length()) {
+            end = text.length();
+        }
+        getScreen().hLineXY(0, 0, getWidth(), GraphicsChars.HATCH, fieldColor);
+        getScreen().putStringXY(0, 0, text.substring(windowStart, end),
+            fieldColor);
+
+        // Fix the cursor, it will be rendered by TApplication.drawAll().
+        updateCursor();
+    }
+
+    // ------------------------------------------------------------------------
+    // TField -----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Get field text.
+     *
+     * @return field text
+     */
+    public final String getText() {
+        return text;
+    }
+
+    /**
+     * Set field text.
+     *
+     * @param text the new field text
+     */
+    public final void setText(String text) {
+        this.text = text;
+        position = 0;
+        windowStart = 0;
+    }
+
+    /**
+     * Dispatch to the action function.
+     *
+     * @param enter if true, the user pressed Enter, else this was an update
+     * to the text.
+     */
+    protected void dispatch(final boolean enter) {
+        if (enter) {
+            if (enterAction != null) {
+                enterAction.DO();
+            }
+        } else {
+            if (updateAction != null) {
+                updateAction.DO();
+            }
+        }
+    }
+
+    /**
+     * Update the visible cursor position to match the location of position
+     * and windowStart.
+     */
+    protected void updateCursor() {
+        if ((position > getWidth()) && fixed) {
+            setCursorX(getWidth());
+        } else if ((position - windowStart == getWidth()) && !fixed) {
+            setCursorX(getWidth() - 1);
+        } else {
+            setCursorX(position - windowStart);
+        }
+    }
+
+    /**
+     * Normalize windowStart such that most of the field data if visible.
+     */
+    protected void normalizeWindowStart() {
+        if (fixed) {
+            // windowStart had better be zero, there is nothing to do here.
+            assert (windowStart == 0);
+            return;
+        }
+        windowStart = position - (getWidth() - 1);
+        if (windowStart < 0) {
+            windowStart = 0;
+        }
+
+        updateCursor();
+    }
+
     /**
      * Append char to the end of the field.
      *
     /**
      * Append char to the end of the field.
      *
index 05629cf8e3d3cecf4a10a7a222a247ce6cbf22e4..18d65f22ea047b83cb17b709df4e7a71cc5e173d 100644 (file)
@@ -34,6 +34,9 @@ import java.util.ResourceBundle;
 
 import jexer.bits.GraphicsChars;
 import jexer.event.TKeypressEvent;
 
 import jexer.bits.GraphicsChars;
 import jexer.event.TKeypressEvent;
+import jexer.ttree.TDirectoryTreeItem;
+import jexer.ttree.TTreeItem;
+import jexer.ttree.TTreeViewWidget;
 import static jexer.TKeypress.*;
 
 /**
 import static jexer.TKeypress.*;
 
 /**
@@ -59,6 +62,10 @@ public final class TFileOpenBox extends TWindow {
      */
     private static final ResourceBundle i18n = ResourceBundle.getBundle(TFileOpenBox.class.getName());
 
      */
     private static final ResourceBundle i18n = ResourceBundle.getBundle(TFileOpenBox.class.getName());
 
+    // ------------------------------------------------------------------------
+    // Constants --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * TFileOpenBox can be called for either Open or Save actions.
      */
     /**
      * TFileOpenBox can be called for either Open or Save actions.
      */
@@ -74,24 +81,19 @@ public final class TFileOpenBox extends TWindow {
         SAVE
     }
 
         SAVE
     }
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * String to return, or null if the user canceled.
      */
     private String filename = null;
 
     /**
      * String to return, or null if the user canceled.
      */
     private String filename = null;
 
-    /**
-     * Get the return string.
-     *
-     * @return the filename the user selected, or null if they canceled.
-     */
-    public String getFilename() {
-        return filename;
-    }
-
     /**
      * The left-side tree view pane.
      */
     /**
      * The left-side tree view pane.
      */
-    private TTreeView treeView;
+    private TTreeViewWidget treeView;
 
     /**
      * The data behind treeView.
 
     /**
      * The data behind treeView.
@@ -113,31 +115,9 @@ public final class TFileOpenBox extends TWindow {
      */
     private TButton openButton;
 
      */
     private TButton openButton;
 
-    /**
-     * See if there is a valid filename to return.  If the filename is a
-     * directory, then
-     *
-     * @param newFilename the filename to check and return
-     * @throws IOException of a java.io operation throws
-     */
-    private void checkFilename(final String newFilename) throws IOException {
-        File newFile = new File(newFilename);
-        if (newFile.exists()) {
-            if (newFile.isFile()) {
-                filename = newFilename;
-                getApplication().closeWindow(this);
-                return;
-            }
-            if (newFile.isDirectory()) {
-                treeViewRoot = new TDirectoryTreeItem(treeView, newFilename,
-                    true);
-                treeView.setTreeRoot(treeViewRoot, true);
-                treeView.reflowData();
-                openButton.setEnabled(false);
-                directoryList.setPath(newFilename);
-            }
-        }
-    }
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Public constructor.  The file open box will be centered on screen.
 
     /**
      * Public constructor.  The file open box will be centered on screen.
@@ -168,7 +148,7 @@ public final class TFileOpenBox extends TWindow {
         entryField.onKeypress(new TKeypressEvent(kbEnd));
 
         // Add directory treeView
         entryField.onKeypress(new TKeypressEvent(kbEnd));
 
         // Add directory treeView
-        treeView = addTreeView(1, 3, 30, getHeight() - 6,
+        treeView = addTreeViewWidget(1, 3, 30, getHeight() - 6,
             new TAction() {
                 public void DO() {
                     TTreeItem item = treeView.getSelected();
             new TAction() {
                 public void DO() {
                     TTreeItem item = treeView.getSelected();
@@ -176,7 +156,7 @@ public final class TFileOpenBox extends TWindow {
                     try {
                         directoryList.setPath(selectedDir.getCanonicalPath());
                         openButton.setEnabled(false);
                     try {
                         directoryList.setPath(selectedDir.getCanonicalPath());
                         openButton.setEnabled(false);
-                        activate(directoryList);
+                        activate(treeView);
                     } catch (IOException e) {
                         e.printStackTrace();
                     }
                     } catch (IOException e) {
                         e.printStackTrace();
                     }
@@ -250,15 +230,9 @@ public final class TFileOpenBox extends TWindow {
         getApplication().yield();
     }
 
         getApplication().yield();
     }
 
-    /**
-     * Draw me on screen.
-     */
-    @Override
-    public void draw() {
-        super.draw();
-        getScreen().vLineXY(33, 4, getHeight() - 6, GraphicsChars.WINDOW_SIDE,
-            getBackground());
-    }
+    // ------------------------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Handle keystrokes.
 
     /**
      * Handle keystrokes.
@@ -275,8 +249,86 @@ public final class TFileOpenBox extends TWindow {
             return;
         }
 
             return;
         }
 
+        if (treeView.isActive()) {
+            if ((keypress.equals(kbEnter))
+                || (keypress.equals(kbUp))
+                || (keypress.equals(kbDown))
+                || (keypress.equals(kbPgUp))
+                || (keypress.equals(kbPgDn))
+                || (keypress.equals(kbHome))
+                || (keypress.equals(kbEnd))
+            ) {
+                // Tree view will be changing, update the directory list.
+                super.onKeypress(keypress);
+
+                // This is the same action as treeView's enter.
+                TTreeItem item = treeView.getSelected();
+                File selectedDir = ((TDirectoryTreeItem) item).getFile();
+                try {
+                    directoryList.setPath(selectedDir.getCanonicalPath());
+                    openButton.setEnabled(false);
+                    activate(treeView);
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+                return;
+            }
+        }
+
         // Pass to my parent
         super.onKeypress(keypress);
     }
 
         // Pass to my parent
         super.onKeypress(keypress);
     }
 
+    // ------------------------------------------------------------------------
+    // TWidget ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Draw me on screen.
+     */
+    @Override
+    public void draw() {
+        super.draw();
+        getScreen().vLineXY(33, 4, getHeight() - 6, GraphicsChars.WINDOW_SIDE,
+            getBackground());
+    }
+
+    // ------------------------------------------------------------------------
+    // TFileOpenBox -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Get the return string.
+     *
+     * @return the filename the user selected, or null if they canceled.
+     */
+    public String getFilename() {
+        return filename;
+    }
+
+    /**
+     * See if there is a valid filename to return.  If the filename is a
+     * directory, then
+     *
+     * @param newFilename the filename to check and return
+     * @throws IOException of a java.io operation throws
+     */
+    private void checkFilename(final String newFilename) throws IOException {
+        File newFile = new File(newFilename);
+        if (newFile.exists()) {
+            if (newFile.isFile()) {
+                filename = newFilename;
+                getApplication().closeWindow(this);
+                return;
+            }
+            if (newFile.isDirectory()) {
+                treeViewRoot = new TDirectoryTreeItem(treeView,
+                    newFilename, true);
+                treeView.setTreeRoot(treeViewRoot, true);
+                openButton.setEnabled(false);
+                directoryList.setPath(newFilename);
+            }
+        }
+    }
+
 }
 }
index 9e9b372ec51854d8bf75e968746d65e58ef1c8c8..90133a46a0b89049664d687e109f404af8753ab2 100644 (file)
@@ -37,11 +37,206 @@ import jexer.event.TMouseEvent;
  */
 public final class THScroller extends TWidget {
 
  */
 public final class THScroller extends TWidget {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Value that corresponds to being on the left edge of the scroll bar.
      */
     private int leftValue = 0;
 
     /**
      * Value that corresponds to being on the left edge of the scroll bar.
      */
     private int leftValue = 0;
 
+    /**
+     * Value that corresponds to being on the right edge of the scroll bar.
+     */
+    private int rightValue = 100;
+
+    /**
+     * Current value of the scroll.
+     */
+    private int value = 0;
+
+    /**
+     * The increment for clicking on an arrow.
+     */
+    private int smallChange = 1;
+
+    /**
+     * The increment for clicking in the bar between the box and an arrow.
+     */
+    private int bigChange = 20;
+
+    /**
+     * When true, the user is dragging the scroll box.
+     */
+    private boolean inScroll = false;
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Public constructor.
+     *
+     * @param parent parent widget
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param width height of scroll bar
+     */
+    public THScroller(final TWidget parent, final int x, final int y,
+        final int width) {
+
+        // Set parent and window
+        super(parent, x, y, width, 1);
+    }
+
+    // ------------------------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Handle mouse button releases.
+     *
+     * @param mouse mouse button release event
+     */
+    @Override
+    public void onMouseUp(final TMouseEvent mouse) {
+
+        if (inScroll) {
+            inScroll = false;
+            return;
+        }
+
+        if (rightValue == leftValue) {
+            return;
+        }
+
+        if ((mouse.getX() == 0)
+            && (mouse.getY() == 0)
+        ) {
+            // Clicked on the left arrow
+            decrement();
+            return;
+        }
+
+        if ((mouse.getY() == 0)
+            && (mouse.getX() == getWidth() - 1)
+        ) {
+            // Clicked on the right arrow
+            increment();
+            return;
+        }
+
+        if ((mouse.getY() == 0)
+            && (mouse.getX() > 0)
+            && (mouse.getX() < boxPosition())
+        ) {
+            // Clicked between the left arrow and the box
+            value -= bigChange;
+            if (value < leftValue) {
+                value = leftValue;
+            }
+            return;
+        }
+
+        if ((mouse.getY() == 0)
+            && (mouse.getX() > boxPosition())
+            && (mouse.getX() < getWidth() - 1)
+        ) {
+            // Clicked between the box and the right arrow
+            value += bigChange;
+            if (value > rightValue) {
+                value = rightValue;
+            }
+            return;
+        }
+    }
+
+    /**
+     * Handle mouse movement events.
+     *
+     * @param mouse mouse motion event
+     */
+    @Override
+    public void onMouseMotion(final TMouseEvent mouse) {
+
+        if (rightValue == leftValue) {
+            inScroll = false;
+            return;
+        }
+
+        if ((mouse.isMouse1())
+            && (inScroll)
+            && (mouse.getX() > 0)
+            && (mouse.getX() < getWidth() - 1)
+        ) {
+            // Recompute value based on new box position
+            value = (rightValue - leftValue)
+                * (mouse.getX()) / (getWidth() - 3) + leftValue;
+            if (value > rightValue) {
+                value = rightValue;
+            }
+            if (value < leftValue) {
+                value = leftValue;
+            }
+            return;
+        }
+        inScroll = false;
+    }
+
+    /**
+     * Handle mouse button press events.
+     *
+     * @param mouse mouse button press event
+     */
+    @Override
+    public void onMouseDown(final TMouseEvent mouse) {
+        if (rightValue == leftValue) {
+            inScroll = false;
+            return;
+        }
+
+        if ((mouse.getY() == 0)
+            && (mouse.getX() == boxPosition())
+        ) {
+            inScroll = true;
+            return;
+        }
+
+    }
+
+    // ------------------------------------------------------------------------
+    // TWidget ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Draw a horizontal scroll bar.
+     */
+    @Override
+    public void draw() {
+        CellAttributes arrowColor = getTheme().getColor("tscroller.arrows");
+        CellAttributes barColor = getTheme().getColor("tscroller.bar");
+        getScreen().putCharXY(0, 0, GraphicsChars.CP437[0x11], arrowColor);
+        getScreen().putCharXY(getWidth() - 1, 0, GraphicsChars.CP437[0x10],
+            arrowColor);
+
+        // Place the box
+        if (rightValue > leftValue) {
+            getScreen().hLineXY(1, 0, getWidth() - 2, GraphicsChars.CP437[0xB1],
+                barColor);
+            getScreen().putCharXY(boxPosition(), 0, GraphicsChars.BOX,
+                arrowColor);
+        } else {
+            getScreen().hLineXY(1, 0, getWidth() - 2, GraphicsChars.HATCH,
+                barColor);
+        }
+
+    }
+
+    // ------------------------------------------------------------------------
+    // THScroller -------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Get the value that corresponds to being on the left edge of the scroll
      * bar.
     /**
      * Get the value that corresponds to being on the left edge of the scroll
      * bar.
@@ -62,11 +257,6 @@ public final class THScroller extends TWidget {
         this.leftValue = leftValue;
     }
 
         this.leftValue = leftValue;
     }
 
-    /**
-     * Value that corresponds to being on the right edge of the scroll bar.
-     */
-    private int rightValue = 100;
-
     /**
      * Get the value that corresponds to being on the right edge of the
      * scroll bar.
     /**
      * Get the value that corresponds to being on the right edge of the
      * scroll bar.
@@ -87,11 +277,6 @@ public final class THScroller extends TWidget {
         this.rightValue = rightValue;
     }
 
         this.rightValue = rightValue;
     }
 
-    /**
-     * Current value of the scroll.
-     */
-    private int value = 0;
-
     /**
      * Get current value of the scroll.
      *
     /**
      * Get current value of the scroll.
      *
@@ -110,11 +295,6 @@ public final class THScroller extends TWidget {
         this.value = value;
     }
 
         this.value = value;
     }
 
-    /**
-     * The increment for clicking on an arrow.
-     */
-    private int smallChange = 1;
-
     /**
      * Get the increment for clicking on an arrow.
      *
     /**
      * Get the increment for clicking on an arrow.
      *
@@ -133,11 +313,6 @@ public final class THScroller extends TWidget {
         this.smallChange = smallChange;
     }
 
         this.smallChange = smallChange;
     }
 
-    /**
-     * The increment for clicking in the bar between the box and an arrow.
-     */
-    private int bigChange = 20;
-
     /**
      * Set the increment for clicking in the bar between the box and an
      * arrow.
     /**
      * Set the increment for clicking in the bar between the box and an
      * arrow.
@@ -158,26 +333,6 @@ public final class THScroller extends TWidget {
         this.bigChange = bigChange;
     }
 
         this.bigChange = bigChange;
     }
 
-    /**
-     * When true, the user is dragging the scroll box.
-     */
-    private boolean inScroll = false;
-
-    /**
-     * Public constructor.
-     *
-     * @param parent parent widget
-     * @param x column relative to parent
-     * @param y row relative to parent
-     * @param width height of scroll bar
-     */
-    public THScroller(final TWidget parent, final int x, final int y,
-        final int width) {
-
-        // Set parent and window
-        super(parent, x, y, width, 1);
-    }
-
     /**
      * Compute the position of the scroll box (a.k.a. grip, thumb).
      *
     /**
      * Compute the position of the scroll box (a.k.a. grip, thumb).
      *
@@ -187,30 +342,6 @@ public final class THScroller extends TWidget {
         return (getWidth() - 3) * (value - leftValue) / (rightValue - leftValue) + 1;
     }
 
         return (getWidth() - 3) * (value - leftValue) / (rightValue - leftValue) + 1;
     }
 
-    /**
-     * Draw a horizontal scroll bar.
-     */
-    @Override
-    public void draw() {
-        CellAttributes arrowColor = getTheme().getColor("tscroller.arrows");
-        CellAttributes barColor = getTheme().getColor("tscroller.bar");
-        getScreen().putCharXY(0, 0, GraphicsChars.CP437[0x11], arrowColor);
-        getScreen().putCharXY(getWidth() - 1, 0, GraphicsChars.CP437[0x10],
-            arrowColor);
-
-        // Place the box
-        if (rightValue > leftValue) {
-            getScreen().hLineXY(1, 0, getWidth() - 2, GraphicsChars.CP437[0xB1],
-                barColor);
-            getScreen().putCharXY(boxPosition(), 0, GraphicsChars.BOX,
-                arrowColor);
-        } else {
-            getScreen().hLineXY(1, 0, getWidth() - 2, GraphicsChars.HATCH,
-                barColor);
-        }
-
-    }
-
     /**
      * Perform a small step change left.
      */
     /**
      * Perform a small step change left.
      */
@@ -277,109 +408,4 @@ public final class THScroller extends TWidget {
         value = rightValue;
     }
 
         value = rightValue;
     }
 
-    /**
-     * Handle mouse button releases.
-     *
-     * @param mouse mouse button release event
-     */
-    @Override
-    public void onMouseUp(final TMouseEvent mouse) {
-
-        if (inScroll) {
-            inScroll = false;
-            return;
-        }
-
-        if (rightValue == leftValue) {
-            return;
-        }
-
-        if ((mouse.getX() == 0)
-            && (mouse.getY() == 0)
-        ) {
-            // Clicked on the left arrow
-            decrement();
-            return;
-        }
-
-        if ((mouse.getY() == 0)
-            && (mouse.getX() == getWidth() - 1)
-        ) {
-            // Clicked on the right arrow
-            increment();
-            return;
-        }
-
-        if ((mouse.getY() == 0)
-            && (mouse.getX() > 0)
-            && (mouse.getX() < boxPosition())
-        ) {
-            // Clicked between the left arrow and the box
-            value -= bigChange;
-            if (value < leftValue) {
-                value = leftValue;
-            }
-            return;
-        }
-
-        if ((mouse.getY() == 0)
-            && (mouse.getX() > boxPosition())
-            && (mouse.getX() < getWidth() - 1)
-        ) {
-            // Clicked between the box and the right arrow
-            value += bigChange;
-            if (value > rightValue) {
-                value = rightValue;
-            }
-            return;
-        }
-    }
-
-    /**
-     * Handle mouse movement events.
-     *
-     * @param mouse mouse motion event
-     */
-    @Override
-    public void onMouseMotion(final TMouseEvent mouse) {
-
-        if (rightValue == leftValue) {
-            inScroll = false;
-            return;
-        }
-
-        if ((mouse.isMouse1())
-            && (inScroll)
-            && (mouse.getX() > 0)
-            && (mouse.getX() < getWidth() - 1)
-        ) {
-            // Recompute value based on new box position
-            value = (rightValue - leftValue)
-                * (mouse.getX()) / (getWidth() - 3) + leftValue;
-            return;
-        }
-        inScroll = false;
-    }
-
-    /**
-     * Handle mouse button press events.
-     *
-     * @param mouse mouse button press event
-     */
-    @Override
-    public void onMouseDown(final TMouseEvent mouse) {
-        if (rightValue == leftValue) {
-            inScroll = false;
-            return;
-        }
-
-        if ((mouse.getY() == 0)
-            && (mouse.getX() == boxPosition())
-        ) {
-            inScroll = true;
-            return;
-        }
-
-    }
-
 }
 }
index 872ebc45b50d44891267aaa265994e6b4538bf07..2470bdfba892196348016f85f0633489413352c5 100644 (file)
@@ -33,6 +33,10 @@ package jexer;
  */
 public final class TKeypress {
 
  */
 public final class TKeypress {
 
+    // ------------------------------------------------------------------------
+    // Constants --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     // Various special keystrokes
 
     /**
     // Various special keystrokes
 
     /**
@@ -170,386 +174,10 @@ public final class TKeypress {
      */
     public static final int ESC         = 43;
 
      */
     public static final int ESC         = 43;
 
-    /**
-     * If true, ch is meaningless, use keyCode instead.
-     */
-    private boolean isFunctionKey;
-
-    /**
-     * Getter for isFunctionKey.
-     *
-     * @return if true, ch is meaningless, use keyCode instead
-     */
-    public boolean isFnKey() {
-        return isFunctionKey;
-    }
-
-    /**
-     * Will be set to F1, F2, HOME, END, etc. if isKey is true.
-     */
-    private int keyCode;
-
-    /**
-     * Getter for function key code.
-     *
-     * @return function key code int value (only valid is isKey is true)
-     */
-    public int getKeyCode() {
-        return keyCode;
-    }
-
-    /**
-     * Keystroke modifier ALT.
-     */
-    private boolean alt;
-
-    /**
-     * Getter for ALT.
-     *
-     * @return alt value
-     */
-    public boolean isAlt() {
-        return alt;
-    }
-
-    /**
-     * Keystroke modifier CTRL.
-     */
-    private boolean ctrl;
-
-    /**
-     * Getter for CTRL.
-     *
-     * @return ctrl value
-     */
-    public boolean isCtrl() {
-        return ctrl;
-    }
-
-    /**
-     * Keystroke modifier SHIFT.
-     */
-    private boolean shift;
-
-    /**
-     * Getter for SHIFT.
-     *
-     * @return shift value
-     */
-    public boolean isShift() {
-        return shift;
-    }
-
-    /**
-     * The character received.
-     */
-    private char ch;
-
-    /**
-     * Getter for character.
-     *
-     * @return the character (only valid if isKey is false)
-     */
-    public char getChar() {
-        return ch;
-    }
-
-    /**
-     * Public constructor makes an immutable instance.
-     *
-     * @param isKey is true, this is a function key
-     * @param fnKey the function key code (only valid if isKey is true)
-     * @param ch the character (only valid if fnKey is false)
-     * @param alt if true, ALT was pressed with this keystroke
-     * @param ctrl if true, CTRL was pressed with this keystroke
-     * @param shift if true, SHIFT was pressed with this keystroke
-     */
-    public TKeypress(final boolean isKey, final int fnKey, final char ch,
-            final boolean alt, final boolean ctrl, final boolean shift) {
-
-        this.isFunctionKey = isKey;
-        this.keyCode       = fnKey;
-        this.ch            = ch;
-        this.alt           = alt;
-        this.ctrl          = ctrl;
-        this.shift         = shift;
-    }
-
-    /**
-     * Create a duplicate instance.
-     *
-     * @return duplicate intance
-     */
-    public TKeypress dup() {
-        TKeypress keypress = new TKeypress(isFunctionKey, keyCode, ch,
-            alt, ctrl, shift);
-        return keypress;
-    }
-
-    /**
-     * Comparison check.  All fields must match to return true.
-     *
-     * @param rhs another TKeypress instance
-     * @return true if all fields are equal
-     */
-    @Override
-    public boolean equals(final Object rhs) {
-        if (!(rhs instanceof TKeypress)) {
-            return false;
-        }
-
-        TKeypress that = (TKeypress) rhs;
-        return ((isFunctionKey == that.isFunctionKey)
-                && (keyCode == that.keyCode)
-                && (ch == that.ch)
-                && (alt == that.alt)
-                && (ctrl == that.ctrl)
-                && (shift == that.shift));
-    }
-
-    /**
-     * Comparison check, omitting the ctrl/alt/shift flags.
-     *
-     * @param rhs another TKeypress instance
-     * @return true if all fields (except for ctrl/alt/shift) are equal
-     */
-    public boolean equalsWithoutModifiers(final Object rhs) {
-        if (!(rhs instanceof TKeypress)) {
-            return false;
-        }
-
-        TKeypress that = (TKeypress) rhs;
-        return ((isFunctionKey == that.isFunctionKey)
-                && (keyCode == that.keyCode)
-                && (ch == that.ch));
-    }
 
 
-    /**
-     * Hashcode uses all fields in equals().
-     *
-     * @return the hash
-     */
-    @Override
-    public int hashCode() {
-        int A = 13;
-        int B = 23;
-        int hash = A;
-        hash = (B * hash) + (isFunctionKey ? 1 : 0);
-        hash = (B * hash) + keyCode;
-        hash = (B * hash) + ch;
-        hash = (B * hash) + (alt ? 1 : 0);
-        hash = (B * hash) + (ctrl ? 1 : 0);
-        hash = (B * hash) + (shift ? 1 : 0);
-        return hash;
-    }
-
-    /**
-     * Make human-readable description of this TKeypress.
-     *
-     * @return displayable String
-     */
-    @Override
-    public String toString() {
-        if (isFunctionKey) {
-            switch (keyCode) {
-            case F1:
-                return String.format("%s%s%sF1",
-                        ctrl ? "Ctrl+" : "",
-                                alt ? "Alt+" : "",
-                                        shift ? "Shift+" : "");
-            case F2:
-                return String.format("%s%s%sF2",
-                        ctrl ? "Ctrl+" : "",
-                                alt ? "Alt+" : "",
-                                        shift ? "Shift+" : "");
-            case F3:
-                return String.format("%s%s%sF3",
-                        ctrl ? "Ctrl+" : "",
-                                alt ? "Alt+" : "",
-                                        shift ? "Shift+" : "");
-            case F4:
-                return String.format("%s%s%sF4",
-                        ctrl ? "Ctrl+" : "",
-                                alt ? "Alt+" : "",
-                                        shift ? "Shift+" : "");
-            case F5:
-                return String.format("%s%s%sF5",
-                        ctrl ? "Ctrl+" : "",
-                                alt ? "Alt+" : "",
-                                        shift ? "Shift+" : "");
-            case F6:
-                return String.format("%s%s%sF6",
-                        ctrl ? "Ctrl+" : "",
-                                alt ? "Alt+" : "",
-                                        shift ? "Shift+" : "");
-            case F7:
-                return String.format("%s%s%sF7",
-                        ctrl ? "Ctrl+" : "",
-                                alt ? "Alt+" : "",
-                                        shift ? "Shift+" : "");
-            case F8:
-                return String.format("%s%s%sF8",
-                        ctrl ? "Ctrl+" : "",
-                                alt ? "Alt+" : "",
-                                        shift ? "Shift+" : "");
-            case F9:
-                return String.format("%s%s%sF9",
-                        ctrl ? "Ctrl+" : "",
-                                alt ? "Alt+" : "",
-                                        shift ? "Shift+" : "");
-            case F10:
-                return String.format("%s%s%sF10",
-                        ctrl ? "Ctrl+" : "",
-                                alt ? "Alt+" : "",
-                                        shift ? "Shift+" : "");
-            case F11:
-                return String.format("%s%s%sF11",
-                        ctrl ? "Ctrl+" : "",
-                                alt ? "Alt+" : "",
-                                        shift ? "Shift+" : "");
-            case F12:
-                return String.format("%s%s%sF12",
-                        ctrl ? "Ctrl+" : "",
-                                alt ? "Alt+" : "",
-                                        shift ? "Shift+" : "");
-            case HOME:
-                return String.format("%s%s%sHOME",
-                        ctrl ? "Ctrl+" : "",
-                                alt ? "Alt+" : "",
-                                        shift ? "Shift+" : "");
-            case END:
-                return String.format("%s%s%sEND",
-                        ctrl ? "Ctrl+" : "",
-                                alt ? "Alt+" : "",
-                                        shift ? "Shift+" : "");
-            case PGUP:
-                return String.format("%s%s%sPGUP",
-                        ctrl ? "Ctrl+" : "",
-                                alt ? "Alt+" : "",
-                                        shift ? "Shift+" : "");
-            case PGDN:
-                return String.format("%s%s%sPGDN",
-                        ctrl ? "Ctrl+" : "",
-                                alt ? "Alt+" : "",
-                                        shift ? "Shift+" : "");
-            case INS:
-                return String.format("%s%s%sINS",
-                        ctrl ? "Ctrl+" : "",
-                                alt ? "Alt+" : "",
-                                        shift ? "Shift+" : "");
-            case DEL:
-                return String.format("%s%s%sDEL",
-                        ctrl ? "Ctrl+" : "",
-                                alt ? "Alt+" : "",
-                                        shift ? "Shift+" : "");
-            case RIGHT:
-                return String.format("%s%s%sRIGHT",
-                        ctrl ? "Ctrl+" : "",
-                                alt ? "Alt+" : "",
-                                        shift ? "Shift+" : "");
-            case LEFT:
-                return String.format("%s%s%sLEFT",
-                        ctrl ? "Ctrl+" : "",
-                                alt ? "Alt+" : "",
-                                        shift ? "Shift+" : "");
-            case UP:
-                return String.format("%s%s%sUP",
-                        ctrl ? "Ctrl+" : "",
-                                alt ? "Alt+" : "",
-                                        shift ? "Shift+" : "");
-            case DOWN:
-                return String.format("%s%s%sDOWN",
-                        ctrl ? "Ctrl+" : "",
-                                alt ? "Alt+" : "",
-                                        shift ? "Shift+" : "");
-            case TAB:
-                return String.format("%s%s%sTAB",
-                        ctrl ? "Ctrl+" : "",
-                                alt ? "Alt+" : "",
-                                        shift ? "Shift+" : "");
-            case BTAB:
-                return String.format("%s%s%sBTAB",
-                        ctrl ? "Ctrl+" : "",
-                                alt ? "Alt+" : "",
-                                        shift ? "Shift+" : "");
-            case ENTER:
-                return String.format("%s%s%sENTER",
-                        ctrl ? "Ctrl+" : "",
-                                alt ? "Alt+" : "",
-                                        shift ? "Shift+" : "");
-            case ESC:
-                return String.format("%s%s%sESC",
-                        ctrl ? "Ctrl+" : "",
-                                alt ? "Alt+" : "",
-                                        shift ? "Shift+" : "");
-            default:
-                return String.format("--UNKNOWN--");
-            }
-        } else {
-            if (alt && !shift && !ctrl) {
-                // Alt-X
-                return String.format("Alt+%c", Character.toUpperCase(ch));
-            } else if (!alt && shift && !ctrl) {
-                // Shift-X
-                return String.format("%c", ch);
-            } else if (!alt && !shift && ctrl) {
-                // Ctrl-X
-                return String.format("Ctrl+%c", ch);
-            } else if (alt && shift && !ctrl) {
-                // Alt-Shift-X
-                return String.format("Alt+Shift+%c", ch);
-            } else if (!alt && shift && ctrl) {
-                // Ctrl-Shift-X
-                return String.format("Ctrl+Shift+%c", ch);
-            } else if (alt && !shift && ctrl) {
-                // Ctrl-Alt-X
-                return String.format("Ctrl+Alt+%c", Character.toUpperCase(ch));
-            } else if (alt && shift && ctrl) {
-                // Ctrl-Alt-Shift-X
-                return String.format("Ctrl+Alt+Shift+%c",
-                        Character.toUpperCase(ch));
-            } else {
-                // X
-                return String.format("%c", ch);
-            }
-        }
-    }
-
-    /**
-     * Convert a keypress to lowercase.  Function keys and alt/ctrl keys are
-     * not converted.
-     *
-     * @return a new instance with the key converted
-     */
-    public TKeypress toLowerCase() {
-        TKeypress newKey = new TKeypress(isFunctionKey, keyCode, ch, alt, ctrl,
-                shift);
-        if (!isFunctionKey && (ch >= 'A') && (ch <= 'Z') && !ctrl && !alt) {
-            newKey.shift = false;
-            newKey.ch += 32;
-        }
-        return newKey;
-    }
-
-    /**
-     * Convert a keypress to uppercase.  Function keys and alt/ctrl keys are
-     * not converted.
-     *
-     * @return a new instance with the key converted
-     */
-    public TKeypress toUpperCase() {
-        TKeypress newKey = new TKeypress(isFunctionKey, keyCode, ch, alt, ctrl,
-                shift);
-        if (!isFunctionKey && (ch >= 'a') && (ch <= 'z') && !ctrl && !alt) {
-            newKey.shift = true;
-            newKey.ch -= 32;
-        }
-        return newKey;
-    }
-
-    // Special "no-key" keypress, used to ignore undefined keystrokes
-    public static final TKeypress kbNoKey = new TKeypress(true,
-            TKeypress.NONE, ' ', false, false, false);
+    // Special "no-key" keypress, used to ignore undefined keystrokes
+    public static final TKeypress kbNoKey = new TKeypress(true,
+            TKeypress.NONE, ' ', false, false, false);
 
     // Normal keys
     public static final TKeypress kbF1 = new TKeypress(true,
 
     // Normal keys
     public static final TKeypress kbF1 = new TKeypress(true,
@@ -977,4 +605,393 @@ public final class TKeypress {
     public static final TKeypress kbBackspaceDel = new TKeypress(false,
             0, (char)0x7F, false, false, false);
 
     public static final TKeypress kbBackspaceDel = new TKeypress(false,
             0, (char)0x7F, false, false, false);
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * If true, ch is meaningless, use keyCode instead.
+     */
+    private boolean isFunctionKey;
+
+    /**
+     * Will be set to F1, F2, HOME, END, etc. if isKey is true.
+     */
+    private int keyCode;
+
+    /**
+     * Keystroke modifier ALT.
+     */
+    private boolean alt;
+
+    /**
+     * Keystroke modifier CTRL.
+     */
+    private boolean ctrl;
+
+    /**
+     * Keystroke modifier SHIFT.
+     */
+    private boolean shift;
+
+    /**
+     * The character received.
+     */
+    private char ch;
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Public constructor makes an immutable instance.
+     *
+     * @param isKey is true, this is a function key
+     * @param fnKey the function key code (only valid if isKey is true)
+     * @param ch the character (only valid if fnKey is false)
+     * @param alt if true, ALT was pressed with this keystroke
+     * @param ctrl if true, CTRL was pressed with this keystroke
+     * @param shift if true, SHIFT was pressed with this keystroke
+     */
+    public TKeypress(final boolean isKey, final int fnKey, final char ch,
+            final boolean alt, final boolean ctrl, final boolean shift) {
+
+        this.isFunctionKey = isKey;
+        this.keyCode       = fnKey;
+        this.ch            = ch;
+        this.alt           = alt;
+        this.ctrl          = ctrl;
+        this.shift         = shift;
+    }
+
+    // ------------------------------------------------------------------------
+    // TKeypress --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Getter for isFunctionKey.
+     *
+     * @return if true, ch is meaningless, use keyCode instead
+     */
+    public boolean isFnKey() {
+        return isFunctionKey;
+    }
+
+    /**
+     * Getter for function key code.
+     *
+     * @return function key code int value (only valid is isKey is true)
+     */
+    public int getKeyCode() {
+        return keyCode;
+    }
+
+    /**
+     * Getter for ALT.
+     *
+     * @return alt value
+     */
+    public boolean isAlt() {
+        return alt;
+    }
+
+    /**
+     * Getter for CTRL.
+     *
+     * @return ctrl value
+     */
+    public boolean isCtrl() {
+        return ctrl;
+    }
+
+    /**
+     * Getter for SHIFT.
+     *
+     * @return shift value
+     */
+    public boolean isShift() {
+        return shift;
+    }
+
+    /**
+     * Getter for character.
+     *
+     * @return the character (only valid if isKey is false)
+     */
+    public char getChar() {
+        return ch;
+    }
+
+    /**
+     * Create a duplicate instance.
+     *
+     * @return duplicate intance
+     */
+    public TKeypress dup() {
+        TKeypress keypress = new TKeypress(isFunctionKey, keyCode, ch,
+            alt, ctrl, shift);
+        return keypress;
+    }
+
+    /**
+     * Comparison check.  All fields must match to return true.
+     *
+     * @param rhs another TKeypress instance
+     * @return true if all fields are equal
+     */
+    @Override
+    public boolean equals(final Object rhs) {
+        if (!(rhs instanceof TKeypress)) {
+            return false;
+        }
+
+        TKeypress that = (TKeypress) rhs;
+        return ((isFunctionKey == that.isFunctionKey)
+                && (keyCode == that.keyCode)
+                && (ch == that.ch)
+                && (alt == that.alt)
+                && (ctrl == that.ctrl)
+                && (shift == that.shift));
+    }
+
+    /**
+     * Comparison check, omitting the ctrl/alt/shift flags.
+     *
+     * @param rhs another TKeypress instance
+     * @return true if all fields (except for ctrl/alt/shift) are equal
+     */
+    public boolean equalsWithoutModifiers(final Object rhs) {
+        if (!(rhs instanceof TKeypress)) {
+            return false;
+        }
+
+        TKeypress that = (TKeypress) rhs;
+        return ((isFunctionKey == that.isFunctionKey)
+                && (keyCode == that.keyCode)
+                && (ch == that.ch));
+    }
+
+    /**
+     * Hashcode uses all fields in equals().
+     *
+     * @return the hash
+     */
+    @Override
+    public int hashCode() {
+        int A = 13;
+        int B = 23;
+        int hash = A;
+        hash = (B * hash) + (isFunctionKey ? 1 : 0);
+        hash = (B * hash) + keyCode;
+        hash = (B * hash) + ch;
+        hash = (B * hash) + (alt ? 1 : 0);
+        hash = (B * hash) + (ctrl ? 1 : 0);
+        hash = (B * hash) + (shift ? 1 : 0);
+        return hash;
+    }
+
+    /**
+     * Make human-readable description of this TKeypress.
+     *
+     * @return displayable String
+     */
+    @Override
+    public String toString() {
+        if (isFunctionKey) {
+            switch (keyCode) {
+            case F1:
+                return String.format("%s%s%sF1",
+                        ctrl ? "Ctrl+" : "",
+                                alt ? "Alt+" : "",
+                                        shift ? "Shift+" : "");
+            case F2:
+                return String.format("%s%s%sF2",
+                        ctrl ? "Ctrl+" : "",
+                                alt ? "Alt+" : "",
+                                        shift ? "Shift+" : "");
+            case F3:
+                return String.format("%s%s%sF3",
+                        ctrl ? "Ctrl+" : "",
+                                alt ? "Alt+" : "",
+                                        shift ? "Shift+" : "");
+            case F4:
+                return String.format("%s%s%sF4",
+                        ctrl ? "Ctrl+" : "",
+                                alt ? "Alt+" : "",
+                                        shift ? "Shift+" : "");
+            case F5:
+                return String.format("%s%s%sF5",
+                        ctrl ? "Ctrl+" : "",
+                                alt ? "Alt+" : "",
+                                        shift ? "Shift+" : "");
+            case F6:
+                return String.format("%s%s%sF6",
+                        ctrl ? "Ctrl+" : "",
+                                alt ? "Alt+" : "",
+                                        shift ? "Shift+" : "");
+            case F7:
+                return String.format("%s%s%sF7",
+                        ctrl ? "Ctrl+" : "",
+                                alt ? "Alt+" : "",
+                                        shift ? "Shift+" : "");
+            case F8:
+                return String.format("%s%s%sF8",
+                        ctrl ? "Ctrl+" : "",
+                                alt ? "Alt+" : "",
+                                        shift ? "Shift+" : "");
+            case F9:
+                return String.format("%s%s%sF9",
+                        ctrl ? "Ctrl+" : "",
+                                alt ? "Alt+" : "",
+                                        shift ? "Shift+" : "");
+            case F10:
+                return String.format("%s%s%sF10",
+                        ctrl ? "Ctrl+" : "",
+                                alt ? "Alt+" : "",
+                                        shift ? "Shift+" : "");
+            case F11:
+                return String.format("%s%s%sF11",
+                        ctrl ? "Ctrl+" : "",
+                                alt ? "Alt+" : "",
+                                        shift ? "Shift+" : "");
+            case F12:
+                return String.format("%s%s%sF12",
+                        ctrl ? "Ctrl+" : "",
+                                alt ? "Alt+" : "",
+                                        shift ? "Shift+" : "");
+            case HOME:
+                return String.format("%s%s%sHOME",
+                        ctrl ? "Ctrl+" : "",
+                                alt ? "Alt+" : "",
+                                        shift ? "Shift+" : "");
+            case END:
+                return String.format("%s%s%sEND",
+                        ctrl ? "Ctrl+" : "",
+                                alt ? "Alt+" : "",
+                                        shift ? "Shift+" : "");
+            case PGUP:
+                return String.format("%s%s%sPGUP",
+                        ctrl ? "Ctrl+" : "",
+                                alt ? "Alt+" : "",
+                                        shift ? "Shift+" : "");
+            case PGDN:
+                return String.format("%s%s%sPGDN",
+                        ctrl ? "Ctrl+" : "",
+                                alt ? "Alt+" : "",
+                                        shift ? "Shift+" : "");
+            case INS:
+                return String.format("%s%s%sINS",
+                        ctrl ? "Ctrl+" : "",
+                                alt ? "Alt+" : "",
+                                        shift ? "Shift+" : "");
+            case DEL:
+                return String.format("%s%s%sDEL",
+                        ctrl ? "Ctrl+" : "",
+                                alt ? "Alt+" : "",
+                                        shift ? "Shift+" : "");
+            case RIGHT:
+                return String.format("%s%s%sRIGHT",
+                        ctrl ? "Ctrl+" : "",
+                                alt ? "Alt+" : "",
+                                        shift ? "Shift+" : "");
+            case LEFT:
+                return String.format("%s%s%sLEFT",
+                        ctrl ? "Ctrl+" : "",
+                                alt ? "Alt+" : "",
+                                        shift ? "Shift+" : "");
+            case UP:
+                return String.format("%s%s%sUP",
+                        ctrl ? "Ctrl+" : "",
+                                alt ? "Alt+" : "",
+                                        shift ? "Shift+" : "");
+            case DOWN:
+                return String.format("%s%s%sDOWN",
+                        ctrl ? "Ctrl+" : "",
+                                alt ? "Alt+" : "",
+                                        shift ? "Shift+" : "");
+            case TAB:
+                return String.format("%s%s%sTAB",
+                        ctrl ? "Ctrl+" : "",
+                                alt ? "Alt+" : "",
+                                        shift ? "Shift+" : "");
+            case BTAB:
+                return String.format("%s%s%sBTAB",
+                        ctrl ? "Ctrl+" : "",
+                                alt ? "Alt+" : "",
+                                        shift ? "Shift+" : "");
+            case ENTER:
+                return String.format("%s%s%sENTER",
+                        ctrl ? "Ctrl+" : "",
+                                alt ? "Alt+" : "",
+                                        shift ? "Shift+" : "");
+            case ESC:
+                return String.format("%s%s%sESC",
+                        ctrl ? "Ctrl+" : "",
+                                alt ? "Alt+" : "",
+                                        shift ? "Shift+" : "");
+            default:
+                return String.format("--UNKNOWN--");
+            }
+        } else {
+            if (alt && !shift && !ctrl) {
+                // Alt-X
+                return String.format("Alt+%c", Character.toUpperCase(ch));
+            } else if (!alt && shift && !ctrl) {
+                // Shift-X
+                return String.format("%c", ch);
+            } else if (!alt && !shift && ctrl) {
+                // Ctrl-X
+                return String.format("Ctrl+%c", ch);
+            } else if (alt && shift && !ctrl) {
+                // Alt-Shift-X
+                return String.format("Alt+Shift+%c", ch);
+            } else if (!alt && shift && ctrl) {
+                // Ctrl-Shift-X
+                return String.format("Ctrl+Shift+%c", ch);
+            } else if (alt && !shift && ctrl) {
+                // Ctrl-Alt-X
+                return String.format("Ctrl+Alt+%c", Character.toUpperCase(ch));
+            } else if (alt && shift && ctrl) {
+                // Ctrl-Alt-Shift-X
+                return String.format("Ctrl+Alt+Shift+%c",
+                        Character.toUpperCase(ch));
+            } else {
+                // X
+                return String.format("%c", ch);
+            }
+        }
+    }
+
+    /**
+     * Convert a keypress to lowercase.  Function keys and alt/ctrl keys are
+     * not converted.
+     *
+     * @return a new instance with the key converted
+     */
+    public TKeypress toLowerCase() {
+        TKeypress newKey = new TKeypress(isFunctionKey, keyCode, ch, alt, ctrl,
+                shift);
+        if (!isFunctionKey && (ch >= 'A') && (ch <= 'Z') && !ctrl && !alt) {
+            newKey.shift = false;
+            newKey.ch += 32;
+        }
+        return newKey;
+    }
+
+    /**
+     * Convert a keypress to uppercase.  Function keys and alt/ctrl keys are
+     * not converted.
+     *
+     * @return a new instance with the key converted
+     */
+    public TKeypress toUpperCase() {
+        TKeypress newKey = new TKeypress(isFunctionKey, keyCode, ch, alt, ctrl,
+                shift);
+        if (!isFunctionKey && (ch >= 'a') && (ch <= 'z') && !ctrl && !alt) {
+            newKey.shift = true;
+            newKey.ch -= 32;
+        }
+        return newKey;
+    }
+
 }
 }
index d5bb24cd3f180f2fe5985f6dce80ef56c4aff6c5..2eeea915793e0568db485f82cccc89efb9f2ae8a 100644 (file)
@@ -35,34 +35,24 @@ import jexer.bits.CellAttributes;
  */
 public final class TLabel extends TWidget {
 
  */
 public final class TLabel extends TWidget {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Label text.
      */
     private String label = "";
 
     /**
      * Label text.
      */
     private String label = "";
 
-    /**
-     * Get label text.
-     *
-     * @return label text
-     */
-    public String getLabel() {
-        return label;
-    }
-
-    /**
-     * Set label text.
-     *
-     * @param label new label text
-     */
-    public void setLabel(final String label) {
-        this.label = label;
-    }
-
     /**
      * Label color.
      */
     private String colorKey;
 
     /**
      * Label color.
      */
     private String colorKey;
 
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Public constructor, using the default "tlabel" for colorKey.
      *
     /**
      * Public constructor, using the default "tlabel" for colorKey.
      *
@@ -96,6 +86,10 @@ public final class TLabel extends TWidget {
         this.colorKey = colorKey;
     }
 
         this.colorKey = colorKey;
     }
 
+    // ------------------------------------------------------------------------
+    // TWidget ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Draw a static label.
      */
     /**
      * Draw a static label.
      */
@@ -110,4 +104,26 @@ public final class TLabel extends TWidget {
         getScreen().putStringXY(0, 0, label, color);
     }
 
         getScreen().putStringXY(0, 0, label, color);
     }
 
+    // ------------------------------------------------------------------------
+    // TLabel -----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Get label text.
+     *
+     * @return label text
+     */
+    public String getLabel() {
+        return label;
+    }
+
+    /**
+     * Set label text.
+     *
+     * @param label new label text
+     */
+    public void setLabel(final String label) {
+        this.label = label;
+    }
+
 }
 }
index e47d7512e48cd175e480298daa6488824c84335b..46c9307b95eba5cf9f68ed59d82ed4893ca7f1f2 100644 (file)
@@ -41,6 +41,10 @@ import static jexer.TKeypress.*;
  */
 public class TList extends TScrollableWidget {
 
  */
 public class TList extends TScrollableWidget {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * The list of strings to display.
      */
     /**
      * The list of strings to display.
      */
@@ -51,56 +55,6 @@ public class TList extends TScrollableWidget {
      */
     private int selectedString = -1;
 
      */
     private int selectedString = -1;
 
-    /**
-     * Get the selection index.
-     *
-     * @return -1 if nothing is selected, otherwise the index into the list
-     */
-    public final int getSelectedIndex() {
-        return selectedString;
-    }
-
-    /**
-     * Set the selected string index.
-     *
-     * @param index -1 to unselect, otherwise the index into the list
-     */
-    public final void setSelectedIndex(final int index) {
-        selectedString = index;
-    }
-
-    /**
-     * Get the selected string.
-     *
-     * @return the selected string, or null of nothing is selected yet
-     */
-    public final String getSelected() {
-        if ((selectedString >= 0) && (selectedString <= strings.size() - 1)) {
-            return strings.get(selectedString);
-        }
-        return null;
-    }
-
-    /**
-     * Get the maximum selection index value.
-     *
-     * @return -1 if the list is empty
-     */
-    public final int getMaxSelectedIndex() {
-        return strings.size() - 1;
-    }
-
-    /**
-     * Set the new list of strings to display.
-     *
-     * @param list new list of strings
-     */
-    public final void setList(final List<String> list) {
-        strings.clear();
-        strings.addAll(list);
-        reflowData();
-    }
-
     /**
      * Maximum width of a single line.
      */
     /**
      * Maximum width of a single line.
      */
@@ -116,55 +70,9 @@ public class TList extends TScrollableWidget {
      */
     private TAction moveAction = null;
 
      */
     private TAction moveAction = null;
 
-    /**
-     * Perform user selection action.
-     */
-    public void dispatchEnter() {
-        assert (selectedString >= 0);
-        assert (selectedString < strings.size());
-        if (enterAction != null) {
-            enterAction.DO();
-        }
-    }
-
-    /**
-     * Perform list movement action.
-     */
-    public void dispatchMove() {
-        assert (selectedString >= 0);
-        assert (selectedString < strings.size());
-        if (moveAction != null) {
-            moveAction.DO();
-        }
-    }
-
-    /**
-     * Resize for a new width/height.
-     */
-    @Override
-    public void reflowData() {
-
-        // Reset the lines
-        selectedString = -1;
-        maxLineWidth = 0;
-
-        for (int i = 0; i < strings.size(); i++) {
-            String line = strings.get(i);
-            if (line.length() > maxLineWidth) {
-                maxLineWidth = line.length();
-            }
-        }
-
-        setBottomValue(strings.size() - getHeight() + 1);
-        if (getBottomValue() < 0) {
-            setBottomValue(0);
-        }
-
-        setRightValue(maxLineWidth - getWidth() + 1);
-        if (getRightValue() < 0) {
-            setRightValue(0);
-        }
-    }
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Public constructor.
 
     /**
      * Public constructor.
@@ -241,48 +149,9 @@ public class TList extends TScrollableWidget {
         reflowData();
     }
 
         reflowData();
     }
 
-    /**
-     * Draw the files list.
-     */
-    @Override
-    public void draw() {
-        CellAttributes color = null;
-        int begin = getVerticalValue();
-        int topY = 0;
-        for (int i = begin; i < strings.size(); i++) {
-            String line = strings.get(i);
-            if (getHorizontalValue() < line.length()) {
-                line = line.substring(getHorizontalValue());
-            } else {
-                line = "";
-            }
-            if (i == selectedString) {
-                color = getTheme().getColor("tlist.selected");
-            } else if (isAbsoluteActive()) {
-                color = getTheme().getColor("tlist");
-            } else {
-                color = getTheme().getColor("tlist.inactive");
-            }
-            String formatString = "%-" + Integer.toString(getWidth() - 1) + "s";
-            getScreen().putStringXY(0, topY, String.format(formatString, line),
-                    color);
-            topY++;
-            if (topY >= getHeight() - 1) {
-                break;
-            }
-        }
-
-        if (isAbsoluteActive()) {
-            color = getTheme().getColor("tlist");
-        } else {
-            color = getTheme().getColor("tlist.inactive");
-        }
-
-        // Pad the rest with blank lines
-        for (int i = topY; i < getHeight() - 1; i++) {
-            getScreen().hLineXY(0, i, getWidth() - 1, ' ', color);
-        }
-    }
+    // ------------------------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Handle mouse press events.
 
     /**
      * Handle mouse press events.
@@ -427,4 +296,159 @@ public class TList extends TScrollableWidget {
         }
     }
 
         }
     }
 
+    // ------------------------------------------------------------------------
+    // TScrollableWidget ------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Resize for a new width/height.
+     */
+    @Override
+    public void reflowData() {
+
+        // Reset the lines
+        selectedString = -1;
+        maxLineWidth = 0;
+
+        for (int i = 0; i < strings.size(); i++) {
+            String line = strings.get(i);
+            if (line.length() > maxLineWidth) {
+                maxLineWidth = line.length();
+            }
+        }
+
+        setBottomValue(strings.size() - getHeight() + 1);
+        if (getBottomValue() < 0) {
+            setBottomValue(0);
+        }
+
+        setRightValue(maxLineWidth - getWidth() + 1);
+        if (getRightValue() < 0) {
+            setRightValue(0);
+        }
+    }
+
+    /**
+     * Draw the files list.
+     */
+    @Override
+    public void draw() {
+        CellAttributes color = null;
+        int begin = getVerticalValue();
+        int topY = 0;
+        for (int i = begin; i < strings.size(); i++) {
+            String line = strings.get(i);
+            if (getHorizontalValue() < line.length()) {
+                line = line.substring(getHorizontalValue());
+            } else {
+                line = "";
+            }
+            if (i == selectedString) {
+                if (isAbsoluteActive()) {
+                    color = getTheme().getColor("tlist.selected");
+                } else {
+                    color = getTheme().getColor("tlist.selected.inactive");
+                }
+            } else if (isAbsoluteActive()) {
+                color = getTheme().getColor("tlist");
+            } else {
+                color = getTheme().getColor("tlist.inactive");
+            }
+            String formatString = "%-" + Integer.toString(getWidth() - 1) + "s";
+            getScreen().putStringXY(0, topY, String.format(formatString, line),
+                    color);
+            topY++;
+            if (topY >= getHeight() - 1) {
+                break;
+            }
+        }
+
+        if (isAbsoluteActive()) {
+            color = getTheme().getColor("tlist");
+        } else {
+            color = getTheme().getColor("tlist.inactive");
+        }
+
+        // Pad the rest with blank lines
+        for (int i = topY; i < getHeight() - 1; i++) {
+            getScreen().hLineXY(0, i, getWidth() - 1, ' ', color);
+        }
+    }
+
+    // ------------------------------------------------------------------------
+    // TList ------------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Get the selection index.
+     *
+     * @return -1 if nothing is selected, otherwise the index into the list
+     */
+    public final int getSelectedIndex() {
+        return selectedString;
+    }
+
+    /**
+     * Set the selected string index.
+     *
+     * @param index -1 to unselect, otherwise the index into the list
+     */
+    public final void setSelectedIndex(final int index) {
+        selectedString = index;
+    }
+
+    /**
+     * Get the selected string.
+     *
+     * @return the selected string, or null of nothing is selected yet
+     */
+    public final String getSelected() {
+        if ((selectedString >= 0) && (selectedString <= strings.size() - 1)) {
+            return strings.get(selectedString);
+        }
+        return null;
+    }
+
+    /**
+     * Get the maximum selection index value.
+     *
+     * @return -1 if the list is empty
+     */
+    public final int getMaxSelectedIndex() {
+        return strings.size() - 1;
+    }
+
+    /**
+     * Set the new list of strings to display.
+     *
+     * @param list new list of strings
+     */
+    public final void setList(final List<String> list) {
+        strings.clear();
+        strings.addAll(list);
+        reflowData();
+    }
+
+    /**
+     * Perform user selection action.
+     */
+    public void dispatchEnter() {
+        assert (selectedString >= 0);
+        assert (selectedString < strings.size());
+        if (enterAction != null) {
+            enterAction.DO();
+        }
+    }
+
+    /**
+     * Perform list movement action.
+     */
+    public void dispatchMove() {
+        assert (selectedString >= 0);
+        assert (selectedString < strings.size());
+        if (moveAction != null) {
+            moveAction.DO();
+        }
+    }
+
 }
 }
index 49fd76ae8cc899c79b53f4abffe90fb5d5026f51..0677199ad497aa3286f242810275791a761c74b5 100644 (file)
@@ -36,74 +36,28 @@ import jexer.bits.GraphicsChars;
  */
 public final class TProgressBar extends TWidget {
 
  */
 public final class TProgressBar extends TWidget {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Value that corresponds to 0% progress.
      */
     private int minValue = 0;
 
     /**
      * Value that corresponds to 0% progress.
      */
     private int minValue = 0;
 
-    /**
-     * Get the value that corresponds to 0% progress.
-     *
-     * @return the value that corresponds to 0% progress
-     */
-    public int getMinValue() {
-        return minValue;
-    }
-
-    /**
-     * Set the value that corresponds to 0% progress.
-     *
-     * @param minValue the value that corresponds to 0% progress
-     */
-    public void setMinValue(final int minValue) {
-        this.minValue = minValue;
-    }
-
     /**
      * Value that corresponds to 100% progress.
      */
     private int maxValue = 100;
 
     /**
      * Value that corresponds to 100% progress.
      */
     private int maxValue = 100;
 
-    /**
-     * Get the value that corresponds to 100% progress.
-     *
-     * @return the value that corresponds to 100% progress
-     */
-    public int getMaxValue() {
-        return maxValue;
-    }
-
-    /**
-     * Set the value that corresponds to 100% progress.
-     *
-     * @param maxValue the value that corresponds to 100% progress
-     */
-    public void setMaxValue(final int maxValue) {
-        this.maxValue = maxValue;
-    }
-
     /**
      * Current value of the progress.
      */
     private int value = 0;
 
     /**
      * Current value of the progress.
      */
     private int value = 0;
 
-    /**
-     * Get the current value of the progress.
-     *
-     * @return the current value of the progress
-     */
-    public int getValue() {
-        return value;
-    }
-
-    /**
-     * Set the current value of the progress.
-     *
-     * @param value the current value of the progress
-     */
-    public void setValue(final int value) {
-        this.value = value;
-    }
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Public constructor.
 
     /**
      * Public constructor.
@@ -123,6 +77,15 @@ public final class TProgressBar extends TWidget {
         this.value = value;
     }
 
         this.value = value;
     }
 
+    // ------------------------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+
+    // ------------------------------------------------------------------------
+    // TWidget ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Draw a static progress bar.
      */
     /**
      * Draw a static progress bar.
      */
@@ -158,4 +121,62 @@ public final class TProgressBar extends TWidget {
             incompleteColor);
     }
 
             incompleteColor);
     }
 
+    // ------------------------------------------------------------------------
+    // TProgressBar -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Get the value that corresponds to 0% progress.
+     *
+     * @return the value that corresponds to 0% progress
+     */
+    public int getMinValue() {
+        return minValue;
+    }
+
+    /**
+     * Set the value that corresponds to 0% progress.
+     *
+     * @param minValue the value that corresponds to 0% progress
+     */
+    public void setMinValue(final int minValue) {
+        this.minValue = minValue;
+    }
+
+    /**
+     * Get the value that corresponds to 100% progress.
+     *
+     * @return the value that corresponds to 100% progress
+     */
+    public int getMaxValue() {
+        return maxValue;
+    }
+
+    /**
+     * Set the value that corresponds to 100% progress.
+     *
+     * @param maxValue the value that corresponds to 100% progress
+     */
+    public void setMaxValue(final int maxValue) {
+        this.maxValue = maxValue;
+    }
+
+    /**
+     * Get the current value of the progress.
+     *
+     * @return the current value of the progress
+     */
+    public int getValue() {
+        return value;
+    }
+
+    /**
+     * Set the current value of the progress.
+     *
+     * @param value the current value of the progress
+     */
+    public void setValue(final int value) {
+        this.value = value;
+    }
+
 }
 }
index 13a73ffedd73dddd1f3e2fc5bc91d45f4a8bc7f7..cdb56aa35860d3734235ce0ff35a3e942b81352b 100644 (file)
@@ -39,32 +39,15 @@ import static jexer.TKeypress.*;
  */
 public final class TRadioButton extends TWidget {
 
  */
 public final class TRadioButton extends TWidget {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * RadioButton state, true means selected.
      */
     private boolean selected = false;
 
     /**
      * RadioButton state, true means selected.
      */
     private boolean selected = false;
 
-    /**
-     * Get RadioButton state, true means selected.
-     *
-     * @return if true then this is the one button in the group that is
-     * selected
-     */
-    public boolean isSelected() {
-        return selected;
-    }
-
-    /**
-     * Set RadioButton state, true means selected.  Note package private
-     * access.
-     *
-     * @param selected if true then this is the one button in the group that
-     * is selected
-     */
-    void setSelected(final boolean selected) {
-        this.selected = selected;
-    }
-
     /**
      * Label for this radio button.
      */
     /**
      * Label for this radio button.
      */
@@ -76,15 +59,9 @@ public final class TRadioButton extends TWidget {
      */
     private int id;
 
      */
     private int id;
 
-    /**
-     * Get ID for this radio button.  Buttons start counting at 1 in the
-     * RadioGroup.
-     *
-     * @return the ID
-     */
-    public int getId() {
-        return id;
-    }
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Public constructor.
 
     /**
      * Public constructor.
@@ -108,6 +85,10 @@ public final class TRadioButton extends TWidget {
         setCursorX(1);
     }
 
         setCursorX(1);
     }
 
+    // ------------------------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Returns true if the mouse is currently on the radio button.
      *
     /**
      * Returns true if the mouse is currently on the radio button.
      *
@@ -124,30 +105,6 @@ public final class TRadioButton extends TWidget {
         return false;
     }
 
         return false;
     }
 
-    /**
-     * Draw a radio button with label.
-     */
-    @Override
-    public void draw() {
-        CellAttributes radioButtonColor;
-
-        if (isAbsoluteActive()) {
-            radioButtonColor = getTheme().getColor("tradiobutton.active");
-        } else {
-            radioButtonColor = getTheme().getColor("tradiobutton.inactive");
-        }
-
-        getScreen().putCharXY(0, 0, '(', radioButtonColor);
-        if (selected) {
-            getScreen().putCharXY(1, 0, GraphicsChars.CP437[0x07],
-                radioButtonColor);
-        } else {
-            getScreen().putCharXY(1, 0, ' ', radioButtonColor);
-        }
-        getScreen().putCharXY(2, 0, ')', radioButtonColor);
-        getScreen().putStringXY(4, 0, label, radioButtonColor);
-    }
-
     /**
      * Handle mouse button presses.
      *
     /**
      * Handle mouse button presses.
      *
@@ -184,4 +141,67 @@ public final class TRadioButton extends TWidget {
         super.onKeypress(keypress);
     }
 
         super.onKeypress(keypress);
     }
 
+    // ------------------------------------------------------------------------
+    // TWidget ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Draw a radio button with label.
+     */
+    @Override
+    public void draw() {
+        CellAttributes radioButtonColor;
+
+        if (isAbsoluteActive()) {
+            radioButtonColor = getTheme().getColor("tradiobutton.active");
+        } else {
+            radioButtonColor = getTheme().getColor("tradiobutton.inactive");
+        }
+
+        getScreen().putCharXY(0, 0, '(', radioButtonColor);
+        if (selected) {
+            getScreen().putCharXY(1, 0, GraphicsChars.CP437[0x07],
+                radioButtonColor);
+        } else {
+            getScreen().putCharXY(1, 0, ' ', radioButtonColor);
+        }
+        getScreen().putCharXY(2, 0, ')', radioButtonColor);
+        getScreen().putStringXY(4, 0, label, radioButtonColor);
+    }
+
+    // ------------------------------------------------------------------------
+    // TRadioButton -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Get RadioButton state, true means selected.
+     *
+     * @return if true then this is the one button in the group that is
+     * selected
+     */
+    public boolean isSelected() {
+        return selected;
+    }
+
+    /**
+     * Set RadioButton state, true means selected.  Note package private
+     * access.
+     *
+     * @param selected if true then this is the one button in the group that
+     * is selected
+     */
+    void setSelected(final boolean selected) {
+        this.selected = selected;
+    }
+
+    /**
+     * Get ID for this radio button.  Buttons start counting at 1 in the
+     * RadioGroup.
+     *
+     * @return the ID
+     */
+    public int getId() {
+        return id;
+    }
+
 }
 }
index 60b100a5154530e740fa0a9307ce2299ea9d57d6..975d285e4fa33082cf7415c7b345c9456a19729d 100644 (file)
@@ -29,6 +29,7 @@
 package jexer;
 
 import java.util.ArrayList;
 package jexer;
 
 import java.util.ArrayList;
+import java.util.List;
 
 import jexer.bits.CellAttributes;
 import jexer.bits.GraphicsChars;
 
 import jexer.bits.CellAttributes;
 import jexer.bits.GraphicsChars;
@@ -41,6 +42,25 @@ import jexer.event.TMouseEvent;
  */
 public final class TStatusBar extends TWidget {
 
  */
 public final class TStatusBar extends TWidget {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Remember mouse state.
+     */
+    private TMouseEvent mouse;
+
+    /**
+     * The text to display on the right side of the shortcut keys.
+     */
+    private String text = null;
+
+    /**
+     * The shortcut keys.
+     */
+    private List<TStatusBarKey> keys = new ArrayList<TStatusBarKey>();
+
     /**
      * A single shortcut key.
      */
     /**
      * A single shortcut key.
      */
@@ -98,48 +118,9 @@ public final class TStatusBar extends TWidget {
 
     }
 
 
     }
 
-    /**
-     * Remember mouse state.
-     */
-    private TMouseEvent mouse;
-
-    /**
-     * The text to display on the right side of the shortcut keys.
-     */
-    private String text = null;
-
-    /**
-     * The shortcut keys.
-     */
-    private ArrayList<TStatusBarKey> keys = new ArrayList<TStatusBarKey>();
-
-    /**
-     * Add a key to this status bar.
-     *
-     * @param key the key to trigger on
-     * @param cmd the command event to issue when key is pressed or this item
-     * is clicked
-     * @param label the label for this action
-     */
-    public void addShortcutKeypress(final TKeypress key, final TCommand cmd,
-        final String label) {
-
-        TStatusBarKey newKey = new TStatusBarKey(key, cmd, label);
-        if (keys.size() > 0) {
-            TStatusBarKey oldKey = keys.get(keys.size() - 1);
-            newKey.x = oldKey.x + oldKey.width();
-        }
-        keys.add(newKey);
-    }
-
-    /**
-     * Set the text to display on the right side of the shortcut keys.
-     *
-     * @param text the new text
-     */
-    public void setText(final String text) {
-        this.text = text;
-    }
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Public constructor.
 
     /**
      * Public constructor.
@@ -164,56 +145,9 @@ public final class TStatusBar extends TWidget {
         this(parent, "");
     }
 
         this(parent, "");
     }
 
-    /**
-     * Draw the bar.
-     */
-    @Override
-    public void draw() {
-        CellAttributes barColor = new CellAttributes();
-        barColor.setTo(getTheme().getColor("tstatusbar.text"));
-        CellAttributes keyColor = new CellAttributes();
-        keyColor.setTo(getTheme().getColor("tstatusbar.button"));
-        CellAttributes selectedColor = new CellAttributes();
-        selectedColor.setTo(getTheme().getColor("tstatusbar.selected"));
-
-        // Status bar is weird.  Its draw() method is called directly by
-        // TApplication after everything is drawn, and after
-        // Screen.resetClipping().  So at this point we are drawing in
-        // absolute coordinates, not relative to our TWindow.
-        int row = getScreen().getHeight() - 1;
-        int width = getScreen().getWidth();
-
-        getScreen().hLineXY(0, row, width, ' ', barColor);
-
-        int col = 0;
-        for (TStatusBarKey key: keys) {
-            String keyStr = key.key.toString();
-            if (key.selected) {
-                getScreen().putCharXY(col++, row, ' ', selectedColor);
-                getScreen().putStringXY(col, row, keyStr, selectedColor);
-                col += keyStr.length();
-                getScreen().putCharXY(col++, row, ' ', selectedColor);
-                getScreen().putStringXY(col, row, key.label, selectedColor);
-                col += key.label.length();
-                getScreen().putCharXY(col++, row, ' ', selectedColor);
-            } else {
-                getScreen().putCharXY(col++, row, ' ', barColor);
-                getScreen().putStringXY(col, row, keyStr, keyColor);
-                col += keyStr.length() + 1;
-                getScreen().putStringXY(col, row, key.label, barColor);
-                col += key.label.length();
-                getScreen().putCharXY(col++, row, ' ', barColor);
-            }
-        }
-        if (text.length() > 0) {
-            if (keys.size() > 0) {
-                getScreen().putCharXY(col++, row, GraphicsChars.VERTICAL_BAR,
-                    barColor);
-            }
-            getScreen().putCharXY(col++, row, ' ', barColor);
-            getScreen().putStringXY(col, row, text, barColor);
-        }
-    }
+    // ------------------------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Handle keypresses.
 
     /**
      * Handle keypresses.
@@ -302,4 +236,91 @@ public final class TStatusBar extends TWidget {
         }
     }
 
         }
     }
 
+    // ------------------------------------------------------------------------
+    // TWidget ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Draw the bar.
+     */
+    @Override
+    public void draw() {
+        CellAttributes barColor = new CellAttributes();
+        barColor.setTo(getTheme().getColor("tstatusbar.text"));
+        CellAttributes keyColor = new CellAttributes();
+        keyColor.setTo(getTheme().getColor("tstatusbar.button"));
+        CellAttributes selectedColor = new CellAttributes();
+        selectedColor.setTo(getTheme().getColor("tstatusbar.selected"));
+
+        // Status bar is weird.  Its draw() method is called directly by
+        // TApplication after everything is drawn, and after
+        // Screen.resetClipping().  So at this point we are drawing in
+        // absolute coordinates, not relative to our TWindow.
+        int row = getScreen().getHeight() - 1;
+        int width = getScreen().getWidth();
+
+        getScreen().hLineXY(0, row, width, ' ', barColor);
+
+        int col = 0;
+        for (TStatusBarKey key: keys) {
+            String keyStr = key.key.toString();
+            if (key.selected) {
+                getScreen().putCharXY(col++, row, ' ', selectedColor);
+                getScreen().putStringXY(col, row, keyStr, selectedColor);
+                col += keyStr.length();
+                getScreen().putCharXY(col++, row, ' ', selectedColor);
+                getScreen().putStringXY(col, row, key.label, selectedColor);
+                col += key.label.length();
+                getScreen().putCharXY(col++, row, ' ', selectedColor);
+            } else {
+                getScreen().putCharXY(col++, row, ' ', barColor);
+                getScreen().putStringXY(col, row, keyStr, keyColor);
+                col += keyStr.length() + 1;
+                getScreen().putStringXY(col, row, key.label, barColor);
+                col += key.label.length();
+                getScreen().putCharXY(col++, row, ' ', barColor);
+            }
+        }
+        if (text.length() > 0) {
+            if (keys.size() > 0) {
+                getScreen().putCharXY(col++, row, GraphicsChars.VERTICAL_BAR,
+                    barColor);
+            }
+            getScreen().putCharXY(col++, row, ' ', barColor);
+            getScreen().putStringXY(col, row, text, barColor);
+        }
+    }
+
+    // ------------------------------------------------------------------------
+    // TStatusBar -------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Add a key to this status bar.
+     *
+     * @param key the key to trigger on
+     * @param cmd the command event to issue when key is pressed or this item
+     * is clicked
+     * @param label the label for this action
+     */
+    public void addShortcutKeypress(final TKeypress key, final TCommand cmd,
+        final String label) {
+
+        TStatusBarKey newKey = new TStatusBarKey(key, cmd, label);
+        if (keys.size() > 0) {
+            TStatusBarKey oldKey = keys.get(keys.size() - 1);
+            newKey.x = oldKey.x + oldKey.width();
+        }
+        keys.add(newKey);
+    }
+
+    /**
+     * Set the text to display on the right side of the shortcut keys.
+     *
+     * @param text the new text
+     */
+    public void setText(final String text) {
+        this.text = text;
+    }
+
 }
 }
index c57e8846ace8b8ee160d250147333bd63723ab76..d0148f202a047b4b60f15dff444abd34e6150e28 100644 (file)
  */
 package jexer;
 
  */
 package jexer;
 
-import static jexer.TKeypress.kbDown;
-import static jexer.TKeypress.kbEnd;
-import static jexer.TKeypress.kbHome;
-import static jexer.TKeypress.kbLeft;
-import static jexer.TKeypress.kbPgDn;
-import static jexer.TKeypress.kbPgUp;
-import static jexer.TKeypress.kbRight;
-import static jexer.TKeypress.kbUp;
-
 import java.util.LinkedList;
 import java.util.List;
 
 import jexer.bits.CellAttributes;
 import jexer.event.TKeypressEvent;
 import jexer.event.TMouseEvent;
 import java.util.LinkedList;
 import java.util.List;
 
 import jexer.bits.CellAttributes;
 import jexer.event.TKeypressEvent;
 import jexer.event.TMouseEvent;
+import static jexer.TKeypress.*;
 
 /**
  * TText implements a simple scrollable text area. It reflows automatically on
 
 /**
  * TText implements a simple scrollable text area. It reflows automatically on
@@ -224,19 +216,19 @@ public final class TText extends TScrollableWidget {
         for (String p : paragraphs) {
             switch (justification) {
             case LEFT:
         for (String p : paragraphs) {
             switch (justification) {
             case LEFT:
-                lines.addAll(jexer.bits.StringJustifier.left(p,
+                lines.addAll(jexer.bits.StringUtils.left(p,
                         getWidth() - 1));
                 break;
             case CENTER:
                         getWidth() - 1));
                 break;
             case CENTER:
-                lines.addAll(jexer.bits.StringJustifier.center(p,
+                lines.addAll(jexer.bits.StringUtils.center(p,
                         getWidth() - 1));
                 break;
             case RIGHT:
                         getWidth() - 1));
                 break;
             case RIGHT:
-                lines.addAll(jexer.bits.StringJustifier.right(p,
+                lines.addAll(jexer.bits.StringUtils.right(p,
                         getWidth() - 1));
                 break;
             case FULL:
                         getWidth() - 1));
                 break;
             case FULL:
-                lines.addAll(jexer.bits.StringJustifier.full(p,
+                lines.addAll(jexer.bits.StringUtils.full(p,
                         getWidth() - 1));
                 break;
             }
                         getWidth() - 1));
                 break;
             }
index a86c132febbcfb31977ef6a73989e3846905d2ce..5d3dc134f9002bfb3c1e48a8eee1f1d0f02f9a2b 100644 (file)
@@ -35,6 +35,10 @@ import java.util.Date;
  */
 public final class TTimer {
 
  */
 public final class TTimer {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * If true, re-schedule after every tick.  Note package private access.
      */
     /**
      * If true, re-schedule after every tick.  Note package private access.
      */
@@ -50,6 +54,36 @@ public final class TTimer {
      */
     private Date nextTick;
 
      */
     private Date nextTick;
 
+    /**
+     * The action to perfom on a tick.
+     */
+    private TAction action;
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Package private constructor.
+     *
+     * @param duration number of milliseconds to wait between ticks
+     * @param recurring if true, re-schedule this timer after every tick
+     * @param action to perform on next tick
+     */
+    TTimer(final long duration, final boolean recurring, final TAction action) {
+
+        this.recurring = recurring;
+        this.duration  = duration;
+        this.action    = action;
+
+        Date now = new Date();
+        nextTick = new Date(now.getTime() + duration);
+    }
+
+    // ------------------------------------------------------------------------
+    // TTimer -----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Get the next time this timer needs to be ticked.  Note package private
      * access.
     /**
      * Get the next time this timer needs to be ticked.  Note package private
      * access.
@@ -69,11 +103,6 @@ public final class TTimer {
         this.recurring = recurring;
     }
 
         this.recurring = recurring;
     }
 
-    /**
-     * The action to perfom on a tick.
-     */
-    private TAction action;
-
     /**
      * Tick this timer.  Note package private access.
      */
     /**
      * Tick this timer.  Note package private access.
      */
@@ -88,21 +117,4 @@ public final class TTimer {
         }
     }
 
         }
     }
 
-    /**
-     * Package private constructor.
-     *
-     * @param duration number of milliseconds to wait between ticks
-     * @param recurring if true, re-schedule this timer after every tick
-     * @param action to perform on next tick
-     */
-    TTimer(final long duration, final boolean recurring, final TAction action) {
-
-        this.recurring = recurring;
-        this.duration  = duration;
-        this.action    = action;
-
-        Date now = new Date();
-        nextTick = new Date(now.getTime() + duration);
-    }
-
 }
 }
index 18178807fc10d14e86cfcdfc9c3e385de102c0cf..7a88739d69fb6e9bafcaf4da6f6fce64f20765b7 100644 (file)
@@ -37,11 +37,202 @@ import jexer.event.TMouseEvent;
  */
 public final class TVScroller extends TWidget {
 
  */
 public final class TVScroller extends TWidget {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Value that corresponds to being on the top edge of the scroll bar.
      */
     private int topValue = 0;
 
     /**
      * Value that corresponds to being on the top edge of the scroll bar.
      */
     private int topValue = 0;
 
+    /**
+     * Value that corresponds to being on the bottom edge of the scroll bar.
+     */
+    private int bottomValue = 100;
+
+    /**
+     * Current value of the scroll.
+     */
+    private int value = 0;
+
+    /**
+     * The increment for clicking on an arrow.
+     */
+    private int smallChange = 1;
+
+    /**
+     * The increment for clicking in the bar between the box and an arrow.
+     */
+    private int bigChange = 20;
+
+    /**
+     * When true, the user is dragging the scroll box.
+     */
+    private boolean inScroll = false;
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Public constructor.
+     *
+     * @param parent parent widget
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param height height of scroll bar
+     */
+    public TVScroller(final TWidget parent, final int x, final int y,
+        final int height) {
+
+        // Set parent and window
+        super(parent, x, y, 1, height);
+    }
+
+    // ------------------------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Handle mouse button releases.
+     *
+     * @param mouse mouse button release event
+     */
+    @Override
+    public void onMouseUp(final TMouseEvent mouse) {
+        if (bottomValue == topValue) {
+            return;
+        }
+
+        if (inScroll) {
+            inScroll = false;
+            return;
+        }
+
+        if ((mouse.getX() == 0)
+            && (mouse.getY() == 0)
+        ) {
+            // Clicked on the top arrow
+            decrement();
+            return;
+        }
+
+        if ((mouse.getX() == 0)
+            && (mouse.getY() == getHeight() - 1)
+        ) {
+            // Clicked on the bottom arrow
+            increment();
+            return;
+        }
+
+        if ((mouse.getX() == 0)
+            && (mouse.getY() > 0)
+            && (mouse.getY() < boxPosition())
+        ) {
+            // Clicked between the top arrow and the box
+            value -= bigChange;
+            if (value < topValue) {
+                value = topValue;
+            }
+            return;
+        }
+
+        if ((mouse.getX() == 0)
+            && (mouse.getY() > boxPosition())
+            && (mouse.getY() < getHeight() - 1)
+        ) {
+            // Clicked between the box and the bottom arrow
+            value += bigChange;
+            if (value > bottomValue) {
+                value = bottomValue;
+            }
+            return;
+        }
+    }
+
+    /**
+     * Handle mouse movement events.
+     *
+     * @param mouse mouse motion event
+     */
+    @Override
+    public void onMouseMotion(final TMouseEvent mouse) {
+        if (bottomValue == topValue) {
+            return;
+        }
+
+        if ((mouse.isMouse1())
+            && (inScroll)
+            && (mouse.getY() > 0)
+            && (mouse.getY() < getHeight() - 1)
+        ) {
+            // Recompute value based on new box position
+            value = (bottomValue - topValue)
+                * (mouse.getY()) / (getHeight() - 3) + topValue;
+            if (value > bottomValue) {
+                value = bottomValue;
+            }
+            if (value < topValue) {
+                value = topValue;
+            }
+            return;
+        }
+
+        inScroll = false;
+    }
+
+    /**
+     * Handle mouse press events.
+     *
+     * @param mouse mouse button press event
+     */
+    @Override
+    public void onMouseDown(final TMouseEvent mouse) {
+        if (bottomValue == topValue) {
+            return;
+        }
+
+        if ((mouse.getX() == 0)
+            && (mouse.getY() == boxPosition())
+        ) {
+            inScroll = true;
+            return;
+        }
+    }
+
+    // ------------------------------------------------------------------------
+    // TWidget ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Draw a vertical scroll bar.
+     */
+    @Override
+    public void draw() {
+        CellAttributes arrowColor = getTheme().getColor("tscroller.arrows");
+        CellAttributes barColor = getTheme().getColor("tscroller.bar");
+        getScreen().putCharXY(0, 0, GraphicsChars.CP437[0x1E], arrowColor);
+        getScreen().putCharXY(0, getHeight() - 1, GraphicsChars.CP437[0x1F],
+            arrowColor);
+
+        // Place the box
+        if (bottomValue > topValue) {
+            getScreen().vLineXY(0, 1, getHeight() - 2,
+                GraphicsChars.CP437[0xB1], barColor);
+            getScreen().putCharXY(0, boxPosition(), GraphicsChars.BOX,
+                arrowColor);
+        } else {
+            getScreen().vLineXY(0, 1, getHeight() - 2, GraphicsChars.HATCH,
+                barColor);
+        }
+
+    }
+
+    // ------------------------------------------------------------------------
+    // TVScroller -------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Get the value that corresponds to being on the top edge of the scroll
      * bar.
     /**
      * Get the value that corresponds to being on the top edge of the scroll
      * bar.
@@ -62,11 +253,6 @@ public final class TVScroller extends TWidget {
         this.topValue = topValue;
     }
 
         this.topValue = topValue;
     }
 
-    /**
-     * Value that corresponds to being on the bottom edge of the scroll bar.
-     */
-    private int bottomValue = 100;
-
     /**
      * Get the value that corresponds to being on the bottom edge of the
      * scroll bar.
     /**
      * Get the value that corresponds to being on the bottom edge of the
      * scroll bar.
@@ -87,11 +273,6 @@ public final class TVScroller extends TWidget {
         this.bottomValue = bottomValue;
     }
 
         this.bottomValue = bottomValue;
     }
 
-    /**
-     * Current value of the scroll.
-     */
-    private int value = 0;
-
     /**
      * Get current value of the scroll.
      *
     /**
      * Get current value of the scroll.
      *
@@ -110,11 +291,6 @@ public final class TVScroller extends TWidget {
         this.value = value;
     }
 
         this.value = value;
     }
 
-    /**
-     * The increment for clicking on an arrow.
-     */
-    private int smallChange = 1;
-
     /**
      * Get the increment for clicking on an arrow.
      *
     /**
      * Get the increment for clicking on an arrow.
      *
@@ -133,11 +309,6 @@ public final class TVScroller extends TWidget {
         this.smallChange = smallChange;
     }
 
         this.smallChange = smallChange;
     }
 
-    /**
-     * The increment for clicking in the bar between the box and an arrow.
-     */
-    private int bigChange = 20;
-
     /**
      * Set the increment for clicking in the bar between the box and an
      * arrow.
     /**
      * Set the increment for clicking in the bar between the box and an
      * arrow.
@@ -158,26 +329,6 @@ public final class TVScroller extends TWidget {
         this.bigChange = bigChange;
     }
 
         this.bigChange = bigChange;
     }
 
-    /**
-     * When true, the user is dragging the scroll box.
-     */
-    private boolean inScroll = false;
-
-    /**
-     * Public constructor.
-     *
-     * @param parent parent widget
-     * @param x column relative to parent
-     * @param y row relative to parent
-     * @param height height of scroll bar
-     */
-    public TVScroller(final TWidget parent, final int x, final int y,
-        final int height) {
-
-        // Set parent and window
-        super(parent, x, y, 1, height);
-    }
-
     /**
      * Compute the position of the scroll box (a.k.a. grip, thumb).
      *
     /**
      * Compute the position of the scroll box (a.k.a. grip, thumb).
      *
@@ -187,30 +338,6 @@ public final class TVScroller extends TWidget {
         return (getHeight() - 3) * (value - topValue) / (bottomValue - topValue) + 1;
     }
 
         return (getHeight() - 3) * (value - topValue) / (bottomValue - topValue) + 1;
     }
 
-    /**
-     * Draw a vertical scroll bar.
-     */
-    @Override
-    public void draw() {
-        CellAttributes arrowColor = getTheme().getColor("tscroller.arrows");
-        CellAttributes barColor = getTheme().getColor("tscroller.bar");
-        getScreen().putCharXY(0, 0, GraphicsChars.CP437[0x1E], arrowColor);
-        getScreen().putCharXY(0, getHeight() - 1, GraphicsChars.CP437[0x1F],
-            arrowColor);
-
-        // Place the box
-        if (bottomValue > topValue) {
-            getScreen().vLineXY(0, 1, getHeight() - 2,
-                GraphicsChars.CP437[0xB1], barColor);
-            getScreen().putCharXY(0, boxPosition(), GraphicsChars.BOX,
-                arrowColor);
-        } else {
-            getScreen().vLineXY(0, 1, getHeight() - 2, GraphicsChars.HATCH,
-                barColor);
-        }
-
-    }
-
     /**
      * Perform a small step change up.
      */
     /**
      * Perform a small step change up.
      */
@@ -277,105 +404,4 @@ public final class TVScroller extends TWidget {
         value = bottomValue;
     }
 
         value = bottomValue;
     }
 
-    /**
-     * Handle mouse button releases.
-     *
-     * @param mouse mouse button release event
-     */
-    @Override
-    public void onMouseUp(final TMouseEvent mouse) {
-        if (bottomValue == topValue) {
-            return;
-        }
-
-        if (inScroll) {
-            inScroll = false;
-            return;
-        }
-
-        if ((mouse.getX() == 0)
-            && (mouse.getY() == 0)
-        ) {
-            // Clicked on the top arrow
-            decrement();
-            return;
-        }
-
-        if ((mouse.getX() == 0)
-            && (mouse.getY() == getHeight() - 1)
-        ) {
-            // Clicked on the bottom arrow
-            increment();
-            return;
-        }
-
-        if ((mouse.getX() == 0)
-            && (mouse.getY() > 0)
-            && (mouse.getY() < boxPosition())
-        ) {
-            // Clicked between the top arrow and the box
-            value -= bigChange;
-            if (value < topValue) {
-                value = topValue;
-            }
-            return;
-        }
-
-        if ((mouse.getX() == 0)
-            && (mouse.getY() > boxPosition())
-            && (mouse.getY() < getHeight() - 1)
-        ) {
-            // Clicked between the box and the bottom arrow
-            value += bigChange;
-            if (value > bottomValue) {
-                value = bottomValue;
-            }
-            return;
-        }
-    }
-
-    /**
-     * Handle mouse movement events.
-     *
-     * @param mouse mouse motion event
-     */
-    @Override
-    public void onMouseMotion(final TMouseEvent mouse) {
-        if (bottomValue == topValue) {
-            return;
-        }
-
-        if ((mouse.isMouse1())
-            && (inScroll)
-            && (mouse.getY() > 0)
-            && (mouse.getY() < getHeight() - 1)
-        ) {
-            // Recompute value based on new box position
-            value = (bottomValue - topValue)
-                * (mouse.getY()) / (getHeight() - 3) + topValue;
-            return;
-        }
-
-        inScroll = false;
-    }
-
-    /**
-     * Handle mouse press events.
-     *
-     * @param mouse mouse button press event
-     */
-    @Override
-    public void onMouseDown(final TMouseEvent mouse) {
-        if (bottomValue == topValue) {
-            return;
-        }
-
-        if ((mouse.getX() == 0)
-            && (mouse.getY() == boxPosition())
-        ) {
-            inScroll = true;
-            return;
-        }
-    }
-
 }
 }
index 169e5576be85d09a4aedd865ecc680a84d45e2f9..7d2ea3572b3c7f582b052602528326e5cbc0eabe 100644 (file)
@@ -41,6 +41,9 @@ import jexer.event.TMenuEvent;
 import jexer.event.TMouseEvent;
 import jexer.event.TResizeEvent;
 import jexer.menu.TMenu;
 import jexer.event.TMouseEvent;
 import jexer.event.TResizeEvent;
 import jexer.menu.TMenu;
+import jexer.ttree.TTreeItem;
+import jexer.ttree.TTreeView;
+import jexer.ttree.TTreeViewWidget;
 import static jexer.TKeypress.*;
 
 /**
 import static jexer.TKeypress.*;
 
 /**
@@ -50,7 +53,7 @@ import static jexer.TKeypress.*;
 public abstract class TWidget implements Comparable<TWidget> {
 
     // ------------------------------------------------------------------------
 public abstract class TWidget implements Comparable<TWidget> {
 
     // ------------------------------------------------------------------------
-    // Common widget attributes -----------------------------------------------
+    // Variables --------------------------------------------------------------
     // ------------------------------------------------------------------------
 
     /**
     // ------------------------------------------------------------------------
 
     /**
@@ -60,29 +63,11 @@ public abstract class TWidget implements Comparable<TWidget> {
      */
     private TWidget parent = null;
 
      */
     private TWidget parent = null;
 
-    /**
-     * Get parent widget.
-     *
-     * @return parent widget
-     */
-    public final TWidget getParent() {
-        return parent;
-    }
-
     /**
      * Child widgets that this widget contains.
      */
     private List<TWidget> children;
 
     /**
      * 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.
      */
     /**
      * The currently active child widget that will receive keypress events.
      */
@@ -93,1024 +78,1058 @@ public abstract class TWidget implements Comparable<TWidget> {
      */
     private boolean active = false;
 
      */
     private boolean active = false;
 
-    /**
-     * Get active flag.
-     *
-     * @return if true, this widget will receive events
-     */
-    public final boolean isActive() {
-        return active;
-    }
-
-    /**
-     * Set active flag.
-     *
-     * @param active if true, this widget will receive events
-     */
-    public final void setActive(final boolean active) {
-        this.active = active;
-    }
-
     /**
      * The window that this widget draws to.
      */
     private TWindow window = null;
 
     /**
      * The window that this widget draws to.
      */
     private TWindow window = null;
 
-    /**
-     * Get the window this widget is on.
-     *
-     * @return the window
-     */
-    public final TWindow getWindow() {
-        return window;
-    }
-
     /**
      * Absolute X position of the top-left corner.
      */
     private int x = 0;
 
     /**
     /**
      * Absolute X position of the top-left corner.
      */
     private int x = 0;
 
     /**
-     * Get X position.
-     *
-     * @return absolute X position of the top-left corner
+     * Absolute Y position of the top-left corner.
      */
      */
-    public final int getX() {
-        return x;
-    }
+    private int y = 0;
 
     /**
 
     /**
-     * Set X position.
-     *
-     * @param x absolute X position of the top-left corner
+     * Width.
      */
      */
-    public final void setX(final int x) {
-        this.x = x;
-    }
+    private int width = 0;
 
     /**
 
     /**
-     * Absolute Y position of the top-left corner.
+     * Height.
      */
      */
-    private int y = 0;
+    private int height = 0;
 
     /**
 
     /**
-     * Get Y position.
-     *
-     * @return absolute Y position of the top-left corner
+     * My tab order inside a window or containing widget.
      */
      */
-    public final int getY() {
-        return y;
-    }
+    private int tabOrder = 0;
 
     /**
 
     /**
-     * Set Y position.
-     *
-     * @param y absolute Y position of the top-left corner
+     * If true, this widget can be tabbed to or receive events.
      */
      */
-    public final void setY(final int y) {
-        this.y = y;
-    }
+    private boolean enabled = true;
 
     /**
 
     /**
-     * Width.
+     * If true, this widget has a cursor.
      */
      */
-    private int width = 0;
+    private boolean cursorVisible = false;
 
     /**
 
     /**
-     * Get the width.
-     *
-     * @return widget width
+     * Cursor column position in relative coordinates.
      */
      */
-    public final int getWidth() {
-        return this.width;
-    }
+    private int cursorX = 0;
 
     /**
 
     /**
-     * Change the width.
-     *
-     * @param width new widget width
+     * Cursor row position in relative coordinates.
      */
      */
-    public final void setWidth(final int width) {
-        this.width = width;
-    }
+    private int cursorY = 0;
 
 
-    /**
-     * Height.
-     */
-    private int height = 0;
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
 
     /**
-     * Get the height.
-     *
-     * @return widget height
+     * Default constructor for subclasses.
      */
      */
-    public final int getHeight() {
-        return this.height;
+    protected TWidget() {
+        children = new ArrayList<TWidget>();
     }
 
     /**
     }
 
     /**
-     * Change the height.
+     * Protected constructor.
      *
      *
-     * @param height new widget height
+     * @param parent parent widget
      */
      */
-    public final void setHeight(final int height) {
-        this.height = height;
+    protected TWidget(final TWidget parent) {
+        this(parent, true);
     }
 
     /**
     }
 
     /**
-     * Change the dimensions.
+     * Protected constructor.
      *
      *
-     * @param x absolute X position of the top-left corner
-     * @param y absolute Y position of the top-left corner
-     * @param width new widget width
-     * @param height new widget height
+     * @param parent parent widget
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param width width of widget
+     * @param height height of widget
      */
      */
-    public final void setDimensions(final int x, final int y, final int width,
-        final int height) {
+    protected TWidget(final TWidget parent, final int x, final int y,
+        final int width, final int height) {
 
 
-        setX(x);
-        setY(y);
-        setWidth(width);
-        setHeight(height);
+        this(parent, true, x, y, width, height);
     }
 
     /**
     }
 
     /**
-     * My tab order inside a window or containing widget.
-     */
-    private int tabOrder = 0;
-
-    /**
-     * If true, this widget can be tabbed to or receive events.
-     */
-    private boolean enabled = true;
-
-    /**
-     * Get enabled flag.
+     * Protected constructor used by subclasses that are disabled by default.
      *
      *
-     * @return if true, this widget can be tabbed to or receive events
+     * @param parent parent widget
+     * @param enabled if true assume enabled
      */
      */
-    public final boolean isEnabled() {
-        return enabled;
+    protected TWidget(final TWidget parent, final boolean enabled) {
+        this.enabled = enabled;
+        this.parent = parent;
+        this.window = parent.window;
+        children = new ArrayList<TWidget>();
+
+        // Do not add TStatusBars, they are drawn by TApplication
+        if (this instanceof TStatusBar) {
+        } else {
+            parent.addChild(this);
+        }
     }
 
     /**
     }
 
     /**
-     * Set enabled flag.
+     * Protected constructor used by subclasses that are disabled by default.
      *
      *
-     * @param enabled if true, this widget can be tabbed to or receive events
+     * @param parent parent widget
+     * @param enabled if true assume enabled
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param width width of widget
+     * @param height height of widget
      */
      */
-    public final void setEnabled(final boolean enabled) {
+    protected TWidget(final TWidget parent, final boolean enabled,
+        final int x, final int y, final int width, final int height) {
+
         this.enabled = enabled;
         this.enabled = enabled;
-        if (!enabled) {
-            active = false;
-            // See if there are any active siblings to switch to
-            boolean foundSibling = false;
-            if (parent != null) {
-                for (TWidget w: parent.children) {
-                    if ((w.enabled)
-                        && !(this instanceof THScroller)
-                        && !(this instanceof TVScroller)
-                    ) {
-                        parent.activate(w);
-                        foundSibling = true;
-                        break;
-                    }
-                }
-                if (!foundSibling) {
-                    parent.activeChild = null;
-                }
-            }
+        this.parent = parent;
+        this.window = parent.window;
+        children = new ArrayList<TWidget>();
+
+        // Do not add TStatusBars, they are drawn by TApplication
+        if (this instanceof TStatusBar) {
+        } else {
+            parent.addChild(this);
         }
         }
-    }
 
 
-    /**
-     * If true, this widget has a cursor.
-     */
-    private boolean cursorVisible = false;
+        this.x = x;
+        this.y = y;
+        this.width = width;
+        this.height = height;
+    }
 
     /**
 
     /**
-     * Set visible cursor flag.
+     * Backdoor access for TWindow's constructor.  ONLY TWindow USES THIS.
      *
      *
-     * @param cursorVisible if true, this widget has a cursor
+     * @param window the top-level window
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param width width of window
+     * @param height height of window
      */
      */
-    public final void setCursorVisible(final boolean cursorVisible) {
-        this.cursorVisible = cursorVisible;
+    protected final void setupForTWindow(final TWindow window,
+        final int x, final int y, final int width, final int height) {
+
+        this.parent = window;
+        this.window = window;
+        this.x      = x;
+        this.y      = y;
+        this.width  = width;
+        this.height = height;
     }
 
     }
 
+    // ------------------------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
     /**
-     * See if this widget has a visible cursor.
+     * Check if a mouse press/release event coordinate is contained in this
+     * widget.
      *
      *
-     * @return if true, this widget has a visible cursor
+     * @param mouse a mouse-based event
+     * @return whether or not a mouse click would be sent to this widget
      */
      */
-    public final boolean isCursorVisible() {
-        // If cursor is out of my bounds, it is not visible.
-        if ((cursorX >= width)
-            || (cursorX < 0)
-            || (cursorY >= height)
-            || (cursorY < 0)
-        ) {
+    public final boolean mouseWouldHit(final TMouseEvent mouse) {
+
+        if (!enabled) {
             return false;
         }
 
             return false;
         }
 
-        // If cursor is out of my window's bounds, it is not visible.
-        if ((getCursorAbsoluteX() >= window.getAbsoluteX()
-                + window.getWidth() - 1)
-            || (getCursorAbsoluteX() < 0)
-            || (getCursorAbsoluteY() >= window.getAbsoluteY()
-                + window.getHeight() - 1)
-            || (getCursorAbsoluteY() < 0)
+        if ((this instanceof TTreeItem)
+            && ((y < 0) || (y > parent.getHeight() - 1))
         ) {
             return false;
         }
         ) {
             return false;
         }
-        return cursorVisible;
-    }
 
 
-    /**
-     * Cursor column position in relative coordinates.
-     */
-    private int cursorX = 0;
+        if ((mouse.getAbsoluteX() >= getAbsoluteX())
+            && (mouse.getAbsoluteX() <  getAbsoluteX() + width)
+            && (mouse.getAbsoluteY() >= getAbsoluteY())
+            && (mouse.getAbsoluteY() <  getAbsoluteY() + height)
+        ) {
+            return true;
+        }
+        return false;
+    }
 
     /**
 
     /**
-     * Get cursor X value.
+     * Method that subclasses can override to handle keystrokes.
      *
      *
-     * @return cursor column position in relative coordinates
+     * @param keypress keystroke event
      */
      */
-    public final int getCursorX() {
-        return cursorX;
+    public void onKeypress(final TKeypressEvent keypress) {
+
+        if ((children.size() == 0)
+            || (this instanceof TTreeView)
+            || (this instanceof TText)
+        ) {
+
+            // Defaults:
+            //   tab / shift-tab - switch to next/previous widget
+            //   left-arrow or up-arrow: same as shift-tab
+            if ((keypress.equals(kbTab))
+                || (keypress.equals(kbDown))
+            ) {
+                parent.switchWidget(true);
+                return;
+            } else if ((keypress.equals(kbShiftTab))
+                || (keypress.equals(kbBackTab))
+                || (keypress.equals(kbUp))
+            ) {
+                parent.switchWidget(false);
+                return;
+            }
+        }
+
+        if ((children.size() == 0)
+            && !(this instanceof TTreeView)
+        ) {
+
+            // Defaults:
+            //   right-arrow or down-arrow: same as tab
+            if (keypress.equals(kbRight)) {
+                parent.switchWidget(true);
+                return;
+            } else if (keypress.equals(kbLeft)) {
+                parent.switchWidget(false);
+                return;
+            }
+        }
+
+        // If I have any buttons on me AND this is an Alt-key that matches
+        // its mnemonic, send it an Enter keystroke
+        for (TWidget widget: children) {
+            if (widget instanceof TButton) {
+                TButton button = (TButton) widget;
+                if (button.isEnabled()
+                    && !keypress.getKey().isFnKey()
+                    && keypress.getKey().isAlt()
+                    && !keypress.getKey().isCtrl()
+                    && (Character.toLowerCase(button.getMnemonic().getShortcut())
+                        == Character.toLowerCase(keypress.getKey().getChar()))
+                ) {
+
+                    widget.onKeypress(new TKeypressEvent(kbEnter));
+                    return;
+                }
+            }
+        }
+
+        // Dispatch the keypress to an active widget
+        for (TWidget widget: children) {
+            if (widget.active) {
+                widget.onKeypress(keypress);
+                return;
+            }
+        }
     }
 
     /**
     }
 
     /**
-     * Set cursor X value.
+     * Method that subclasses can override to handle mouse button presses.
      *
      *
-     * @param cursorX column position in relative coordinates
+     * @param mouse mouse button event
      */
      */
-    public final void setCursorX(final int cursorX) {
-        this.cursorX = cursorX;
-    }
+    public void onMouseDown(final TMouseEvent mouse) {
+        // Default: do nothing, pass to children instead
+        for (int i = children.size() - 1 ; i >= 0 ; i--) {
+            TWidget widget = children.get(i);
+            if (widget.mouseWouldHit(mouse)) {
+                // Dispatch to this child, also activate it
+                activate(widget);
 
 
-    /**
-     * Cursor row position in relative coordinates.
-     */
-    private int cursorY = 0;
+                // Set x and y relative to the child's coordinates
+                mouse.setX(mouse.getAbsoluteX() - widget.getAbsoluteX());
+                mouse.setY(mouse.getAbsoluteY() - widget.getAbsoluteY());
+                widget.onMouseDown(mouse);
+                return;
+            }
+        }
+    }
 
     /**
 
     /**
-     * Get cursor Y value.
+     * Method that subclasses can override to handle mouse button releases.
      *
      *
-     * @return cursor row position in relative coordinates
+     * @param mouse mouse button event
      */
      */
-    public final int getCursorY() {
-        return cursorY;
+    public void onMouseUp(final TMouseEvent mouse) {
+        // Default: do nothing, pass to children instead
+        for (int i = children.size() - 1 ; i >= 0 ; i--) {
+            TWidget widget = children.get(i);
+            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.onMouseUp(mouse);
+                return;
+            }
+        }
     }
 
     /**
     }
 
     /**
-     * Set cursor Y value.
+     * Method that subclasses can override to handle mouse movements.
      *
      *
-     * @param cursorY row position in relative coordinates
+     * @param mouse mouse motion event
      */
      */
-    public final void setCursorY(final int cursorY) {
-        this.cursorY = cursorY;
+    public void onMouseMotion(final TMouseEvent mouse) {
+        // Default: do nothing, pass it on to ALL of my children.  This way
+        // the children can see the mouse "leaving" their area.
+        for (TWidget widget: children) {
+            // Set x and y relative to the child's coordinates
+            mouse.setX(mouse.getAbsoluteX() - widget.getAbsoluteX());
+            mouse.setY(mouse.getAbsoluteY() - widget.getAbsoluteY());
+            widget.onMouseMotion(mouse);
+        }
     }
 
     }
 
-    // ------------------------------------------------------------------------
-    // TApplication integration -----------------------------------------------
-    // ------------------------------------------------------------------------
-
     /**
     /**
-     * Get this TWidget's parent TApplication.
+     * Method that subclasses can override to handle mouse button
+     * double-clicks.
      *
      *
-     * @return the parent TApplication
+     * @param mouse mouse button event
      */
      */
-    public TApplication getApplication() {
-        return window.getApplication();
+    public void onMouseDoubleClick(final TMouseEvent mouse) {
+        // Default: do nothing, pass to children instead
+        for (int i = children.size() - 1 ; i >= 0 ; i--) {
+            TWidget widget = children.get(i);
+            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.onMouseDoubleClick(mouse);
+                return;
+            }
+        }
     }
 
     /**
     }
 
     /**
-     * Get the Screen.
+     * Method that subclasses can override to handle window/screen resize
+     * events.
      *
      *
-     * @return the Screen
+     * @param resize resize event
      */
      */
-    public Screen getScreen() {
-        return window.getScreen();
+    public void onResize(final TResizeEvent resize) {
+        // Default: change my width/height.
+        if (resize.getType() == TResizeEvent.Type.WIDGET) {
+            width = resize.getWidth();
+            height = resize.getHeight();
+        } else {
+            // Let children see the screen resize
+            for (TWidget widget: children) {
+                widget.onResize(resize);
+            }
+        }
     }
 
     /**
     }
 
     /**
-     * Comparison operator.  For various subclasses it sorts on:
-     * <ul>
-     * <li>tabOrder for TWidgets</li>
-     * <li>z for TWindows</li>
-     * <li>text for TTreeItems</li>
-     * </ul>
+     * Method that subclasses can override to handle posted command events.
      *
      *
-     * @param that another TWidget, TWindow, or TTreeItem instance
-     * @return difference between this.tabOrder and that.tabOrder, or
-     * difference between this.z and that.z, or String.compareTo(text)
+     * @param command command event
      */
      */
-    public final int compareTo(final TWidget that) {
-        if ((this instanceof TWindow)
-            && (that instanceof TWindow)
-        ) {
-            return (((TWindow) this).getZ() - ((TWindow) that).getZ());
-        }
-        if ((this instanceof TTreeItem)
-            && (that instanceof TTreeItem)
-        ) {
-            return (((TTreeItem) this).getText().compareTo(
-                ((TTreeItem) that).getText()));
+    public void onCommand(final TCommandEvent command) {
+        // Default: do nothing, pass to children instead
+        for (TWidget widget: children) {
+            widget.onCommand(command);
         }
         }
-        return (this.tabOrder - that.tabOrder);
     }
 
     /**
     }
 
     /**
-     * See if this widget should render with the active color.
+     * Method that subclasses can override to handle menu or posted menu
+     * events.
      *
      *
-     * @return true if this widget is active and all of its parents are
-     * active.
+     * @param menu menu event
      */
      */
-    public final boolean isAbsoluteActive() {
-        if (parent == this) {
-            return active;
+    public void onMenu(final TMenuEvent menu) {
+        // Default: do nothing, pass to children instead
+        for (TWidget widget: children) {
+            widget.onMenu(menu);
         }
         }
-        return (active && parent.isAbsoluteActive());
     }
 
     /**
     }
 
     /**
-     * Returns the cursor X position.
-     *
-     * @return absolute screen column number for the cursor's X position
+     * Method that subclasses can override to do processing when the UI is
+     * idle.  Note that repainting is NOT assumed.  To get a refresh after
+     * onIdle, call doRepaint().
      */
      */
-    public final int getCursorAbsoluteX() {
-        return getAbsoluteX() + cursorX;
+    public void onIdle() {
+        // Default: do nothing, pass to children instead
+        for (TWidget widget: children) {
+            widget.onIdle();
+        }
     }
 
     /**
     }
 
     /**
-     * Returns the cursor Y position.
+     * Consume event.  Subclasses that want to intercept all events in one go
+     * can override this method.
      *
      *
-     * @return absolute screen row number for the cursor's Y position
+     * @param event keyboard, mouse, resize, command, or menu event
      */
      */
-    public final int getCursorAbsoluteY() {
-        return getAbsoluteY() + cursorY;
+    public void handleEvent(final TInputEvent event) {
+        /*
+        System.err.printf("TWidget (%s) event: %s\n", this.getClass().getName(),
+            event);
+        */
+
+        if (!enabled) {
+            // Discard event
+            // System.err.println("   -- discard --");
+            return;
+        }
+
+        if (event instanceof TKeypressEvent) {
+            onKeypress((TKeypressEvent) event);
+        } else if (event instanceof TMouseEvent) {
+
+            TMouseEvent mouse = (TMouseEvent) event;
+
+            switch (mouse.getType()) {
+
+            case MOUSE_DOWN:
+                onMouseDown(mouse);
+                break;
+
+            case MOUSE_UP:
+                onMouseUp(mouse);
+                break;
+
+            case MOUSE_MOTION:
+                onMouseMotion(mouse);
+                break;
+
+            case MOUSE_DOUBLE_CLICK:
+                onMouseDoubleClick(mouse);
+                break;
+
+            default:
+                throw new IllegalArgumentException("Invalid mouse event type: "
+                    + mouse.getType());
+            }
+        } else if (event instanceof TResizeEvent) {
+            onResize((TResizeEvent) event);
+        } else if (event instanceof TCommandEvent) {
+            onCommand((TCommandEvent) event);
+        } else if (event instanceof TMenuEvent) {
+            onMenu((TMenuEvent) event);
+        }
+
+        // Do nothing else
+        return;
     }
 
     }
 
+    // ------------------------------------------------------------------------
+    // TWidget ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
     /**
-     * Compute my absolute X position as the sum of my X plus all my parent's
-     * X's.
+     * Get parent widget.
      *
      *
-     * @return absolute screen column number for my X position
+     * @return parent widget
      */
      */
-    public final int getAbsoluteX() {
-        assert (parent != null);
-        if (parent == this) {
-            return x;
-        }
-        if ((parent instanceof TWindow)
-            && !(parent instanceof TMenu)
-            && !(parent instanceof TDesktop)
-        ) {
-            // Widgets on a TWindow have (0,0) as their top-left, but this is
-            // actually the TWindow's (1,1).
-            return parent.getAbsoluteX() + x + 1;
-        }
-        return parent.getAbsoluteX() + x;
+    public final TWidget getParent() {
+        return parent;
     }
 
     /**
     }
 
     /**
-     * Compute my absolute Y position as the sum of my Y plus all my parent's
-     * Y's.
+     * Get the list of child widgets that this widget contains.
      *
      *
-     * @return absolute screen row number for my Y position
+     * @return the list of child widgets
      */
      */
-    public final int getAbsoluteY() {
-        assert (parent != null);
-        if (parent == this) {
-            return y;
-        }
-        if ((parent instanceof TWindow)
-            && !(parent instanceof TMenu)
-            && !(parent instanceof TDesktop)
-        ) {
-            // Widgets on a TWindow have (0,0) as their top-left, but this is
-            // actually the TWindow's (1,1).
-            return parent.getAbsoluteY() + y + 1;
-        }
-        return parent.getAbsoluteY() + y;
+    public List<TWidget> getChildren() {
+        return children;
     }
 
     /**
     }
 
     /**
-     * Get the global color theme.
+     * Get active flag.
      *
      *
-     * @return the ColorTheme
+     * @return if true, this widget will receive events
      */
      */
-    public final ColorTheme getTheme() {
-        return window.getApplication().getTheme();
+    public final boolean isActive() {
+        return active;
     }
 
     /**
     }
 
     /**
-     * Draw my specific widget.  When called, the screen rectangle I draw
-     * into is already setup (offset and clipping).
+     * Set active flag.
+     *
+     * @param active if true, this widget will receive events
      */
      */
-    public void draw() {
-        // Default widget draws nothing.
+    public final void setActive(final boolean active) {
+        this.active = active;
     }
 
     /**
     }
 
     /**
-     * Called by parent to render to TWindow.
+     * Get the window this widget is on.
+     *
+     * @return the window
      */
      */
-    public final void drawChildren() {
-        // Set my clipping rectangle
-        assert (window != null);
-        assert (getScreen() != null);
-        Screen screen = getScreen();
-
-        // Special case: TStatusBar is drawn by TApplication, not anything
-        // else.
-        if (this instanceof TStatusBar) {
-            return;
-        }
-
-        screen.setClipRight(width);
-        screen.setClipBottom(height);
-
-        int absoluteRightEdge = window.getAbsoluteX() + window.getWidth();
-        int absoluteBottomEdge = window.getAbsoluteY() + window.getHeight();
-        if (!(this instanceof TWindow) && !(this instanceof TVScroller)) {
-            absoluteRightEdge -= 1;
-        }
-        if (!(this instanceof TWindow) && !(this instanceof THScroller)) {
-            absoluteBottomEdge -= 1;
-        }
-        int myRightEdge = getAbsoluteX() + width;
-        int myBottomEdge = getAbsoluteY() + height;
-        if (getAbsoluteX() > absoluteRightEdge) {
-            // I am offscreen
-            screen.setClipRight(0);
-        } else if (myRightEdge > absoluteRightEdge) {
-            screen.setClipRight(screen.getClipRight()
-                - (myRightEdge - absoluteRightEdge));
-        }
-        if (getAbsoluteY() > absoluteBottomEdge) {
-            // I am offscreen
-            screen.setClipBottom(0);
-        } else if (myBottomEdge > absoluteBottomEdge) {
-            screen.setClipBottom(screen.getClipBottom()
-                - (myBottomEdge - absoluteBottomEdge));
-        }
-
-        // Set my offset
-        screen.setOffsetX(getAbsoluteX());
-        screen.setOffsetY(getAbsoluteY());
-
-        // Draw me
-        draw();
-
-        // Continue down the chain
-        for (TWidget widget: children) {
-            widget.drawChildren();
-        }
+    public final TWindow getWindow() {
+        return window;
     }
 
     /**
     }
 
     /**
-     * Repaint the screen on the next update.
+     * Get X position.
+     *
+     * @return absolute X position of the top-left corner
      */
      */
-    public void doRepaint() {
-        window.getApplication().doRepaint();
+    public final int getX() {
+        return x;
     }
 
     }
 
-    // ------------------------------------------------------------------------
-    // Constructors -----------------------------------------------------------
-    // ------------------------------------------------------------------------
-
     /**
     /**
-     * Default constructor for subclasses.
+     * Set X position.
+     *
+     * @param x absolute X position of the top-left corner
      */
      */
-    protected TWidget() {
-        children = new ArrayList<TWidget>();
+    public final void setX(final int x) {
+        this.x = x;
     }
 
     /**
     }
 
     /**
-     * Protected constructor.
+     * Get Y position.
      *
      *
-     * @param parent parent widget
+     * @return absolute Y position of the top-left corner
      */
      */
-    protected TWidget(final TWidget parent) {
-        this(parent, true);
+    public final int getY() {
+        return y;
     }
 
     /**
     }
 
     /**
-     * Protected constructor.
+     * Set Y position.
      *
      *
-     * @param parent parent widget
-     * @param x column relative to parent
-     * @param y row relative to parent
-     * @param width width of widget
-     * @param height height of widget
+     * @param y absolute Y position of the top-left corner
      */
      */
-    protected TWidget(final TWidget parent, final int x, final int y,
-        final int width, final int height) {
-
-        this(parent, true, x, y, width, height);
+    public final void setY(final int y) {
+        this.y = y;
     }
 
     /**
     }
 
     /**
-     * Protected constructor used by subclasses that are disabled by default.
+     * Get the width.
      *
      *
-     * @param parent parent widget
-     * @param enabled if true assume enabled
+     * @return widget width
      */
      */
-    protected TWidget(final TWidget parent, final boolean enabled) {
-        this.enabled = enabled;
-        this.parent = parent;
-        this.window = parent.window;
-        children = new ArrayList<TWidget>();
-
-        // Do not add TStatusBars, they are drawn by TApplication
-        if (this instanceof TStatusBar) {
-        } else {
-            parent.addChild(this);
-        }
+    public final int getWidth() {
+        return this.width;
     }
 
     /**
     }
 
     /**
-     * Protected constructor used by subclasses that are disabled by default.
+     * Change the width.
      *
      *
-     * @param parent parent widget
-     * @param enabled if true assume enabled
-     * @param x column relative to parent
-     * @param y row relative to parent
-     * @param width width of widget
-     * @param height height of widget
+     * @param width new widget width
      */
      */
-    protected TWidget(final TWidget parent, final boolean enabled,
-        final int x, final int y, final int width, final int height) {
-
-        this.enabled = enabled;
-        this.parent = parent;
-        this.window = parent.window;
-        children = new ArrayList<TWidget>();
-
-        // Do not add TStatusBars, they are drawn by TApplication
-        if (this instanceof TStatusBar) {
-        } else {
-            parent.addChild(this);
-        }
-
-        this.x = x;
-        this.y = y;
+    public final void setWidth(final int width) {
         this.width = width;
         this.width = width;
-        this.height = height;
     }
 
     /**
     }
 
     /**
-     * Backdoor access for TWindow's constructor.  ONLY TWindow USES THIS.
+     * Get the height.
      *
      *
-     * @param window the top-level window
-     * @param x column relative to parent
-     * @param y row relative to parent
-     * @param width width of window
-     * @param height height of window
+     * @return widget height
      */
      */
-    protected final void setupForTWindow(final TWindow window,
-        final int x, final int y, final int width, final int height) {
+    public final int getHeight() {
+        return this.height;
+    }
 
 
-        this.parent = window;
-        this.window = window;
-        this.x      = x;
-        this.y      = y;
-        this.width  = width;
+    /**
+     * Change the height.
+     *
+     * @param height new widget height
+     */
+    public final void setHeight(final int height) {
         this.height = height;
     }
 
         this.height = height;
     }
 
-    // ------------------------------------------------------------------------
-    // General behavior -------------------------------------------------------
-    // ------------------------------------------------------------------------
-
     /**
     /**
-     * Add a child widget to my list of children.  We set its tabOrder to 0
-     * and increment the tabOrder of all other children.
+     * Change the dimensions.
      *
      *
-     * @param child TWidget to add
+     * @param x absolute X position of the top-left corner
+     * @param y absolute Y position of the top-left corner
+     * @param width new widget width
+     * @param height new widget height
      */
      */
-    private void addChild(final TWidget child) {
-        children.add(child);
+    public final void setDimensions(final int x, final int y, final int width,
+        final int height) {
 
 
-        if ((child.enabled)
-            && !(child instanceof THScroller)
-            && !(child instanceof TVScroller)
-        ) {
-            for (TWidget widget: children) {
-                widget.active = false;
-            }
-            child.active = true;
-            activeChild = child;
-        }
-        for (int i = 0; i < children.size(); i++) {
-            children.get(i).tabOrder = i;
-        }
+        setX(x);
+        setY(y);
+        setWidth(width);
+        setHeight(height);
     }
 
     /**
     }
 
     /**
-     * Switch the active child.
+     * Get enabled flag.
      *
      *
-     * @param child TWidget to activate
+     * @return if true, this widget can be tabbed to or receive events
      */
      */
-    public final void activate(final TWidget child) {
-        assert (child.enabled);
-        if ((child instanceof THScroller)
-            || (child instanceof TVScroller)
-        ) {
-            return;
-        }
+    public final boolean isEnabled() {
+        return enabled;
+    }
 
 
-        if (child != activeChild) {
-            if (activeChild != null) {
-                activeChild.active = false;
+    /**
+     * Set enabled flag.
+     *
+     * @param enabled if true, this widget can be tabbed to or receive events
+     */
+    public final void setEnabled(final boolean enabled) {
+        this.enabled = enabled;
+        if (!enabled) {
+            active = false;
+            // See if there are any active siblings to switch to
+            boolean foundSibling = false;
+            if (parent != null) {
+                for (TWidget w: parent.children) {
+                    if ((w.enabled)
+                        && !(this instanceof THScroller)
+                        && !(this instanceof TVScroller)
+                    ) {
+                        parent.activate(w);
+                        foundSibling = true;
+                        break;
+                    }
+                }
+                if (!foundSibling) {
+                    parent.activeChild = null;
+                }
             }
             }
-            child.active = true;
-            activeChild = child;
         }
     }
 
     /**
         }
     }
 
     /**
-     * Switch the active child.
+     * Set visible cursor flag.
      *
      *
-     * @param tabOrder tabOrder of the child to activate.  If that child
-     * isn't enabled, then the next enabled child will be activated.
+     * @param cursorVisible if true, this widget has a cursor
      */
      */
-    public final void activate(final int tabOrder) {
-        if (activeChild == null) {
-            return;
-        }
-        TWidget child = null;
-        for (TWidget widget: children) {
-            if ((widget.enabled)
-                && !(widget instanceof THScroller)
-                && !(widget instanceof TVScroller)
-                && (widget.tabOrder >= tabOrder)
-            ) {
-                child = widget;
-                break;
-            }
-        }
-        if ((child != null) && (child != activeChild)) {
-            activeChild.active = false;
-            assert (child.enabled);
-            child.active = true;
-            activeChild = child;
-        }
+    public final void setCursorVisible(final boolean cursorVisible) {
+        this.cursorVisible = cursorVisible;
     }
 
     /**
     }
 
     /**
-     * Switch the active widget with the next in the tab order.
+     * See if this widget has a visible cursor.
      *
      *
-     * @param forward if true, then switch to the next enabled widget in the
-     * list, otherwise switch to the previous enabled widget in the list
+     * @return if true, this widget has a visible cursor
      */
      */
-    public final void switchWidget(final boolean forward) {
+    public final boolean isCursorVisible() {
+        // If cursor is out of my bounds, it is not visible.
+        if ((cursorX >= width)
+            || (cursorX < 0)
+            || (cursorY >= height)
+            || (cursorY < 0)
+        ) {
+            return false;
+        }
 
 
-        // Only switch if there are multiple enabled widgets
-        if ((children.size() < 2) || (activeChild == null)) {
-            return;
+        // If cursor is out of my window's bounds, it is not visible.
+        if ((getCursorAbsoluteX() >= window.getAbsoluteX()
+                + window.getWidth() - 1)
+            || (getCursorAbsoluteX() < 0)
+            || (getCursorAbsoluteY() >= window.getAbsoluteY()
+                + window.getHeight() - 1)
+            || (getCursorAbsoluteY() < 0)
+        ) {
+            return false;
         }
         }
+        return cursorVisible;
+    }
 
 
-        int tabOrder = activeChild.tabOrder;
-        do {
-            if (forward) {
-                tabOrder++;
-            } else {
-                tabOrder--;
-            }
-            if (tabOrder < 0) {
+    /**
+     * Get cursor X value.
+     *
+     * @return cursor column position in relative coordinates
+     */
+    public final int getCursorX() {
+        return cursorX;
+    }
 
 
-                // If at the end, pass the switch to my parent.
-                if ((!forward) && (parent != this)) {
-                    parent.switchWidget(forward);
-                    return;
-                }
+    /**
+     * Set cursor X value.
+     *
+     * @param cursorX column position in relative coordinates
+     */
+    public final void setCursorX(final int cursorX) {
+        this.cursorX = cursorX;
+    }
 
 
-                tabOrder = children.size() - 1;
-            } else if (tabOrder == children.size()) {
-                // If at the end, pass the switch to my parent.
-                if ((forward) && (parent != this)) {
-                    parent.switchWidget(forward);
-                    return;
-                }
+    /**
+     * Get cursor Y value.
+     *
+     * @return cursor row position in relative coordinates
+     */
+    public final int getCursorY() {
+        return cursorY;
+    }
 
 
-                tabOrder = 0;
-            }
-            if (activeChild.tabOrder == tabOrder) {
-                // We wrapped around
-                break;
-            }
-        } while ((!children.get(tabOrder).enabled)
-            && !(children.get(tabOrder) instanceof THScroller)
-            && !(children.get(tabOrder) instanceof TVScroller));
+    /**
+     * Set cursor Y value.
+     *
+     * @param cursorY row position in relative coordinates
+     */
+    public final void setCursorY(final int cursorY) {
+        this.cursorY = cursorY;
+    }
 
 
-        assert (children.get(tabOrder).enabled);
+    /**
+     * Get this TWidget's parent TApplication.
+     *
+     * @return the parent TApplication
+     */
+    public TApplication getApplication() {
+        return window.getApplication();
+    }
 
 
-        activeChild.active = false;
-        children.get(tabOrder).active = true;
-        activeChild = children.get(tabOrder);
+    /**
+     * Get the Screen.
+     *
+     * @return the Screen
+     */
+    public Screen getScreen() {
+        return window.getScreen();
     }
 
     /**
     }
 
     /**
-     * Returns my active widget.
+     * Comparison operator.  For various subclasses it sorts on:
+     * <ul>
+     * <li>tabOrder for TWidgets</li>
+     * <li>z for TWindows</li>
+     * <li>text for TTreeItems</li>
+     * </ul>
      *
      *
-     * @return widget that is active, or this if no children
+     * @param that another TWidget, TWindow, or TTreeItem instance
+     * @return difference between this.tabOrder and that.tabOrder, or
+     * difference between this.z and that.z, or String.compareTo(text)
      */
      */
-    public TWidget getActiveChild() {
-        if ((this instanceof THScroller)
-            || (this instanceof TVScroller)
+    public final int compareTo(final TWidget that) {
+        if ((this instanceof TWindow)
+            && (that instanceof TWindow)
         ) {
         ) {
-            return parent;
+            return (((TWindow) this).getZ() - ((TWindow) that).getZ());
+        }
+        if ((this instanceof TTreeItem)
+            && (that instanceof TTreeItem)
+        ) {
+            return (((TTreeItem) this).getText().compareTo(
+                ((TTreeItem) that).getText()));
         }
         }
+        return (this.tabOrder - that.tabOrder);
+    }
 
 
-        for (TWidget widget: children) {
-            if (widget.active) {
-                return widget.getActiveChild();
-            }
+    /**
+     * See if this widget should render with the active color.
+     *
+     * @return true if this widget is active and all of its parents are
+     * active.
+     */
+    public final boolean isAbsoluteActive() {
+        if (parent == this) {
+            return active;
         }
         }
-        // No active children, return me
-        return this;
+        return (active && parent.isAbsoluteActive());
     }
 
     }
 
-    // ------------------------------------------------------------------------
-    // Event handlers ---------------------------------------------------------
-    // ------------------------------------------------------------------------
+    /**
+     * Returns the cursor X position.
+     *
+     * @return absolute screen column number for the cursor's X position
+     */
+    public final int getCursorAbsoluteX() {
+        return getAbsoluteX() + cursorX;
+    }
 
     /**
 
     /**
-     * Check if a mouse press/release event coordinate is contained in this
-     * widget.
+     * Returns the cursor Y position.
      *
      *
-     * @param mouse a mouse-based event
-     * @return whether or not a mouse click would be sent to this widget
+     * @return absolute screen row number for the cursor's Y position
      */
      */
-    public final boolean mouseWouldHit(final TMouseEvent mouse) {
+    public final int getCursorAbsoluteY() {
+        return getAbsoluteY() + cursorY;
+    }
 
 
-        if (!enabled) {
-            return false;
+    /**
+     * Compute my absolute X position as the sum of my X plus all my parent's
+     * X's.
+     *
+     * @return absolute screen column number for my X position
+     */
+    public final int getAbsoluteX() {
+        assert (parent != null);
+        if (parent == this) {
+            return x;
         }
         }
+        if ((parent instanceof TWindow)
+            && !(parent instanceof TMenu)
+            && !(parent instanceof TDesktop)
+        ) {
+            // Widgets on a TWindow have (0,0) as their top-left, but this is
+            // actually the TWindow's (1,1).
+            return parent.getAbsoluteX() + x + 1;
+        }
+        return parent.getAbsoluteX() + x;
+    }
 
 
-        if ((mouse.getAbsoluteX() >= getAbsoluteX())
-            && (mouse.getAbsoluteX() <  getAbsoluteX() + width)
-            && (mouse.getAbsoluteY() >= getAbsoluteY())
-            && (mouse.getAbsoluteY() <  getAbsoluteY() + height)
+    /**
+     * Compute my absolute Y position as the sum of my Y plus all my parent's
+     * Y's.
+     *
+     * @return absolute screen row number for my Y position
+     */
+    public final int getAbsoluteY() {
+        assert (parent != null);
+        if (parent == this) {
+            return y;
+        }
+        if ((parent instanceof TWindow)
+            && !(parent instanceof TMenu)
+            && !(parent instanceof TDesktop)
         ) {
         ) {
-            return true;
+            // Widgets on a TWindow have (0,0) as their top-left, but this is
+            // actually the TWindow's (1,1).
+            return parent.getAbsoluteY() + y + 1;
         }
         }
-        return false;
+        return parent.getAbsoluteY() + y;
     }
 
     /**
     }
 
     /**
-     * Method that subclasses can override to handle keystrokes.
+     * Get the global color theme.
      *
      *
-     * @param keypress keystroke event
+     * @return the ColorTheme
      */
      */
-    public void onKeypress(final TKeypressEvent keypress) {
+    public final ColorTheme getTheme() {
+        return window.getApplication().getTheme();
+    }
 
 
-        if ((children.size() == 0)
-            || (this instanceof TTreeView)
-            || (this instanceof TText)
-        ) {
+    /**
+     * Draw my specific widget.  When called, the screen rectangle I draw
+     * into is already setup (offset and clipping).
+     */
+    public void draw() {
+        // Default widget draws nothing.
+    }
 
 
-            // Defaults:
-            //   tab / shift-tab - switch to next/previous widget
-            //   right-arrow or down-arrow: same as tab
-            //   left-arrow or up-arrow: same as shift-tab
-            if ((keypress.equals(kbTab))
-                || (keypress.equals(kbRight))
-                || (keypress.equals(kbDown))
-            ) {
-                parent.switchWidget(true);
-                return;
-            } else if ((keypress.equals(kbShiftTab))
-                || (keypress.equals(kbBackTab))
-                || (keypress.equals(kbLeft))
-                || (keypress.equals(kbUp))
-            ) {
-                parent.switchWidget(false);
-                return;
-            }
+    /**
+     * Called by parent to render to TWindow.
+     */
+    public final void drawChildren() {
+        // Set my clipping rectangle
+        assert (window != null);
+        assert (getScreen() != null);
+        Screen screen = getScreen();
+
+        // Special case: TStatusBar is drawn by TApplication, not anything
+        // else.
+        if (this instanceof TStatusBar) {
+            return;
         }
 
         }
 
-        // If I have any buttons on me AND this is an Alt-key that matches
-        // its mnemonic, send it an Enter keystroke
-        for (TWidget widget: children) {
-            if (widget instanceof TButton) {
-                TButton button = (TButton) widget;
-                if (button.isEnabled()
-                    && !keypress.getKey().isFnKey()
-                    && keypress.getKey().isAlt()
-                    && !keypress.getKey().isCtrl()
-                    && (Character.toLowerCase(button.getMnemonic().getShortcut())
-                        == Character.toLowerCase(keypress.getKey().getChar()))
-                ) {
+        screen.setClipRight(width);
+        screen.setClipBottom(height);
 
 
-                    widget.handleEvent(new TKeypressEvent(kbEnter));
-                    return;
-                }
-            }
+        int absoluteRightEdge = window.getAbsoluteX() + window.getWidth();
+        int absoluteBottomEdge = window.getAbsoluteY() + window.getHeight();
+        if (!(this instanceof TWindow) && !(this instanceof TVScroller)) {
+            absoluteRightEdge -= 1;
+        }
+        if (!(this instanceof TWindow) && !(this instanceof THScroller)) {
+            absoluteBottomEdge -= 1;
         }
         }
+        int myRightEdge = getAbsoluteX() + width;
+        int myBottomEdge = getAbsoluteY() + height;
+        if (getAbsoluteX() > absoluteRightEdge) {
+            // I am offscreen
+            screen.setClipRight(0);
+        } else if (myRightEdge > absoluteRightEdge) {
+            screen.setClipRight(screen.getClipRight()
+                - (myRightEdge - absoluteRightEdge));
+        }
+        if (getAbsoluteY() > absoluteBottomEdge) {
+            // I am offscreen
+            screen.setClipBottom(0);
+        } else if (myBottomEdge > absoluteBottomEdge) {
+            screen.setClipBottom(screen.getClipBottom()
+                - (myBottomEdge - absoluteBottomEdge));
+        }
+
+        // Set my offset
+        screen.setOffsetX(getAbsoluteX());
+        screen.setOffsetY(getAbsoluteY());
 
 
-        // Dispatch the keypress to an active widget
+        // Draw me
+        draw();
+
+        // Continue down the chain
         for (TWidget widget: children) {
         for (TWidget widget: children) {
-            if (widget.active) {
-                widget.handleEvent(keypress);
-                return;
-            }
+            widget.drawChildren();
         }
     }
 
     /**
         }
     }
 
     /**
-     * Method that subclasses can override to handle mouse button presses.
-     *
-     * @param mouse mouse button event
+     * Repaint the screen on the next update.
      */
      */
-    public void onMouseDown(final TMouseEvent mouse) {
-        // Default: do nothing, pass to children instead
-        for (int i = children.size() - 1 ; i >= 0 ; i--) {
-            TWidget widget = children.get(i);
-            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;
-            }
-        }
+    public void doRepaint() {
+        window.getApplication().doRepaint();
     }
 
     /**
     }
 
     /**
-     * Method that subclasses can override to handle mouse button releases.
+     * Add a child widget to my list of children.  We set its tabOrder to 0
+     * and increment the tabOrder of all other children.
      *
      *
-     * @param mouse mouse button event
+     * @param child TWidget to add
      */
      */
-    public void onMouseUp(final TMouseEvent mouse) {
-        // Default: do nothing, pass to children instead
-        for (int i = children.size() - 1 ; i >= 0 ; i--) {
-            TWidget widget = children.get(i);
-            if (widget.mouseWouldHit(mouse)) {
-                // Dispatch to this child, also activate it
-                activate(widget);
+    private void addChild(final TWidget child) {
+        children.add(child);
 
 
-                // 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;
+        if ((child.enabled)
+            && !(child instanceof THScroller)
+            && !(child instanceof TVScroller)
+        ) {
+            for (TWidget widget: children) {
+                widget.active = false;
             }
             }
+            child.active = true;
+            activeChild = child;
         }
         }
-    }
-
-    /**
-     * Method that subclasses can override to handle mouse movements.
-     *
-     * @param mouse mouse motion event
-     */
-    public void onMouseMotion(final TMouseEvent mouse) {
-        // Default: do nothing, pass it on to ALL of my children.  This way
-        // the children can see the mouse "leaving" their area.
-        for (TWidget widget: children) {
-            // 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);
+        for (int i = 0; i < children.size(); i++) {
+            children.get(i).tabOrder = i;
         }
     }
 
     /**
         }
     }
 
     /**
-     * Method that subclasses can override to handle mouse button
-     * double-clicks.
+     * Switch the active child.
      *
      *
-     * @param mouse mouse button event
+     * @param child TWidget to activate
      */
      */
-    public void onMouseDoubleClick(final TMouseEvent mouse) {
-        // Default: do nothing, pass to children instead
-        for (int i = children.size() - 1 ; i >= 0 ; i--) {
-            TWidget widget = children.get(i);
-            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;
-            }
+    public final void activate(final TWidget child) {
+        assert (child.enabled);
+        if ((child instanceof THScroller)
+            || (child instanceof TVScroller)
+        ) {
+            return;
         }
         }
-    }
 
 
-    /**
-     * Method that subclasses can override to handle window/screen resize
-     * events.
-     *
-     * @param resize resize event
-     */
-    public void onResize(final TResizeEvent resize) {
-        // Default: change my width/height.
-        if (resize.getType() == TResizeEvent.Type.WIDGET) {
-            width = resize.getWidth();
-            height = resize.getHeight();
-        } else {
-            // Let children see the screen resize
-            for (TWidget widget: children) {
-                widget.onResize(resize);
+        if (child != activeChild) {
+            if (activeChild != null) {
+                activeChild.active = false;
             }
             }
+            child.active = true;
+            activeChild = child;
         }
     }
 
     /**
         }
     }
 
     /**
-     * Method that subclasses can override to handle posted command events.
+     * Switch the active child.
      *
      *
-     * @param command command event
+     * @param tabOrder tabOrder of the child to activate.  If that child
+     * isn't enabled, then the next enabled child will be activated.
      */
      */
-    public void onCommand(final TCommandEvent command) {
-        // Default: do nothing, pass to children instead
-        for (TWidget widget: children) {
-            widget.onCommand(command);
+    public final void activate(final int tabOrder) {
+        if (activeChild == null) {
+            return;
         }
         }
-    }
-
-    /**
-     * Method that subclasses can override to handle menu or posted menu
-     * events.
-     *
-     * @param menu menu event
-     */
-    public void onMenu(final TMenuEvent menu) {
-        // Default: do nothing, pass to children instead
+        TWidget child = null;
         for (TWidget widget: children) {
         for (TWidget widget: children) {
-            widget.onMenu(menu);
+            if ((widget.enabled)
+                && !(widget instanceof THScroller)
+                && !(widget instanceof TVScroller)
+                && (widget.tabOrder >= tabOrder)
+            ) {
+                child = widget;
+                break;
+            }
         }
         }
-    }
-
-    /**
-     * Method that subclasses can override to do processing when the UI is
-     * idle.  Note that repainting is NOT assumed.  To get a refresh after
-     * onIdle, call doRepaint().
-     */
-    public void onIdle() {
-        // Default: do nothing, pass to children instead
-        for (TWidget widget: children) {
-            widget.onIdle();
+        if ((child != null) && (child != activeChild)) {
+            activeChild.active = false;
+            assert (child.enabled);
+            child.active = true;
+            activeChild = child;
         }
     }
 
     /**
         }
     }
 
     /**
-     * Consume event.  Subclasses that want to intercept all events in one go
-     * can override this method.
+     * Switch the active widget with the next in the tab order.
      *
      *
-     * @param event keyboard, mouse, resize, command, or menu event
+     * @param forward if true, then switch to the next enabled widget in the
+     * list, otherwise switch to the previous enabled widget in the list
      */
      */
-    public void handleEvent(final TInputEvent event) {
-        // System.err.printf("TWidget (%s) event: %s\n", this.getClass().getName(),
-        //     event);
+    public final void switchWidget(final boolean forward) {
 
 
-        if (!enabled) {
-            // Discard event
-            // System.err.println("   -- discard --");
+        // Only switch if there are multiple enabled widgets
+        if ((children.size() < 2) || (activeChild == null)) {
             return;
         }
 
             return;
         }
 
-        if (event instanceof TKeypressEvent) {
-            onKeypress((TKeypressEvent) event);
-        } else if (event instanceof TMouseEvent) {
+        int tabOrder = activeChild.tabOrder;
+        do {
+            if (forward) {
+                tabOrder++;
+            } else {
+                tabOrder--;
+            }
+            if (tabOrder < 0) {
 
 
-            TMouseEvent mouse = (TMouseEvent) event;
+                // If at the end, pass the switch to my parent.
+                if ((!forward) && (parent != this)) {
+                    parent.switchWidget(forward);
+                    return;
+                }
 
 
-            switch (mouse.getType()) {
+                tabOrder = children.size() - 1;
+            } else if (tabOrder == children.size()) {
+                // If at the end, pass the switch to my parent.
+                if ((forward) && (parent != this)) {
+                    parent.switchWidget(forward);
+                    return;
+                }
 
 
-            case MOUSE_DOWN:
-                onMouseDown(mouse);
+                tabOrder = 0;
+            }
+            if (activeChild.tabOrder == tabOrder) {
+                // We wrapped around
                 break;
                 break;
+            }
+        } while ((!children.get(tabOrder).enabled)
+            && !(children.get(tabOrder) instanceof THScroller)
+            && !(children.get(tabOrder) instanceof TVScroller));
 
 
-            case MOUSE_UP:
-                onMouseUp(mouse);
-                break;
+        assert (children.get(tabOrder).enabled);
 
 
-            case MOUSE_MOTION:
-                onMouseMotion(mouse);
-                break;
+        activeChild.active = false;
+        children.get(tabOrder).active = true;
+        activeChild = children.get(tabOrder);
+    }
 
 
-            case MOUSE_DOUBLE_CLICK:
-                onMouseDoubleClick(mouse);
-                break;
+    /**
+     * Returns my active widget.
+     *
+     * @return widget that is active, or this if no children
+     */
+    public TWidget getActiveChild() {
+        if ((this instanceof THScroller)
+            || (this instanceof TVScroller)
+        ) {
+            return parent;
+        }
 
 
-            default:
-                throw new IllegalArgumentException("Invalid mouse event type: "
-                    + mouse.getType());
+        for (TWidget widget: children) {
+            if (widget.active) {
+                return widget.getActiveChild();
             }
             }
-        } else if (event instanceof TResizeEvent) {
-            onResize((TResizeEvent) event);
-        } else if (event instanceof TCommandEvent) {
-            onCommand((TCommandEvent) event);
-        } else if (event instanceof TMenuEvent) {
-            onMenu((TMenuEvent) event);
         }
         }
-
-        // Do nothing else
-        return;
+        // No active children, return me
+        return this;
     }
 
     // ------------------------------------------------------------------------
     }
 
     // ------------------------------------------------------------------------
@@ -1421,7 +1440,8 @@ public abstract class TWidget implements Comparable<TWidget> {
     }
 
     /**
     }
 
     /**
-     * Convenience function to add a tree view to this container/window.
+     * Convenience function to add a scrollable tree view to this
+     * container/window.
      *
      * @param x column relative to parent
      * @param y row relative to parent
      *
      * @param x column relative to parent
      * @param y row relative to parent
@@ -1429,14 +1449,15 @@ public abstract class TWidget implements Comparable<TWidget> {
      * @param height height of tree view
      * @return the new tree view
      */
      * @param height height of tree view
      * @return the new tree view
      */
-    public final TTreeView addTreeView(final int x, final int y,
+    public final TTreeViewWidget addTreeViewWidget(final int x, final int y,
         final int width, final int height) {
 
         final int width, final int height) {
 
-        return new TTreeView(this, x, y, width, height);
+        return new TTreeViewWidget(this, x, y, width, height);
     }
 
     /**
     }
 
     /**
-     * Convenience function to add a tree view to this container/window.
+     * Convenience function to add a scrollable tree view to this
+     * container/window.
      *
      * @param x column relative to parent
      * @param y row relative to parent
      *
      * @param x column relative to parent
      * @param y row relative to parent
@@ -1445,10 +1466,10 @@ public abstract class TWidget implements Comparable<TWidget> {
      * @param action action to perform when an item is selected
      * @return the new tree view
      */
      * @param action action to perform when an item is selected
      * @return the new tree view
      */
-    public final TTreeView addTreeView(final int x, final int y,
+    public final TTreeViewWidget addTreeViewWidget(final int x, final int y,
         final int width, final int height, final TAction action) {
 
         final int width, final int height, final TAction action) {
 
-        return new TTreeView(this, x, y, width, height, action);
+        return new TTreeViewWidget(this, x, y, width, height, action);
     }
 
     /**
     }
 
     /**
index 19c96fd141d2550209a9ca8fb14ae53bcbfbc378..140a38aa263f1c2138aed7ff558ada47df9507e1 100644 (file)
@@ -29,6 +29,7 @@
 package jexer;
 
 import java.util.HashSet;
 package jexer;
 
 import java.util.HashSet;
+import java.util.Set;
 
 import jexer.backend.Screen;
 import jexer.bits.Cell;
 
 import jexer.backend.Screen;
 import jexer.bits.Cell;
@@ -49,7 +50,7 @@ import static jexer.TKeypress.*;
 public class TWindow extends TWidget {
 
     // ------------------------------------------------------------------------
 public class TWindow extends TWidget {
 
     // ------------------------------------------------------------------------
-    // Public constants -------------------------------------------------------
+    // Constants --------------------------------------------------------------
     // ------------------------------------------------------------------------
 
     /**
     // ------------------------------------------------------------------------
 
     /**
@@ -78,162 +79,48 @@ public class TWindow extends TWidget {
      */
     public static final int NOZOOMBOX   = 0x10;
 
      */
     public static final int NOZOOMBOX   = 0x10;
 
-    // ------------------------------------------------------------------------
-    // Common window attributes -----------------------------------------------
-    // ------------------------------------------------------------------------
-
-    /**
-     * Window flags.  Note package private access.
-     */
-    int flags = RESIZABLE;
-
     /**
     /**
-     * Window title.
-     */
-    private String title = "";
-
-    /**
-     * Get window title.
-     *
-     * @return window title
+     * Window is placed at absolute position (no smart placement) (default
+     * no).
      */
      */
-    public final String getTitle() {
-        return title;
-    }
+    public static final int ABSOLUTEXY  = 0x20;
 
     /**
 
     /**
-     * Set window title.
-     *
-     * @param title new window title
+     * Hitting the closebox with the mouse calls TApplication.hideWindow()
+     * rather than TApplication.closeWindow() (default no).
      */
      */
-    public final void setTitle(final String title) {
-        this.title = title;
-    }
+    public static final int HIDEONCLOSE = 0x40;
 
     // ------------------------------------------------------------------------
 
     // ------------------------------------------------------------------------
-    // TApplication integration -----------------------------------------------
+    // Variables --------------------------------------------------------------
     // ------------------------------------------------------------------------
 
     /**
     // ------------------------------------------------------------------------
 
     /**
-     * Window's parent TApplication.
+     * Window flags.  Note package private access.
      */
      */
-    private TApplication application;
+    int flags = RESIZABLE;
 
     /**
 
     /**
-     * Get this TWindow's parent TApplication.
-     *
-     * @return this TWindow's parent TApplication
+     * Window title.
      */
      */
-    @Override
-    public final TApplication getApplication() {
-        return application;
-    }
+    private String title = "";
 
     /**
 
     /**
-     * Get the Screen.
-     *
-     * @return the Screen
+     * Window's parent TApplication.
      */
      */
-    @Override
-    public final Screen getScreen() {
-        return application.getScreen();
-    }
+    private TApplication application;
 
     /**
      * Z order.  Lower number means more in-front.
      */
     private int z = 0;
 
 
     /**
      * Z order.  Lower number means more in-front.
      */
     private int z = 0;
 
-    /**
-     * Get Z order.  Lower number means more in-front.
-     *
-     * @return Z value.  Lower number means more in-front.
-     */
-    public final int getZ() {
-        return z;
-    }
-
-    /**
-     * Set Z order.  Lower number means more in-front.
-     *
-     * @param z the new Z value.  Lower number means more in-front.
-     */
-    public final void setZ(final int z) {
-        this.z = z;
-    }
-
     /**
      * Window's keyboard shortcuts.  Any key in this set will be passed to
      * the window directly rather than processed through the menu
      * accelerators.
      */
     /**
      * Window's keyboard shortcuts.  Any key in this set will be passed to
      * the window directly rather than processed through the menu
      * accelerators.
      */
-    private HashSet<TKeypress> keyboardShortcuts = new HashSet<TKeypress>();
-
-    /**
-     * Add a keypress to be overridden for this window.
-     *
-     * @param key the key to start taking control of
-     */
-    protected void addShortcutKeypress(final TKeypress key) {
-        keyboardShortcuts.add(key);
-    }
-
-    /**
-     * Remove a keypress to be overridden for this window.
-     *
-     * @param key the key to stop taking control of
-     */
-    protected void removeShortcutKeypress(final TKeypress key) {
-        keyboardShortcuts.remove(key);
-    }
-
-    /**
-     * Remove all keypresses to be overridden for this window.
-     */
-    protected void clearShortcutKeypresses() {
-        keyboardShortcuts.clear();
-    }
-
-    /**
-     * Determine if a keypress is overridden for this window.
-     *
-     * @param key the key to check
-     * @return true if this window wants to process this key on its own
-     */
-    public boolean isShortcutKeypress(final TKeypress key) {
-        return keyboardShortcuts.contains(key);
-    }
-
-    /**
-     * A window may have a status bar associated with it.  TApplication will
-     * draw this status bar last, and will also route events to it first
-     * before the window.
-     */
-    protected TStatusBar statusBar = null;
-
-    /**
-     * Get the window's status bar, or null if it does not have one.
-     *
-     * @return the status bar, or null
-     */
-    public TStatusBar getStatusBar() {
-        return statusBar;
-    }
-
-    /**
-     * Set the window's status bar to a new one.
-     *
-     * @param text the status bar text
-     * @return the status bar
-     */
-    public TStatusBar newStatusBar(final String text) {
-        statusBar = new TStatusBar(this, text);
-        return statusBar;
-    }
-
-    // ------------------------------------------------------------------------
-    // Window movement/resizing support ---------------------------------------
-    // ------------------------------------------------------------------------
+    private Set<TKeypress> keyboardShortcuts = new HashSet<TKeypress>();
 
     /**
      * If true, then the user clicked on the title bar and is moving the
 
     /**
      * If true, then the user clicked on the title bar and is moving the
@@ -251,7 +138,7 @@ public class TWindow extends TWidget {
      * If true, then the user selected "Size/Move" (or hit Ctrl-F5) and is
      * resizing/moving the window via the keyboard.
      */
      * If true, then the user selected "Size/Move" (or hit Ctrl-F5) and is
      * resizing/moving the window via the keyboard.
      */
-    private boolean inKeyboardResize = false;
+    protected boolean inKeyboardResize = false;
 
     /**
      * If true, this window is maximized.
 
     /**
      * If true, this window is maximized.
@@ -284,226 +171,67 @@ public class TWindow extends TWidget {
     private int restoreWindowY;
 
     /**
     private int restoreWindowY;
 
     /**
-     * Set the maximum width for this window.
-     *
-     * @param maximumWindowWidth new maximum width
+     * Hidden flag.  A hidden window will still have its onIdle() called, and
+     * will also have onClose() called at application exit.  Note package
+     * private access: TApplication will force hidden false if a modal window
+     * is active.
      */
      */
-    public final void setMaximumWindowWidth(final int maximumWindowWidth) {
-        if ((maximumWindowWidth != -1)
-            && (maximumWindowWidth < minimumWindowWidth + 1)
-        ) {
-            throw new IllegalArgumentException("Maximum window width cannot " +
-                "be smaller than minimum window width + 1");
-        }
-        this.maximumWindowWidth = maximumWindowWidth;
-    }
+    boolean hidden = false;
 
     /**
 
     /**
-     * Set the minimum width for this window.
-     *
-     * @param minimumWindowWidth new minimum width
+     * A window may have a status bar associated with it.  TApplication will
+     * draw this status bar last, and will also route events to it first
+     * before the window.
      */
      */
-    public final void setMinimumWindowWidth(final int minimumWindowWidth) {
-        if ((maximumWindowWidth != -1)
-            && (minimumWindowWidth > maximumWindowWidth - 1)
-        ) {
-            throw new IllegalArgumentException("Minimum window width cannot " +
-                "be larger than maximum window width - 1");
-        }
-        this.minimumWindowWidth = minimumWindowWidth;
-    }
+    protected TStatusBar statusBar = null;
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
 
     /**
-     * Set the maximum height for this window.
+     * Public constructor.  Window will be located at (0, 0).
      *
      *
-     * @param maximumWindowHeight new maximum height
+     * @param application TApplication that manages this window
+     * @param title window title, will be centered along the top border
+     * @param width width of window
+     * @param height height of window
      */
      */
-    public final void setMaximumWindowHeight(final int maximumWindowHeight) {
-        if ((maximumWindowHeight != -1)
-            && (maximumWindowHeight < minimumWindowHeight + 1)
-        ) {
-            throw new IllegalArgumentException("Maximum window height cannot " +
-                "be smaller than minimum window height + 1");
-        }
-        this.maximumWindowHeight = maximumWindowHeight;
+    public TWindow(final TApplication application, final String title,
+        final int width, final int height) {
+
+        this(application, title, 0, 0, width, height, RESIZABLE);
     }
 
     /**
     }
 
     /**
-     * Set the minimum height for this window.
+     * Public constructor.  Window will be located at (0, 0).
      *
      *
-     * @param minimumWindowHeight new minimum height
+     * @param application TApplication that manages this window
+     * @param title window title, will be centered along the top border
+     * @param width width of window
+     * @param height height of window
+     * @param flags bitmask of RESIZABLE, CENTERED, or MODAL
      */
      */
-    public final void setMinimumWindowHeight(final int minimumWindowHeight) {
-        if ((maximumWindowHeight != -1)
-            && (minimumWindowHeight > maximumWindowHeight - 1)
-        ) {
-            throw new IllegalArgumentException("Minimum window height cannot " +
-                "be larger than maximum window height - 1");
-        }
-        this.minimumWindowHeight = minimumWindowHeight;
-    }
+    public TWindow(final TApplication application, final String title,
+        final int width, final int height, final int flags) {
 
 
-    /**
-     * Recenter the window on-screen.
-     */
-    public final void center() {
-        if ((flags & CENTERED) != 0) {
-            if (getWidth() < getScreen().getWidth()) {
-                setX((getScreen().getWidth() - getWidth()) / 2);
-            } else {
-                setX(0);
-            }
-            setY(((application.getDesktopBottom()
-                    - application.getDesktopTop()) - getHeight()) / 2);
-            if (getY() < 0) {
-                setY(0);
-            }
-            setY(getY() + application.getDesktopTop());
-        }
+        this(application, title, 0, 0, width, height, flags);
     }
 
     /**
     }
 
     /**
-     * Maximize window.
+     * Public constructor.
+     *
+     * @param application TApplication that manages this window
+     * @param title window title, will be centered along the top border
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param width width of window
+     * @param height height of window
      */
      */
-    public void maximize() {
-        if (maximized) {
-            return;
-        }
+    public TWindow(final TApplication application, final String title,
+        final int x, final int y, final int width, final int height) {
 
 
-        restoreWindowWidth = getWidth();
-        restoreWindowHeight = getHeight();
-        restoreWindowX = getX();
-        restoreWindowY = getY();
-        setWidth(getScreen().getWidth());
-        setHeight(application.getDesktopBottom() - 1);
-        setX(0);
-        setY(1);
-        maximized = true;
-    }
-
-    /**
-     * Restore (unmaximize) window.
-     */
-    public void restore() {
-        if (!maximized) {
-            return;
-        }
-
-        setWidth(restoreWindowWidth);
-        setHeight(restoreWindowHeight);
-        setX(restoreWindowX);
-        setY(restoreWindowY);
-        maximized = false;
-    }
-
-    // ------------------------------------------------------------------------
-    // Window visibility ------------------------------------------------------
-    // ------------------------------------------------------------------------
-
-    /**
-     * Hidden flag.  A hidden window will still have its onIdle() called, and
-     * will also have onClose() called at application exit.  Note package
-     * private access: TApplication will force hidden false if a modal window
-     * is active.
-     */
-    boolean hidden = false;
-
-    /**
-     * Returns true if this window is hidden.
-     *
-     * @return true if this window is hidden, false if the window is shown
-     */
-    public final boolean isHidden() {
-        return hidden;
-    }
-
-    /**
-     * Returns true if this window is shown.
-     *
-     * @return true if this window is shown, false if the window is hidden
-     */
-    public final boolean isShown() {
-        return !hidden;
-    }
-
-    /**
-     * Hide window.  A hidden window will still have its onIdle() called, and
-     * will also have onClose() called at application exit.  Hidden windows
-     * will not receive any other events.
-     */
-    public void hide() {
-        application.hideWindow(this);
-    }
-
-    /**
-     * Show window.
-     */
-    public void show() {
-        application.showWindow(this);
-    }
-
-    /**
-     * Activate window (bring to top and receive events).
-     */
-    public void activate() {
-        application.activateWindow(this);
-    }
-
-    /**
-     * Close window.  Note that windows without a close box can still be
-     * closed by calling the close() method.
-     */
-    public void close() {
-        application.closeWindow(this);
-    }
-
-    // ------------------------------------------------------------------------
-    // Constructors -----------------------------------------------------------
-    // ------------------------------------------------------------------------
-
-    /**
-     * Public constructor.  Window will be located at (0, 0).
-     *
-     * @param application TApplication that manages this window
-     * @param title window title, will be centered along the top border
-     * @param width width of window
-     * @param height height of window
-     */
-    public TWindow(final TApplication application, final String title,
-        final int width, final int height) {
-
-        this(application, title, 0, 0, width, height, RESIZABLE);
-    }
-
-    /**
-     * Public constructor.  Window will be located at (0, 0).
-     *
-     * @param application TApplication that manages this window
-     * @param title window title, will be centered along the top border
-     * @param width width of window
-     * @param height height of window
-     * @param flags bitmask of RESIZABLE, CENTERED, or MODAL
-     */
-    public TWindow(final TApplication application, final String title,
-        final int width, final int height, final int flags) {
-
-        this(application, title, 0, 0, width, height, flags);
-    }
-
-    /**
-     * Public constructor.
-     *
-     * @param application TApplication that manages this window
-     * @param title window title, will be centered along the top border
-     * @param x column relative to parent
-     * @param y row relative to parent
-     * @param width width of window
-     * @param height height of window
-     */
-    public TWindow(final TApplication application, final String title,
-        final int x, final int y, final int width, final int height) {
-
-        this(application, title, x, y, width, height, RESIZABLE);
+        this(application, title, x, y, width, height, RESIZABLE);
     }
 
     /**
     }
 
     /**
@@ -545,310 +273,96 @@ public class TWindow extends TWidget {
         center();
 
         // Add me to the application
         center();
 
         // Add me to the application
-        application.addWindow(this);
+        application.addWindowToApplication(this);
     }
 
     // ------------------------------------------------------------------------
     }
 
     // ------------------------------------------------------------------------
-    // General behavior -------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
     // ------------------------------------------------------------------------
 
     /**
     // ------------------------------------------------------------------------
 
     /**
-     * See if this window is undergoing any movement/resize/etc.
+     * Returns true if the mouse is currently on the close button.
      *
      *
-     * @return true if the window is moving
+     * @return true if mouse is currently on the close button
      */
      */
-    public boolean inMovements() {
-        if (inWindowResize || inWindowMove || inKeyboardResize) {
+    protected boolean mouseOnClose() {
+        if ((flags & NOCLOSEBOX) != 0) {
+            return false;
+        }
+        if ((mouse != null)
+            && (mouse.getAbsoluteY() == getY())
+            && (mouse.getAbsoluteX() == getX() + 3)
+        ) {
             return true;
         }
         return false;
     }
 
     /**
             return true;
         }
         return false;
     }
 
     /**
-     * Stop any pending movement/resize/etc.
-     */
-    public void stopMovements() {
-        inWindowResize = false;
-        inWindowMove = false;
-        inKeyboardResize = false;
-    }
-
-    /**
-     * Returns true if this window is modal.
+     * Returns true if the mouse is currently on the maximize/restore button.
      *
      *
-     * @return true if this window is modal
+     * @return true if the mouse is currently on the maximize/restore button
      */
      */
-    public final boolean isModal() {
-        if ((flags & MODAL) == 0) {
+    protected boolean mouseOnMaximize() {
+        if ((flags & NOZOOMBOX) != 0) {
             return false;
         }
             return false;
         }
-        return true;
+        if ((mouse != null)
+            && !isModal()
+            && (mouse.getAbsoluteY() == getY())
+            && (mouse.getAbsoluteX() == getX() + getWidth() - 4)
+        ) {
+            return true;
+        }
+        return false;
     }
 
     /**
     }
 
     /**
-     * Returns true if this window has a close box.
+     * Returns true if the mouse is currently on the resizable lower right
+     * corner.
      *
      *
-     * @return true if this window has a close box
+     * @return true if the mouse is currently on the resizable lower right
+     * corner
      */
      */
-    public final boolean hasCloseBox() {
-        if ((flags & NOCLOSEBOX) != 0) {
+    protected boolean mouseOnResize() {
+        if (((flags & RESIZABLE) != 0)
+            && !isModal()
+            && (mouse != null)
+            && (mouse.getAbsoluteY() == getY() + getHeight() - 1)
+            && ((mouse.getAbsoluteX() == getX() + getWidth() - 1)
+                || (mouse.getAbsoluteX() == getX() + getWidth() - 2))
+        ) {
             return true;
         }
         return false;
     }
 
     /**
             return true;
         }
         return false;
     }
 
     /**
-     * Returns true if this window has a maximize/zoom box.
-     *
-     * @return true if this window has a maximize/zoom box
+     * Subclasses should override this method to cleanup resources.  This is
+     * called by application.closeWindow().
      */
      */
-    public final boolean hasZoomBox() {
-        if ((flags & NOZOOMBOX) != 0) {
-            return true;
-        }
-        return false;
+    public void onClose() {
+        // Default: do nothing
     }
 
     /**
     }
 
     /**
-     * Retrieve the background color.
-     *
-     * @return the background color
+     * Called by application.switchWindow() when this window gets the
+     * focus, and also by application.addWindow().
      */
      */
-    public CellAttributes getBackground() {
-        if (!isModal()
-            && (inWindowMove || inWindowResize || inKeyboardResize)
-        ) {
-            assert (isActive());
-            return getTheme().getColor("twindow.background.windowmove");
-        } else if (isModal() && inWindowMove) {
-            assert (isActive());
-            return getTheme().getColor("twindow.background.modal");
-        } else if (isModal()) {
-            if (isActive()) {
-                return getTheme().getColor("twindow.background.modal");
-            }
-            return getTheme().getColor("twindow.background.modal.inactive");
-        } else if (isActive()) {
-            assert (!isModal());
-            return getTheme().getColor("twindow.background");
-        } else {
-            assert (!isModal());
-            return getTheme().getColor("twindow.background.inactive");
-        }
+    public void onFocus() {
+        // Default: do nothing
     }
 
     /**
     }
 
     /**
-     * Retrieve the border color.
-     *
-     * @return the border color
+     * Called by application.switchWindow() when another window gets the
+     * focus.
      */
      */
-    public CellAttributes getBorder() {
-        if (!isModal()
-            && (inWindowMove || inWindowResize || inKeyboardResize)
-        ) {
-            assert (isActive());
-            return getTheme().getColor("twindow.border.windowmove");
-        } else if (isModal() && inWindowMove) {
-            assert (isActive());
-            return getTheme().getColor("twindow.border.modal.windowmove");
-        } else if (isModal()) {
-            if (isActive()) {
-                return getTheme().getColor("twindow.border.modal");
-            } else {
-                return getTheme().getColor("twindow.border.modal.inactive");
-            }
-        } else if (isActive()) {
-            assert (!isModal());
-            return getTheme().getColor("twindow.border");
-        } else {
-            assert (!isModal());
-            return getTheme().getColor("twindow.border.inactive");
-        }
+    public void onUnfocus() {
+        // Default: do nothing
     }
 
     /**
     }
 
     /**
-     * Retrieve the border line type.
-     *
-     * @return the border line type
-     */
-    private int getBorderType() {
-        if (!isModal()
-            && (inWindowMove || inWindowResize || inKeyboardResize)
-        ) {
-            assert (isActive());
-            return 1;
-        } else if (isModal() && inWindowMove) {
-            assert (isActive());
-            return 1;
-        } else if (isModal()) {
-            if (isActive()) {
-                return 2;
-            } else {
-                return 1;
-            }
-        } else if (isActive()) {
-            return 2;
-        } else {
-            return 1;
-        }
-    }
-
-    /**
-     * Called by TApplication.drawChildren() to render on screen.
-     */
-    @Override
-    public void draw() {
-        // Draw the box and background first.
-        CellAttributes border = getBorder();
-        CellAttributes background = getBackground();
-        int borderType = getBorderType();
-
-        getScreen().drawBox(0, 0, getWidth(), getHeight(), border,
-            background, borderType, true);
-
-        // Draw the title
-        int titleLeft = (getWidth() - title.length() - 2) / 2;
-        putCharXY(titleLeft, 0, ' ', border);
-        putStringXY(titleLeft + 1, 0, title);
-        putCharXY(titleLeft + title.length() + 1, 0, ' ', border);
-
-        if (isActive()) {
-
-            // Draw the close button
-            if ((flags & NOCLOSEBOX) == 0) {
-                putCharXY(2, 0, '[', border);
-                putCharXY(4, 0, ']', border);
-                if (mouseOnClose() && mouse.isMouse1()) {
-                    putCharXY(3, 0, GraphicsChars.CP437[0x0F],
-                        !isModal()
-                        ? getTheme().getColor("twindow.border.windowmove")
-                        : getTheme().getColor("twindow.border.modal.windowmove"));
-                } else {
-                    putCharXY(3, 0, GraphicsChars.CP437[0xFE],
-                        !isModal()
-                        ? getTheme().getColor("twindow.border.windowmove")
-                        : getTheme().getColor("twindow.border.modal.windowmove"));
-                }
-            }
-
-            // Draw the maximize button
-            if (!isModal() && ((flags & NOZOOMBOX) == 0)) {
-
-                putCharXY(getWidth() - 5, 0, '[', border);
-                putCharXY(getWidth() - 3, 0, ']', border);
-                if (mouseOnMaximize() && mouse.isMouse1()) {
-                    putCharXY(getWidth() - 4, 0, GraphicsChars.CP437[0x0F],
-                        getTheme().getColor("twindow.border.windowmove"));
-                } else {
-                    if (maximized) {
-                        putCharXY(getWidth() - 4, 0, GraphicsChars.CP437[0x12],
-                            getTheme().getColor("twindow.border.windowmove"));
-                    } else {
-                        putCharXY(getWidth() - 4, 0, GraphicsChars.UPARROW,
-                            getTheme().getColor("twindow.border.windowmove"));
-                    }
-                }
-
-                // Draw the resize corner
-                if ((flags & RESIZABLE) != 0) {
-                    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"));
-                }
-            }
-        }
-    }
-
-    // ------------------------------------------------------------------------
-    // Event handlers ---------------------------------------------------------
-    // ------------------------------------------------------------------------
-
-    /**
-     * Returns true if the mouse is currently on the close button.
-     *
-     * @return true if mouse is currently on the close button
-     */
-    protected boolean mouseOnClose() {
-        if ((flags & NOCLOSEBOX) != 0) {
-            return false;
-        }
-        if ((mouse != null)
-            && (mouse.getAbsoluteY() == getY())
-            && (mouse.getAbsoluteX() == getX() + 3)
-        ) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Returns true if the mouse is currently on the maximize/restore button.
-     *
-     * @return true if the mouse is currently on the maximize/restore button
-     */
-    protected boolean mouseOnMaximize() {
-        if ((flags & NOZOOMBOX) != 0) {
-            return false;
-        }
-        if ((mouse != null)
-            && !isModal()
-            && (mouse.getAbsoluteY() == getY())
-            && (mouse.getAbsoluteX() == getX() + getWidth() - 4)
-        ) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Returns true if the mouse is currently on the resizable lower right
-     * corner.
-     *
-     * @return true if the mouse is currently on the resizable lower right
-     * corner
-     */
-    protected boolean mouseOnResize() {
-        if (((flags & RESIZABLE) != 0)
-            && !isModal()
-            && (mouse != null)
-            && (mouse.getAbsoluteY() == getY() + getHeight() - 1)
-            && ((mouse.getAbsoluteX() == getX() + getWidth() - 1)
-                || (mouse.getAbsoluteX() == getX() + getWidth() - 2))
-        ) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Subclasses should override this method to cleanup resources.  This is
-     * called by application.closeWindow().
-     */
-    public void onClose() {
-        // Default: do nothing
-    }
-
-    /**
-     * Called by application.switchWindow() when this window gets the
-     * focus, and also by application.addWindow().
-     */
-    public void onFocus() {
-        // Default: do nothing
-    }
-
-    /**
-     * Called by application.switchWindow() when another window gets the
-     * focus.
-     */
-    public void onUnfocus() {
-        // Default: do nothing
-    }
-
-    /**
-     * Called by application.hideWindow().
+     * Called by application.hideWindow().
      */
     public void onHide() {
         // Default: do nothing
      */
     public void onHide() {
         // Default: do nothing
@@ -936,8 +450,13 @@ public class TWindow extends TWidget {
         }
 
         if (mouse.isMouse1() && mouseOnClose()) {
         }
 
         if (mouse.isMouse1() && mouseOnClose()) {
-            // Close window
-            application.closeWindow(this);
+            if ((flags & HIDEONCLOSE) == 0) {
+                // Close window
+                application.closeWindow(this);
+            } else {
+                // Hide window
+                application.hideWindow(this);
+            }
             return;
         }
 
             return;
         }
 
@@ -1147,7 +666,13 @@ public class TWindow extends TWidget {
             // Ctrl-W - close window
             if (keypress.equals(kbCtrlW)) {
                 if ((flags & NOCLOSEBOX) == 0) {
             // Ctrl-W - close window
             if (keypress.equals(kbCtrlW)) {
                 if ((flags & NOCLOSEBOX) == 0) {
-                    application.closeWindow(this);
+                    if ((flags & HIDEONCLOSE) == 0) {
+                        // Close window
+                        application.closeWindow(this);
+                    } else {
+                        // Hide window
+                        application.hideWindow(this);
+                    }
                 }
                 return;
             }
                 }
                 return;
             }
@@ -1200,7 +725,13 @@ public class TWindow extends TWidget {
 
             if (command.equals(cmWindowClose)) {
                 if ((flags & NOCLOSEBOX) == 0) {
 
             if (command.equals(cmWindowClose)) {
                 if ((flags & NOCLOSEBOX) == 0) {
-                    application.closeWindow(this);
+                    if ((flags & HIDEONCLOSE) == 0) {
+                        // Close window
+                        application.closeWindow(this);
+                    } else {
+                        // Hide window
+                        application.hideWindow(this);
+                    }
                 }
                 return;
             }
                 }
                 return;
             }
@@ -1246,7 +777,13 @@ public class TWindow extends TWidget {
 
             if (menu.getId() == TMenu.MID_WINDOW_CLOSE) {
                 if ((flags & NOCLOSEBOX) == 0) {
 
             if (menu.getId() == TMenu.MID_WINDOW_CLOSE) {
                 if ((flags & NOCLOSEBOX) == 0) {
-                    application.closeWindow(this);
+                    if ((flags & HIDEONCLOSE) == 0) {
+                        // Close window
+                        application.closeWindow(this);
+                    } else {
+                        // Hide window
+                        application.hideWindow(this);
+                    }
                 }
                 return;
             }
                 }
                 return;
             }
@@ -1283,6 +820,509 @@ public class TWindow extends TWidget {
         super.onMenu(menu);
     }
 
         super.onMenu(menu);
     }
 
+    // ------------------------------------------------------------------------
+    // TWidget ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Get this TWindow's parent TApplication.
+     *
+     * @return this TWindow's parent TApplication
+     */
+    @Override
+    public final TApplication getApplication() {
+        return application;
+    }
+
+    /**
+     * Get the Screen.
+     *
+     * @return the Screen
+     */
+    @Override
+    public final Screen getScreen() {
+        return application.getScreen();
+    }
+
+    /**
+     * Called by TApplication.drawChildren() to render on screen.
+     */
+    @Override
+    public void draw() {
+        // Draw the box and background first.
+        CellAttributes border = getBorder();
+        CellAttributes background = getBackground();
+        int borderType = getBorderType();
+
+        getScreen().drawBox(0, 0, getWidth(), getHeight(), border,
+            background, borderType, true);
+
+        // Draw the title
+        int titleLeft = (getWidth() - title.length() - 2) / 2;
+        putCharXY(titleLeft, 0, ' ', border);
+        putStringXY(titleLeft + 1, 0, title);
+        putCharXY(titleLeft + title.length() + 1, 0, ' ', border);
+
+        if (isActive()) {
+
+            // Draw the close button
+            if ((flags & NOCLOSEBOX) == 0) {
+                putCharXY(2, 0, '[', border);
+                putCharXY(4, 0, ']', border);
+                if (mouseOnClose() && mouse.isMouse1()) {
+                    putCharXY(3, 0, GraphicsChars.CP437[0x0F],
+                        getBorderControls());
+                } else {
+                    putCharXY(3, 0, GraphicsChars.CP437[0xFE],
+                        getBorderControls());
+                }
+            }
+
+            // Draw the maximize button
+            if (!isModal() && ((flags & NOZOOMBOX) == 0)) {
+
+                putCharXY(getWidth() - 5, 0, '[', border);
+                putCharXY(getWidth() - 3, 0, ']', border);
+                if (mouseOnMaximize() && mouse.isMouse1()) {
+                    putCharXY(getWidth() - 4, 0, GraphicsChars.CP437[0x0F],
+                        getBorderControls());
+                } else {
+                    if (maximized) {
+                        putCharXY(getWidth() - 4, 0, GraphicsChars.CP437[0x12],
+                            getBorderControls());
+                    } else {
+                        putCharXY(getWidth() - 4, 0, GraphicsChars.UPARROW,
+                            getBorderControls());
+                    }
+                }
+
+                // Draw the resize corner
+                if ((flags & RESIZABLE) != 0) {
+                    putCharXY(getWidth() - 2, getHeight() - 1,
+                        GraphicsChars.SINGLE_BAR, getBorderControls());
+                    putCharXY(getWidth() - 1, getHeight() - 1,
+                        GraphicsChars.LRCORNER, getBorderControls());
+                }
+            }
+        }
+    }
+
+    // ------------------------------------------------------------------------
+    // TWindow ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Get window title.
+     *
+     * @return window title
+     */
+    public final String getTitle() {
+        return title;
+    }
+
+    /**
+     * Set window title.
+     *
+     * @param title new window title
+     */
+    public final void setTitle(final String title) {
+        this.title = title;
+    }
+
+    /**
+     * Get Z order.  Lower number means more in-front.
+     *
+     * @return Z value.  Lower number means more in-front.
+     */
+    public final int getZ() {
+        return z;
+    }
+
+    /**
+     * Set Z order.  Lower number means more in-front.
+     *
+     * @param z the new Z value.  Lower number means more in-front.
+     */
+    public final void setZ(final int z) {
+        this.z = z;
+    }
+
+    /**
+     * Add a keypress to be overridden for this window.
+     *
+     * @param key the key to start taking control of
+     */
+    protected void addShortcutKeypress(final TKeypress key) {
+        keyboardShortcuts.add(key);
+    }
+
+    /**
+     * Remove a keypress to be overridden for this window.
+     *
+     * @param key the key to stop taking control of
+     */
+    protected void removeShortcutKeypress(final TKeypress key) {
+        keyboardShortcuts.remove(key);
+    }
+
+    /**
+     * Remove all keypresses to be overridden for this window.
+     */
+    protected void clearShortcutKeypresses() {
+        keyboardShortcuts.clear();
+    }
+
+    /**
+     * Determine if a keypress is overridden for this window.
+     *
+     * @param key the key to check
+     * @return true if this window wants to process this key on its own
+     */
+    public boolean isShortcutKeypress(final TKeypress key) {
+        return keyboardShortcuts.contains(key);
+    }
+
+    /**
+     * Get the window's status bar, or null if it does not have one.
+     *
+     * @return the status bar, or null
+     */
+    public TStatusBar getStatusBar() {
+        return statusBar;
+    }
+
+    /**
+     * Set the window's status bar to a new one.
+     *
+     * @param text the status bar text
+     * @return the status bar
+     */
+    public TStatusBar newStatusBar(final String text) {
+        statusBar = new TStatusBar(this, text);
+        return statusBar;
+    }
+
+    /**
+     * Set the maximum width for this window.
+     *
+     * @param maximumWindowWidth new maximum width
+     */
+    public final void setMaximumWindowWidth(final int maximumWindowWidth) {
+        if ((maximumWindowWidth != -1)
+            && (maximumWindowWidth < minimumWindowWidth + 1)
+        ) {
+            throw new IllegalArgumentException("Maximum window width cannot " +
+                "be smaller than minimum window width + 1");
+        }
+        this.maximumWindowWidth = maximumWindowWidth;
+    }
+
+    /**
+     * Set the minimum width for this window.
+     *
+     * @param minimumWindowWidth new minimum width
+     */
+    public final void setMinimumWindowWidth(final int minimumWindowWidth) {
+        if ((maximumWindowWidth != -1)
+            && (minimumWindowWidth > maximumWindowWidth - 1)
+        ) {
+            throw new IllegalArgumentException("Minimum window width cannot " +
+                "be larger than maximum window width - 1");
+        }
+        this.minimumWindowWidth = minimumWindowWidth;
+    }
+
+    /**
+     * Set the maximum height for this window.
+     *
+     * @param maximumWindowHeight new maximum height
+     */
+    public final void setMaximumWindowHeight(final int maximumWindowHeight) {
+        if ((maximumWindowHeight != -1)
+            && (maximumWindowHeight < minimumWindowHeight + 1)
+        ) {
+            throw new IllegalArgumentException("Maximum window height cannot " +
+                "be smaller than minimum window height + 1");
+        }
+        this.maximumWindowHeight = maximumWindowHeight;
+    }
+
+    /**
+     * Set the minimum height for this window.
+     *
+     * @param minimumWindowHeight new minimum height
+     */
+    public final void setMinimumWindowHeight(final int minimumWindowHeight) {
+        if ((maximumWindowHeight != -1)
+            && (minimumWindowHeight > maximumWindowHeight - 1)
+        ) {
+            throw new IllegalArgumentException("Minimum window height cannot " +
+                "be larger than maximum window height - 1");
+        }
+        this.minimumWindowHeight = minimumWindowHeight;
+    }
+
+    /**
+     * Recenter the window on-screen.
+     */
+    public final void center() {
+        if ((flags & CENTERED) != 0) {
+            if (getWidth() < getScreen().getWidth()) {
+                setX((getScreen().getWidth() - getWidth()) / 2);
+            } else {
+                setX(0);
+            }
+            setY(((application.getDesktopBottom()
+                    - application.getDesktopTop()) - getHeight()) / 2);
+            if (getY() < 0) {
+                setY(0);
+            }
+            setY(getY() + application.getDesktopTop());
+        }
+    }
+
+    /**
+     * Maximize window.
+     */
+    public void maximize() {
+        if (maximized) {
+            return;
+        }
+
+        restoreWindowWidth = getWidth();
+        restoreWindowHeight = getHeight();
+        restoreWindowX = getX();
+        restoreWindowY = getY();
+        setWidth(getScreen().getWidth());
+        setHeight(application.getDesktopBottom() - 1);
+        setX(0);
+        setY(1);
+        maximized = true;
+
+        onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, getWidth(),
+                getHeight()));
+    }
+
+    /**
+     * Restore (unmaximize) window.
+     */
+    public void restore() {
+        if (!maximized) {
+            return;
+        }
+
+        setWidth(restoreWindowWidth);
+        setHeight(restoreWindowHeight);
+        setX(restoreWindowX);
+        setY(restoreWindowY);
+        maximized = false;
+
+        onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, getWidth(),
+                getHeight()));
+    }
+
+    /**
+     * Returns true if this window is hidden.
+     *
+     * @return true if this window is hidden, false if the window is shown
+     */
+    public final boolean isHidden() {
+        return hidden;
+    }
+
+    /**
+     * Returns true if this window is shown.
+     *
+     * @return true if this window is shown, false if the window is hidden
+     */
+    public final boolean isShown() {
+        return !hidden;
+    }
+
+    /**
+     * Hide window.  A hidden window will still have its onIdle() called, and
+     * will also have onClose() called at application exit.  Hidden windows
+     * will not receive any other events.
+     */
+    public void hide() {
+        application.hideWindow(this);
+    }
+
+    /**
+     * Show window.
+     */
+    public void show() {
+        application.showWindow(this);
+    }
+
+    /**
+     * Activate window (bring to top and receive events).
+     */
+    public void activate() {
+        application.activateWindow(this);
+    }
+
+    /**
+     * Close window.  Note that windows without a close box can still be
+     * closed by calling the close() method.
+     */
+    public void close() {
+        application.closeWindow(this);
+    }
+
+    /**
+     * See if this window is undergoing any movement/resize/etc.
+     *
+     * @return true if the window is moving
+     */
+    public boolean inMovements() {
+        if (inWindowResize || inWindowMove || inKeyboardResize) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Stop any pending movement/resize/etc.
+     */
+    public void stopMovements() {
+        inWindowResize = false;
+        inWindowMove = false;
+        inKeyboardResize = false;
+    }
+
+    /**
+     * Returns true if this window is modal.
+     *
+     * @return true if this window is modal
+     */
+    public final boolean isModal() {
+        if ((flags & MODAL) == 0) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Returns true if this window has a close box.
+     *
+     * @return true if this window has a close box
+     */
+    public final boolean hasCloseBox() {
+        if ((flags & NOCLOSEBOX) != 0) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if this window has a maximize/zoom box.
+     *
+     * @return true if this window has a maximize/zoom box
+     */
+    public final boolean hasZoomBox() {
+        if ((flags & NOZOOMBOX) != 0) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Retrieve the background color.
+     *
+     * @return the background color
+     */
+    public CellAttributes getBackground() {
+        if (!isModal()
+            && (inWindowMove || inWindowResize || inKeyboardResize)
+        ) {
+            assert (isActive());
+            return getTheme().getColor("twindow.background.windowmove");
+        } else if (isModal() && inWindowMove) {
+            assert (isActive());
+            return getTheme().getColor("twindow.background.modal");
+        } else if (isModal()) {
+            if (isActive()) {
+                return getTheme().getColor("twindow.background.modal");
+            }
+            return getTheme().getColor("twindow.background.modal.inactive");
+        } else if (isActive()) {
+            assert (!isModal());
+            return getTheme().getColor("twindow.background");
+        } else {
+            assert (!isModal());
+            return getTheme().getColor("twindow.background.inactive");
+        }
+    }
+
+    /**
+     * Retrieve the border color.
+     *
+     * @return the border color
+     */
+    public CellAttributes getBorder() {
+        if (!isModal()
+            && (inWindowMove || inWindowResize || inKeyboardResize)
+        ) {
+            assert (isActive());
+            return getTheme().getColor("twindow.border.windowmove");
+        } else if (isModal() && inWindowMove) {
+            assert (isActive());
+            return getTheme().getColor("twindow.border.modal.windowmove");
+        } else if (isModal()) {
+            if (isActive()) {
+                return getTheme().getColor("twindow.border.modal");
+            } else {
+                return getTheme().getColor("twindow.border.modal.inactive");
+            }
+        } else if (isActive()) {
+            assert (!isModal());
+            return getTheme().getColor("twindow.border");
+        } else {
+            assert (!isModal());
+            return getTheme().getColor("twindow.border.inactive");
+        }
+    }
+
+    /**
+     * Retrieve the color used by the window movement/sizing controls.
+     *
+     * @return the color used by the zoom box, resize bar, and close box
+     */
+    public CellAttributes getBorderControls() {
+        if (isModal()) {
+            return getTheme().getColor("twindow.border.modal.windowmove");
+        }
+        return getTheme().getColor("twindow.border.windowmove");
+    }
+
+    /**
+     * Retrieve the border line type.
+     *
+     * @return the border line type
+     */
+    private int getBorderType() {
+        if (!isModal()
+            && (inWindowMove || inWindowResize || inKeyboardResize)
+        ) {
+            assert (isActive());
+            return 1;
+        } else if (isModal() && inWindowMove) {
+            assert (isActive());
+            return 1;
+        } else if (isModal()) {
+            if (isActive()) {
+                return 2;
+            } else {
+                return 1;
+            }
+        } else if (isActive()) {
+            return 2;
+        } else {
+            return 1;
+        }
+    }
+
     // ------------------------------------------------------------------------
     // Passthru for Screen functions ------------------------------------------
     // ------------------------------------------------------------------------
     // ------------------------------------------------------------------------
     // Passthru for Screen functions ------------------------------------------
     // ------------------------------------------------------------------------
index d1863288c54dabd8dfe4f0bb5b59f9356397db8b..20efa7eeaec8ad913387a4a546dcab512f41d512 100644 (file)
@@ -60,6 +60,13 @@ public interface Backend {
      */
     public void flushScreen();
 
      */
     public void flushScreen();
 
+    /**
+     * Check if there are events in the queue.
+     *
+     * @return if true, getEvents() has something to return to the application
+     */
+    public boolean hasEvents();
+
     /**
      * Classes must provide an implementation to get keyboard, mouse, and
      * screen resize events.
     /**
      * Classes must provide an implementation to get keyboard, mouse, and
      * screen resize events.
index 59cd670552b482b201fca443c966763988ab086c..b99c12014edc85506b6a867534159387bbab4bca 100644 (file)
@@ -40,6 +40,10 @@ import java.io.UnsupportedEncodingException;
  */
 public final class ECMA48Backend extends GenericBackend {
 
  */
 public final class ECMA48Backend extends GenericBackend {
 
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Public constructor will use System.in and System.out and UTF-8
      * encoding. On non-Windows systems System.in will be put in raw mode;
     /**
      * Public constructor will use System.in and System.out and UTF-8
      * encoding. On non-Windows systems System.in will be put in raw mode;
@@ -161,5 +165,4 @@ public final class ECMA48Backend extends GenericBackend {
         this(listener, input, reader, writer, false);
     }
 
         this(listener, input, reader, writer, false);
     }
 
-
 }
 }
index ae356107ee9c05f3d049f8bbb8b969a1aea50135..360994a1841bea9bdf820d0d74bf9f5b5e7d631c 100644 (file)
@@ -59,6 +59,27 @@ import static jexer.TKeypress.*;
 public final class ECMA48Terminal extends LogicalScreen
                                   implements TerminalReader, Runnable {
 
 public final class ECMA48Terminal extends LogicalScreen
                                   implements TerminalReader, Runnable {
 
+    // ------------------------------------------------------------------------
+    // Constants --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * States in the input parser.
+     */
+    private enum ParseState {
+        GROUND,
+        ESCAPE,
+        ESCAPE_INTERMEDIATE,
+        CSI_ENTRY,
+        CSI_PARAM,
+        MOUSE,
+        MOUSE_SGR,
+    }
+
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Emit debugging to stderr.
      */
     /**
      * Emit debugging to stderr.
      */
@@ -75,15 +96,6 @@ public final class ECMA48Terminal extends LogicalScreen
      */
     private SessionInfo sessionInfo;
 
      */
     private SessionInfo sessionInfo;
 
-    /**
-     * Getter for sessionInfo.
-     *
-     * @return the SessionInfo
-     */
-    public SessionInfo getSessionInfo() {
-        return sessionInfo;
-    }
-
     /**
      * The event queue, filled up by a thread reading on input.
      */
     /**
      * The event queue, filled up by a thread reading on input.
      */
@@ -103,20 +115,7 @@ public final class ECMA48Terminal extends LogicalScreen
      * Parameters being collected.  E.g. if the string is \033[1;3m, then
      * params[0] will be 1 and params[1] will be 3.
      */
      * Parameters being collected.  E.g. if the string is \033[1;3m, then
      * params[0] will be 1 and params[1] will be 3.
      */
-    private ArrayList<String> params;
-
-    /**
-     * States in the input parser.
-     */
-    private enum ParseState {
-        GROUND,
-        ESCAPE,
-        ESCAPE_INTERMEDIATE,
-        CSI_ENTRY,
-        CSI_PARAM,
-        MOUSE,
-        MOUSE_SGR,
-    }
+    private List<String> params;
 
     /**
      * Current parsing state.
 
     /**
      * Current parsing state.
@@ -194,101 +193,9 @@ public final class ECMA48Terminal extends LogicalScreen
      */
     private Object listener;
 
      */
     private Object listener;
 
-    /**
-     * Set listener to a different Object.
-     *
-     * @param listener the new listening object that run() wakes up on new
-     * input
-     */
-    public void setListener(final Object listener) {
-        this.listener = listener;
-    }
-
-    /**
-     * Get the output writer.
-     *
-     * @return the Writer
-     */
-    public PrintWriter getOutput() {
-        return output;
-    }
-
-    /**
-     * Check if there are events in the queue.
-     *
-     * @return if true, getEvents() has something to return to the backend
-     */
-    public boolean hasEvents() {
-        synchronized (eventQueue) {
-            return (eventQueue.size() > 0);
-        }
-    }
-
-    /**
-     * Call 'stty' to set cooked mode.
-     *
-     * <p>Actually executes '/bin/sh -c stty sane cooked &lt; /dev/tty'
-     */
-    private void sttyCooked() {
-        doStty(false);
-    }
-
-    /**
-     * Call 'stty' to set raw mode.
-     *
-     * <p>Actually executes '/bin/sh -c stty -ignbrk -brkint -parmrk -istrip
-     * -inlcr -igncr -icrnl -ixon -opost -echo -echonl -icanon -isig -iexten
-     * -parenb cs8 min 1 &lt; /dev/tty'
-     */
-    private void sttyRaw() {
-        doStty(true);
-    }
-
-    /**
-     * Call 'stty' to set raw or cooked mode.
-     *
-     * @param mode if true, set raw mode, otherwise set cooked mode
-     */
-    private void doStty(final boolean mode) {
-        String [] cmdRaw = {
-            "/bin/sh", "-c", "stty -ignbrk -brkint -parmrk -istrip -inlcr -igncr -icrnl -ixon -opost -echo -echonl -icanon -isig -iexten -parenb cs8 min 1 < /dev/tty"
-        };
-        String [] cmdCooked = {
-            "/bin/sh", "-c", "stty sane cooked < /dev/tty"
-        };
-        try {
-            Process process;
-            if (mode) {
-                process = Runtime.getRuntime().exec(cmdRaw);
-            } else {
-                process = Runtime.getRuntime().exec(cmdCooked);
-            }
-            BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8"));
-            String line = in.readLine();
-            if ((line != null) && (line.length() > 0)) {
-                System.err.println("WEIRD?! Normal output from stty: " + line);
-            }
-            while (true) {
-                BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream(), "UTF-8"));
-                line = err.readLine();
-                if ((line != null) && (line.length() > 0)) {
-                    System.err.println("Error output from stty: " + line);
-                }
-                try {
-                    process.waitFor();
-                    break;
-                } catch (InterruptedException e) {
-                    e.printStackTrace();
-                }
-            }
-            int rc = process.exitValue();
-            if (rc != 0) {
-                System.err.println("stty returned error code: " + rc);
-            }
-        } catch (IOException e) {
-            e.printStackTrace();
-        }
-    }
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Constructor sets up state for getEvent().
 
     /**
      * Constructor sets up state for getEvent().
@@ -517,6 +424,73 @@ public final class ECMA48Terminal extends LogicalScreen
         this(listener, input, reader, writer, false);
     }
 
         this(listener, input, reader, writer, false);
     }
 
+    // ------------------------------------------------------------------------
+    // LogicalScreen ----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Set the window title.
+     *
+     * @param title the new title
+     */
+    @Override
+    public void setTitle(final String title) {
+        output.write(getSetTitleString(title));
+        flush();
+    }
+
+    /**
+     * Push the logical screen to the physical device.
+     */
+    @Override
+    public void flushPhysical() {
+        String result = flushString();
+        if ((cursorVisible)
+            && (cursorY >= 0)
+            && (cursorX >= 0)
+            && (cursorY <= height - 1)
+            && (cursorX <= width - 1)
+        ) {
+            result += cursor(true);
+            result += gotoXY(cursorX, cursorY);
+        } else {
+            result += cursor(false);
+        }
+        output.write(result);
+        flush();
+    }
+
+    // ------------------------------------------------------------------------
+    // TerminalReader ---------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Check if there are events in the queue.
+     *
+     * @return if true, getEvents() has something to return to the backend
+     */
+    public boolean hasEvents() {
+        synchronized (eventQueue) {
+            return (eventQueue.size() > 0);
+        }
+    }
+
+    /**
+     * Return any events in the IO queue.
+     *
+     * @param queue list to append new events to
+     */
+    public void getEvents(final List<TInputEvent> queue) {
+        synchronized (eventQueue) {
+            if (eventQueue.size() > 0) {
+                synchronized (queue) {
+                    queue.addAll(eventQueue);
+                }
+                eventQueue.clear();
+            }
+        }
+    }
+
     /**
      * Restore terminal to normal state.
      */
     /**
      * Restore terminal to normal state.
      */
@@ -558,6 +532,195 @@ public final class ECMA48Terminal extends LogicalScreen
         }
     }
 
         }
     }
 
+    /**
+     * Set listener to a different Object.
+     *
+     * @param listener the new listening object that run() wakes up on new
+     * input
+     */
+    public void setListener(final Object listener) {
+        this.listener = listener;
+    }
+
+    // ------------------------------------------------------------------------
+    // Runnable ---------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Read function runs on a separate thread.
+     */
+    public void run() {
+        boolean done = false;
+        // available() will often return > 1, so we need to read in chunks to
+        // stay caught up.
+        char [] readBuffer = new char[128];
+        List<TInputEvent> events = new LinkedList<TInputEvent>();
+
+        while (!done && !stopReaderThread) {
+            try {
+                // We assume that if inputStream has bytes available, then
+                // input won't block on read().
+                int n = inputStream.available();
+
+                /*
+                System.err.printf("inputStream.available(): %d\n", n);
+                System.err.flush();
+                */
+
+                if (n > 0) {
+                    if (readBuffer.length < n) {
+                        // The buffer wasn't big enough, make it huger
+                        readBuffer = new char[readBuffer.length * 2];
+                    }
+
+                    // System.err.printf("BEFORE read()\n"); System.err.flush();
+
+                    int rc = input.read(readBuffer, 0, readBuffer.length);
+
+                    /*
+                    System.err.printf("AFTER read() %d\n", rc);
+                    System.err.flush();
+                    */
+
+                    if (rc == -1) {
+                        // This is EOF
+                        done = true;
+                    } else {
+                        for (int i = 0; i < rc; i++) {
+                            int ch = readBuffer[i];
+                            processChar(events, (char)ch);
+                        }
+                        getIdleEvents(events);
+                        if (events.size() > 0) {
+                            // Add to the queue for the backend thread to
+                            // be able to obtain.
+                            synchronized (eventQueue) {
+                                eventQueue.addAll(events);
+                            }
+                            if (listener != null) {
+                                synchronized (listener) {
+                                    listener.notifyAll();
+                                }
+                            }
+                            events.clear();
+                        }
+                    }
+                } else {
+                    getIdleEvents(events);
+                    if (events.size() > 0) {
+                        synchronized (eventQueue) {
+                            eventQueue.addAll(events);
+                        }
+                        if (listener != null) {
+                            synchronized (listener) {
+                                listener.notifyAll();
+                            }
+                        }
+                        events.clear();
+                    }
+
+                    // Wait 20 millis for more data
+                    Thread.sleep(20);
+                }
+                // System.err.println("end while loop"); System.err.flush();
+            } catch (InterruptedException e) {
+                // SQUASH
+            } catch (IOException e) {
+                e.printStackTrace();
+                done = true;
+            }
+        } // while ((done == false) && (stopReaderThread == false))
+        // System.err.println("*** run() exiting..."); System.err.flush();
+    }
+
+    // ------------------------------------------------------------------------
+    // ECMA48Terminal ---------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Getter for sessionInfo.
+     *
+     * @return the SessionInfo
+     */
+    public SessionInfo getSessionInfo() {
+        return sessionInfo;
+    }
+
+    /**
+     * Get the output writer.
+     *
+     * @return the Writer
+     */
+    public PrintWriter getOutput() {
+        return output;
+    }
+
+    /**
+     * Call 'stty' to set cooked mode.
+     *
+     * <p>Actually executes '/bin/sh -c stty sane cooked &lt; /dev/tty'
+     */
+    private void sttyCooked() {
+        doStty(false);
+    }
+
+    /**
+     * Call 'stty' to set raw mode.
+     *
+     * <p>Actually executes '/bin/sh -c stty -ignbrk -brkint -parmrk -istrip
+     * -inlcr -igncr -icrnl -ixon -opost -echo -echonl -icanon -isig -iexten
+     * -parenb cs8 min 1 &lt; /dev/tty'
+     */
+    private void sttyRaw() {
+        doStty(true);
+    }
+
+    /**
+     * Call 'stty' to set raw or cooked mode.
+     *
+     * @param mode if true, set raw mode, otherwise set cooked mode
+     */
+    private void doStty(final boolean mode) {
+        String [] cmdRaw = {
+            "/bin/sh", "-c", "stty -ignbrk -brkint -parmrk -istrip -inlcr -igncr -icrnl -ixon -opost -echo -echonl -icanon -isig -iexten -parenb cs8 min 1 < /dev/tty"
+        };
+        String [] cmdCooked = {
+            "/bin/sh", "-c", "stty sane cooked < /dev/tty"
+        };
+        try {
+            Process process;
+            if (mode) {
+                process = Runtime.getRuntime().exec(cmdRaw);
+            } else {
+                process = Runtime.getRuntime().exec(cmdCooked);
+            }
+            BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8"));
+            String line = in.readLine();
+            if ((line != null) && (line.length() > 0)) {
+                System.err.println("WEIRD?! Normal output from stty: " + line);
+            }
+            while (true) {
+                BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream(), "UTF-8"));
+                line = err.readLine();
+                if ((line != null) && (line.length() > 0)) {
+                    System.err.println("Error output from stty: " + line);
+                }
+                try {
+                    process.waitFor();
+                    break;
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+            int rc = process.exitValue();
+            if (rc != 0) {
+                System.err.println("stty returned error code: " + rc);
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
     /**
      * Flush output.
      */
     /**
      * Flush output.
      */
@@ -763,37 +926,6 @@ public final class ECMA48Terminal extends LogicalScreen
         return result;
     }
 
         return result;
     }
 
-    /**
-     * Push the logical screen to the physical device.
-     */
-    @Override
-    public void flushPhysical() {
-        String result = flushString();
-        if ((cursorVisible)
-            && (cursorY >= 0)
-            && (cursorX >= 0)
-            && (cursorY <= height - 1)
-            && (cursorX <= width - 1)
-        ) {
-            result += cursor(true);
-            result += gotoXY(cursorX, cursorY);
-        } else {
-            result += cursor(false);
-        }
-        output.write(result);
-        flush();
-    }
-
-    /**
-     * Set the window title.
-     *
-     * @param title the new title
-     */
-    public void setTitle(final String title) {
-        output.write(getSetTitleString(title));
-        flush();
-    }
-
     /**
      * Reset keyboard/mouse input parser.
      */
     /**
      * Reset keyboard/mouse input parser.
      */
@@ -1108,22 +1240,6 @@ public final class ECMA48Terminal extends LogicalScreen
             eventMouseWheelUp, eventMouseWheelDown);
     }
 
             eventMouseWheelUp, eventMouseWheelDown);
     }
 
-    /**
-     * Return any events in the IO queue.
-     *
-     * @param queue list to append new events to
-     */
-    public void getEvents(final List<TInputEvent> queue) {
-        synchronized (eventQueue) {
-            if (eventQueue.size() > 0) {
-                synchronized (queue) {
-                    queue.addAll(eventQueue);
-                }
-                eventQueue.clear();
-            }
-        }
-    }
-
     /**
      * Return any events in the IO queue due to timeout.
      *
     /**
      * Return any events in the IO queue due to timeout.
      *
@@ -1895,91 +2011,4 @@ public final class ECMA48Terminal extends LogicalScreen
         return "\033[?1002;1003;1006;1005l\033[?1049l";
     }
 
         return "\033[?1002;1003;1006;1005l\033[?1049l";
     }
 
-    /**
-     * Read function runs on a separate thread.
-     */
-    public void run() {
-        boolean done = false;
-        // available() will often return > 1, so we need to read in chunks to
-        // stay caught up.
-        char [] readBuffer = new char[128];
-        List<TInputEvent> events = new LinkedList<TInputEvent>();
-
-        while (!done && !stopReaderThread) {
-            try {
-                // We assume that if inputStream has bytes available, then
-                // input won't block on read().
-                int n = inputStream.available();
-
-                /*
-                System.err.printf("inputStream.available(): %d\n", n);
-                System.err.flush();
-                */
-
-                if (n > 0) {
-                    if (readBuffer.length < n) {
-                        // The buffer wasn't big enough, make it huger
-                        readBuffer = new char[readBuffer.length * 2];
-                    }
-
-                    // System.err.printf("BEFORE read()\n"); System.err.flush();
-
-                    int rc = input.read(readBuffer, 0, readBuffer.length);
-
-                    /*
-                    System.err.printf("AFTER read() %d\n", rc);
-                    System.err.flush();
-                    */
-
-                    if (rc == -1) {
-                        // This is EOF
-                        done = true;
-                    } else {
-                        for (int i = 0; i < rc; i++) {
-                            int ch = readBuffer[i];
-                            processChar(events, (char)ch);
-                        }
-                        getIdleEvents(events);
-                        if (events.size() > 0) {
-                            // Add to the queue for the backend thread to
-                            // be able to obtain.
-                            synchronized (eventQueue) {
-                                eventQueue.addAll(events);
-                            }
-                            if (listener != null) {
-                                synchronized (listener) {
-                                    listener.notifyAll();
-                                }
-                            }
-                            events.clear();
-                        }
-                    }
-                } else {
-                    getIdleEvents(events);
-                    if (events.size() > 0) {
-                        synchronized (eventQueue) {
-                            eventQueue.addAll(events);
-                        }
-                        if (listener != null) {
-                            synchronized (listener) {
-                                listener.notifyAll();
-                            }
-                        }
-                        events.clear();
-                    }
-
-                    // Wait 20 millis for more data
-                    Thread.sleep(20);
-                }
-                // System.err.println("end while loop"); System.err.flush();
-            } catch (InterruptedException e) {
-                // SQUASH
-            } catch (IOException e) {
-                e.printStackTrace();
-                done = true;
-            }
-        } // while ((done == false) && (stopReaderThread == false))
-        // System.err.println("*** run() exiting..."); System.err.flush();
-    }
-
 }
 }
index bf27e947607f1c7bea073b4edac0689f300e6067..bb9ae9f95af83a52c618541f121df6cbb0faecc5 100644 (file)
@@ -39,11 +39,33 @@ import jexer.event.TInputEvent;
  */
 public abstract class GenericBackend implements Backend {
 
  */
 public abstract class GenericBackend implements Backend {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * The session information.
      */
     protected SessionInfo sessionInfo;
 
     /**
      * The session information.
      */
     protected SessionInfo sessionInfo;
 
+    /**
+     * The screen to draw on.
+     */
+    protected Screen screen;
+
+    /**
+     * Input events are processed by this Terminal.
+     */
+    protected TerminalReader terminal;
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    // ------------------------------------------------------------------------
+    // Backend ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Getter for sessionInfo.
      *
     /**
      * Getter for sessionInfo.
      *
@@ -53,11 +75,6 @@ public abstract class GenericBackend implements Backend {
         return sessionInfo;
     }
 
         return sessionInfo;
     }
 
-    /**
-     * The screen to draw on.
-     */
-    protected Screen screen;
-
     /**
      * Getter for screen.
      *
     /**
      * Getter for screen.
      *
@@ -75,9 +92,13 @@ public abstract class GenericBackend implements Backend {
     }
 
     /**
     }
 
     /**
-     * Input events are processed by this Terminal.
+     * Check if there are events in the queue.
+     *
+     * @return if true, getEvents() has something to return to the application
      */
      */
-    protected TerminalReader terminal;
+    public boolean hasEvents() {
+        return terminal.hasEvents();
+    }
 
     /**
      * Get keyboard, mouse, and screen resize events.
 
     /**
      * Get keyboard, mouse, and screen resize events.
index c9b025abb257cb260f1ecff0f62a97e792d4caef..c24703e7a23098740804f5be8f4873b27ceb1324 100644 (file)
@@ -37,6 +37,10 @@ import jexer.bits.GraphicsChars;
  */
 public class LogicalScreen implements Screen {
 
  */
 public class LogicalScreen implements Screen {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Width of the visible window.
      */
     /**
      * Width of the visible window.
      */
@@ -52,6 +56,84 @@ public class LogicalScreen implements Screen {
      */
     private int offsetX;
 
      */
     private int offsetX;
 
+    /**
+     * Drawing offset for y.
+     */
+    private int offsetY;
+
+    /**
+     * Ignore anything drawn right of clipRight.
+     */
+    private int clipRight;
+
+    /**
+     * Ignore anything drawn below clipBottom.
+     */
+    private int clipBottom;
+
+    /**
+     * Ignore anything drawn left of clipLeft.
+     */
+    private int clipLeft;
+
+    /**
+     * Ignore anything drawn above clipTop.
+     */
+    private int clipTop;
+
+    /**
+     * The physical screen last sent out on flush().
+     */
+    protected Cell [][] physical;
+
+    /**
+     * The logical screen being rendered to.
+     */
+    protected Cell [][] logical;
+
+    /**
+     * Set if the user explicitly wants to redraw everything starting with a
+     * ECMATerminal.clearAll().
+     */
+    protected boolean reallyCleared;
+
+    /**
+     * If true, the cursor is visible and should be placed onscreen at
+     * (cursorX, cursorY) during a call to flushPhysical().
+     */
+    protected boolean cursorVisible;
+
+    /**
+     * Cursor X position if visible.
+     */
+    protected int cursorX;
+
+    /**
+     * Cursor Y position if visible.
+     */
+    protected int cursorY;
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Public constructor.  Sets everything to not-bold, white-on-black.
+     */
+    protected LogicalScreen() {
+        offsetX  = 0;
+        offsetY  = 0;
+        width    = 80;
+        height   = 24;
+        logical  = null;
+        physical = null;
+        reallocate(width, height);
+    }
+
+    // ------------------------------------------------------------------------
+    // Screen -----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Set drawing offset for x.
      *
     /**
      * Set drawing offset for x.
      *
@@ -61,11 +143,6 @@ public class LogicalScreen implements Screen {
         this.offsetX = offsetX;
     }
 
         this.offsetX = offsetX;
     }
 
-    /**
-     * Drawing offset for y.
-     */
-    private int offsetY;
-
     /**
      * Set drawing offset for y.
      *
     /**
      * Set drawing offset for y.
      *
@@ -75,11 +152,6 @@ public class LogicalScreen implements Screen {
         this.offsetY = offsetY;
     }
 
         this.offsetY = offsetY;
     }
 
-    /**
-     * Ignore anything drawn right of clipRight.
-     */
-    private int clipRight;
-
     /**
      * Get right drawing clipping boundary.
      *
     /**
      * Get right drawing clipping boundary.
      *
@@ -98,11 +170,6 @@ public class LogicalScreen implements Screen {
         this.clipRight = clipRight;
     }
 
         this.clipRight = clipRight;
     }
 
-    /**
-     * Ignore anything drawn below clipBottom.
-     */
-    private int clipBottom;
-
     /**
      * Get bottom drawing clipping boundary.
      *
     /**
      * Get bottom drawing clipping boundary.
      *
@@ -121,11 +188,6 @@ public class LogicalScreen implements Screen {
         this.clipBottom = clipBottom;
     }
 
         this.clipBottom = clipBottom;
     }
 
-    /**
-     * Ignore anything drawn left of clipLeft.
-     */
-    private int clipLeft;
-
     /**
      * Get left drawing clipping boundary.
      *
     /**
      * Get left drawing clipping boundary.
      *
@@ -144,11 +206,6 @@ public class LogicalScreen implements Screen {
         this.clipLeft = clipLeft;
     }
 
         this.clipLeft = clipLeft;
     }
 
-    /**
-     * Ignore anything drawn above clipTop.
-     */
-    private int clipTop;
-
     /**
      * Get top drawing clipping boundary.
      *
     /**
      * Get top drawing clipping boundary.
      *
@@ -167,16 +224,6 @@ public class LogicalScreen implements Screen {
         this.clipTop = clipTop;
     }
 
         this.clipTop = clipTop;
     }
 
-    /**
-     * The physical screen last sent out on flush().
-     */
-    protected Cell [][] physical;
-
-    /**
-     * The logical screen being rendered to.
-     */
-    protected Cell [][] logical;
-
     /**
      * Get dirty flag.
      *
     /**
      * Get dirty flag.
      *
@@ -200,28 +247,6 @@ public class LogicalScreen implements Screen {
         return false;
     }
 
         return false;
     }
 
-    /**
-     * Set if the user explicitly wants to redraw everything starting with a
-     * ECMATerminal.clearAll().
-     */
-    protected boolean reallyCleared;
-
-    /**
-     * If true, the cursor is visible and should be placed onscreen at
-     * (cursorX, cursorY) during a call to flushPhysical().
-     */
-    protected boolean cursorVisible;
-
-    /**
-     * Cursor X position if visible.
-     */
-    protected int cursorX;
-
-    /**
-     * Cursor Y position if visible.
-     */
-    protected int cursorY;
-
     /**
      * Get the attributes at one location.
      *
     /**
      * Get the attributes at one location.
      *
@@ -491,50 +516,6 @@ public class LogicalScreen implements Screen {
         }
     }
 
         }
     }
 
-    /**
-     * Reallocate screen buffers.
-     *
-     * @param width new width
-     * @param height new height
-     */
-    private synchronized void reallocate(final int width, final int height) {
-        if (logical != null) {
-            for (int row = 0; row < this.height; row++) {
-                for (int col = 0; col < this.width; col++) {
-                    logical[col][row] = null;
-                }
-            }
-            logical = null;
-        }
-        logical = new Cell[width][height];
-        if (physical != null) {
-            for (int row = 0; row < this.height; row++) {
-                for (int col = 0; col < this.width; col++) {
-                    physical[col][row] = null;
-                }
-            }
-            physical = null;
-        }
-        physical = new Cell[width][height];
-
-        for (int row = 0; row < height; row++) {
-            for (int col = 0; col < width; col++) {
-                physical[col][row] = new Cell();
-                logical[col][row] = new Cell();
-            }
-        }
-
-        this.width = width;
-        this.height = height;
-
-        clipLeft = 0;
-        clipTop = 0;
-        clipRight = width;
-        clipBottom = height;
-
-        reallyCleared = true;
-    }
-
     /**
      * Change the width.  Everything on-screen will be destroyed and must be
      * redrawn.
     /**
      * Change the width.  Everything on-screen will be destroyed and must be
      * redrawn.
@@ -584,19 +565,6 @@ public class LogicalScreen implements Screen {
         return this.width;
     }
 
         return this.width;
     }
 
-    /**
-     * Public constructor.  Sets everything to not-bold, white-on-black.
-     */
-    protected LogicalScreen() {
-        offsetX  = 0;
-        offsetY  = 0;
-        width    = 80;
-        height   = 24;
-        logical  = null;
-        physical = null;
-        reallocate(width, height);
-    }
-
     /**
      * Reset screen to not-bold, white-on-black.  Also flushes the offset and
      * clip variables.
     /**
      * Reset screen to not-bold, white-on-black.  Also flushes the offset and
      * clip variables.
@@ -629,17 +597,6 @@ public class LogicalScreen implements Screen {
         reset();
     }
 
         reset();
     }
 
-    /**
-     * Clear the physical screen.
-     */
-    public final void clearPhysical() {
-        for (int row = 0; row < height; row++) {
-            for (int col = 0; col < width; col++) {
-                physical[col][row].reset();
-            }
-        }
-    }
-
     /**
      * Draw a box with a border and empty background.
      *
     /**
      * Draw a box with a border and empty background.
      *
@@ -852,4 +809,63 @@ public class LogicalScreen implements Screen {
      */
     public void setTitle(final String title) {}
 
      */
     public void setTitle(final String title) {}
 
+    // ------------------------------------------------------------------------
+    // LogicalScreen ----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Reallocate screen buffers.
+     *
+     * @param width new width
+     * @param height new height
+     */
+    private synchronized void reallocate(final int width, final int height) {
+        if (logical != null) {
+            for (int row = 0; row < this.height; row++) {
+                for (int col = 0; col < this.width; col++) {
+                    logical[col][row] = null;
+                }
+            }
+            logical = null;
+        }
+        logical = new Cell[width][height];
+        if (physical != null) {
+            for (int row = 0; row < this.height; row++) {
+                for (int col = 0; col < this.width; col++) {
+                    physical[col][row] = null;
+                }
+            }
+            physical = null;
+        }
+        physical = new Cell[width][height];
+
+        for (int row = 0; row < height; row++) {
+            for (int col = 0; col < width; col++) {
+                physical[col][row] = new Cell();
+                logical[col][row] = new Cell();
+            }
+        }
+
+        this.width = width;
+        this.height = height;
+
+        clipLeft = 0;
+        clipTop = 0;
+        clipRight = width;
+        clipBottom = height;
+
+        reallyCleared = true;
+    }
+
+    /**
+     * Clear the physical screen.
+     */
+    public final void clearPhysical() {
+        for (int row = 0; row < height; row++) {
+            for (int col = 0; col < width; col++) {
+                physical[col][row].reset();
+            }
+        }
+    }
+
 }
 }
index 9166e1c19bec44817a21297d87d949cb27b1a1f2..4e82d4ee694e13cbe12275ca1e16f67b42391251 100644 (file)
@@ -38,6 +38,10 @@ import jexer.event.TInputEvent;
  */
 public class MultiBackend implements Backend {
 
  */
 public class MultiBackend implements Backend {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * The screen to use.
      */
     /**
      * The screen to use.
      */
@@ -48,6 +52,10 @@ public class MultiBackend implements Backend {
      */
     private List<Backend> backends = new LinkedList<Backend>();
 
      */
     private List<Backend> backends = new LinkedList<Backend>();
 
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Public constructor requires one backend.  Note that this backend's
      * screen will be replaced with a MultiScreen.
     /**
      * Public constructor requires one backend.  Note that this backend's
      * screen will be replaced with a MultiScreen.
@@ -63,35 +71,9 @@ public class MultiBackend implements Backend {
         }
     }
 
         }
     }
 
-    /**
-     * Add a backend to the list.
-     *
-     * @param backend the backend to add
-     */
-    public void addBackend(final Backend backend) {
-        backends.add(backend);
-        if (backend instanceof TWindowBackend) {
-            multiScreen.addScreen(((TWindowBackend) backend).getOtherScreen());
-        } else {
-            multiScreen.addScreen(backend.getScreen());
-        }
-    }
-
-    /**
-     * Remove a backend from the list.
-     *
-     * @param backend the backend to remove
-     */
-    public void removeBackend(final Backend backend) {
-        if (backends.size() > 1) {
-            if (backend instanceof TWindowBackend) {
-                multiScreen.removeScreen(((TWindowBackend) backend).getOtherScreen());
-            } else {
-                multiScreen.removeScreen(backend.getScreen());
-            }
-            backends.remove(backend);
-        }
-    }
+    // ------------------------------------------------------------------------
+    // Backend ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Getter for sessionInfo.
 
     /**
      * Getter for sessionInfo.
@@ -121,6 +103,20 @@ public class MultiBackend implements Backend {
         }
     }
 
         }
     }
 
+    /**
+     * Check if there are events in the queue.
+     *
+     * @return if true, getEvents() has something to return to the application
+     */
+    public boolean hasEvents() {
+        for (Backend backend: backends) {
+            if (backend.hasEvents()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Subclasses must provide an implementation to get keyboard, mouse, and
      * screen resize events.
     /**
      * Subclasses must provide an implementation to get keyboard, mouse, and
      * screen resize events.
@@ -166,4 +162,38 @@ public class MultiBackend implements Backend {
         }
     }
 
         }
     }
 
+    // ------------------------------------------------------------------------
+    // MultiBackend -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Add a backend to the list.
+     *
+     * @param backend the backend to add
+     */
+    public void addBackend(final Backend backend) {
+        backends.add(backend);
+        if (backend instanceof TWindowBackend) {
+            multiScreen.addScreen(((TWindowBackend) backend).getOtherScreen());
+        } else {
+            multiScreen.addScreen(backend.getScreen());
+        }
+    }
+
+    /**
+     * Remove a backend from the list.
+     *
+     * @param backend the backend to remove
+     */
+    public void removeBackend(final Backend backend) {
+        if (backends.size() > 1) {
+            if (backend instanceof TWindowBackend) {
+                multiScreen.removeScreen(((TWindowBackend) backend).getOtherScreen());
+            } else {
+                multiScreen.removeScreen(backend.getScreen());
+            }
+            backends.remove(backend);
+        }
+    }
+
 }
 }
index 735faace1a130576b28e6fd8d8f667a9acc36510..77688734fa9179aca6414829a5bc29cdcc91ecf7 100644 (file)
@@ -39,11 +39,19 @@ import jexer.bits.CellAttributes;
  */
 public class MultiScreen implements Screen {
 
  */
 public class MultiScreen implements Screen {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * The list of screens to use.
      */
     private List<Screen> screens = new LinkedList<Screen>();
 
     /**
      * The list of screens to use.
      */
     private List<Screen> screens = new LinkedList<Screen>();
 
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Public constructor requires one screen.
      *
     /**
      * Public constructor requires one screen.
      *
@@ -53,25 +61,9 @@ public class MultiScreen implements Screen {
         screens.add(screen);
     }
 
         screens.add(screen);
     }
 
-    /**
-     * Add a screen to the list.
-     *
-     * @param screen the screen to add
-     */
-    public void addScreen(final Screen screen) {
-        screens.add(screen);
-    }
-
-    /**
-     * Remove a screen from the list.
-     *
-     * @param screen the screen to remove
-     */
-    public void removeScreen(final Screen screen) {
-        if (screens.size() > 1) {
-            screens.remove(screen);
-        }
-    }
+    // ------------------------------------------------------------------------
+    // Screen -----------------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Set drawing offset for x.
 
     /**
      * Set drawing offset for x.
@@ -574,4 +566,28 @@ public class MultiScreen implements Screen {
         }
     }
 
         }
     }
 
+    // ------------------------------------------------------------------------
+    // MultiScreen ------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Add a screen to the list.
+     *
+     * @param screen the screen to add
+     */
+    public void addScreen(final Screen screen) {
+        screens.add(screen);
+    }
+
+    /**
+     * Remove a screen from the list.
+     *
+     * @param screen the screen to remove
+     */
+    public void removeScreen(final Screen screen) {
+        if (screens.size() > 1) {
+            screens.remove(screen);
+        }
+    }
+
 }
 }
index a98627ec3f860d79df7bd71c55be2fab50b63f29..d6e07422d467f0cbe867ed5f755b38ac3357e4ad 100644 (file)
@@ -37,6 +37,10 @@ import javax.swing.JComponent;
  */
 public final class SwingBackend extends GenericBackend {
 
  */
 public final class SwingBackend extends GenericBackend {
 
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Public constructor.  The window will be 80x25 with font size 20 pts.
      */
     /**
      * Public constructor.  The window will be 80x25 with font size 20 pts.
      */
@@ -127,6 +131,10 @@ public final class SwingBackend extends GenericBackend {
         screen = (SwingTerminal) terminal;
     }
 
         screen = (SwingTerminal) terminal;
     }
 
+    // ------------------------------------------------------------------------
+    // SwingBackend -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Set to a new font, and resize the screen to match its dimensions.
      *
     /**
      * Set to a new font, and resize the screen to match its dimensions.
      *
index 48e4a44fa6b5b9f2db1765ec8f700f78137d38b3..4b0b2b4d079bc2f8b9d43bbc7ad10ab268852438 100644 (file)
@@ -52,11 +52,53 @@ import javax.swing.JFrame;
  */
 class SwingComponent {
 
  */
 class SwingComponent {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * If true, use triple buffering when drawing to a JFrame.
      */
     public static boolean tripleBuffer = true;
 
     /**
      * If true, use triple buffering when drawing to a JFrame.
      */
     public static boolean tripleBuffer = true;
 
+    /**
+     * The frame reference, if we are drawing to a JFrame.
+     */
+    private JFrame frame;
+
+    /**
+     * The component reference, if we are drawing to a JComponent.
+     */
+    private JComponent component;
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Construct using a JFrame.
+     *
+     * @param frame the JFrame to draw to
+     */
+    public SwingComponent(final JFrame frame) {
+        this.frame = frame;
+        setupFrame();
+    }
+
+    /**
+     * Construct using a JComponent.
+     *
+     * @param component the JComponent to draw to
+     */
+    public SwingComponent(final JComponent component) {
+        this.component = component;
+        setupComponent();
+    }
+
+    // ------------------------------------------------------------------------
+    // SwingComponent ---------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Get the BufferStrategy object needed for triple-buffering.
      *
     /**
      * Get the BufferStrategy object needed for triple-buffering.
      *
@@ -73,16 +115,6 @@ class SwingComponent {
         }
     }
 
         }
     }
 
-    /**
-     * The frame reference, if we are drawing to a JFrame.
-     */
-    private JFrame frame;
-
-    /**
-     * The component reference, if we are drawing to a JComponent.
-     */
-    private JComponent component;
-
     /**
      * Get the JFrame reference.
      *
     /**
      * Get the JFrame reference.
      *
@@ -101,26 +133,6 @@ class SwingComponent {
         return component;
     }
 
         return component;
     }
 
-    /**
-     * Construct using a JFrame.
-     *
-     * @param frame the JFrame to draw to
-     */
-    public SwingComponent(final JFrame frame) {
-        this.frame = frame;
-        setupFrame();
-    }
-
-    /**
-     * Construct using a JComponent.
-     *
-     * @param component the JComponent to draw to
-     */
-    public SwingComponent(final JComponent component) {
-        this.component = component;
-        setupComponent();
-    }
-
     /**
      * Setup to render to an existing JComponent.
      */
     /**
      * Setup to render to an existing JComponent.
      */
index 7457f57a0d73b185f1f618e4a035faade6e7c88b..b4c0b59d9f3c6868518dc470a39a7f06fc192401 100644 (file)
@@ -37,6 +37,10 @@ import java.awt.Insets;
  */
 public final class SwingSessionInfo implements SessionInfo {
 
  */
 public final class SwingSessionInfo implements SessionInfo {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * The Swing JFrame or JComponent.
      */
     /**
      * The Swing JFrame or JComponent.
      */
@@ -72,6 +76,48 @@ public final class SwingSessionInfo implements SessionInfo {
      */
     private int windowHeight = 25;
 
      */
     private int windowHeight = 25;
 
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Public constructor.
+     *
+     * @param swing the Swing JFrame or JComponent
+     * @param textWidth the width of a cell in pixels
+     * @param textHeight the height of a cell in pixels
+     */
+    public SwingSessionInfo(final SwingComponent swing, final int textWidth,
+        final int textHeight) {
+
+        this.swing      = swing;
+        this.textWidth  = textWidth;
+        this.textHeight = textHeight;
+    }
+
+    /**
+     * Public constructor.
+     *
+     * @param swing the Swing JFrame or JComponent
+     * @param textWidth the width of a cell in pixels
+     * @param textHeight the height of a cell in pixels
+     * @param width the number of columns
+     * @param height the number of rows
+     */
+    public SwingSessionInfo(final SwingComponent swing, final int textWidth,
+        final int textHeight, final int width, final int height) {
+
+        this.swing              = swing;
+        this.textWidth          = textWidth;
+        this.textHeight         = textHeight;
+        this.windowWidth        = width;
+        this.windowHeight       = height;
+    }
+
+    // ------------------------------------------------------------------------
+    // SessionInfo ------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Username getter.
      *
     /**
      * Username getter.
      *
@@ -126,53 +172,6 @@ public final class SwingSessionInfo implements SessionInfo {
         return windowHeight;
     }
 
         return windowHeight;
     }
 
-    /**
-     * Set the dimensions of a single text cell.
-     *
-     * @param textWidth the width of a cell in pixels
-     * @param textHeight the height of a cell in pixels
-     */
-    public void setTextCellDimensions(final int textWidth,
-        final int textHeight) {
-
-        this.textWidth  = textWidth;
-        this.textHeight = textHeight;
-    }
-
-    /**
-     * Public constructor.
-     *
-     * @param swing the Swing JFrame or JComponent
-     * @param textWidth the width of a cell in pixels
-     * @param textHeight the height of a cell in pixels
-     */
-    public SwingSessionInfo(final SwingComponent swing, final int textWidth,
-        final int textHeight) {
-
-        this.swing      = swing;
-        this.textWidth  = textWidth;
-        this.textHeight = textHeight;
-    }
-
-    /**
-     * Public constructor.
-     *
-     * @param swing the Swing JFrame or JComponent
-     * @param textWidth the width of a cell in pixels
-     * @param textHeight the height of a cell in pixels
-     * @param width the number of columns
-     * @param height the number of rows
-     */
-    public SwingSessionInfo(final SwingComponent swing, final int textWidth,
-        final int textHeight, final int width, final int height) {
-
-        this.swing              = swing;
-        this.textWidth          = textWidth;
-        this.textHeight         = textHeight;
-        this.windowWidth        = width;
-        this.windowHeight       = height;
-    }
-
     /**
      * Re-query the text window size.
      */
     /**
      * Re-query the text window size.
      */
@@ -191,4 +190,21 @@ public final class SwingSessionInfo implements SessionInfo {
 
     }
 
 
     }
 
+    // ------------------------------------------------------------------------
+    // SwingSessionInfo -------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Set the dimensions of a single text cell.
+     *
+     * @param textWidth the width of a cell in pixels
+     * @param textHeight the height of a cell in pixels
+     */
+    public void setTextCellDimensions(final int textWidth,
+        final int textHeight) {
+
+        this.textWidth  = textWidth;
+        this.textHeight = textHeight;
+    }
+
 }
 }
index a98063867e75479c2b18d91e2872fb2fdadec4a5..598f0ab1bce9f7d834cb770a283500c6aa95b2f2 100644 (file)
@@ -54,8 +54,10 @@ import java.io.InputStream;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import javax.swing.JComponent;
 import javax.swing.JFrame;
 import javax.swing.JComponent;
 import javax.swing.JFrame;
+import javax.swing.ImageIcon;
 import javax.swing.SwingUtilities;
 
 import jexer.TKeypress;
 import javax.swing.SwingUtilities;
 
 import jexer.TKeypress;
@@ -84,14 +86,19 @@ public final class SwingTerminal extends LogicalScreen
                                             MouseListener, MouseMotionListener,
                                             MouseWheelListener, WindowListener {
 
                                             MouseListener, MouseMotionListener,
                                             MouseWheelListener, WindowListener {
 
+    // ------------------------------------------------------------------------
+    // Constants --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
     /**
-     * The Swing component or frame to draw to.
+     * The icon image location.
      */
      */
-    private SwingComponent swing;
+    private static final String ICONFILE = "jexer_logo_128.png";
 
 
-    // ------------------------------------------------------------------------
-    // Screen -----------------------------------------------------------------
-    // ------------------------------------------------------------------------
+    /**
+     * The terminus font resource filename.
+     */
+    private static final String FONTFILE = "terminus-ttf-4.39/TerminusTTF-Bold-4.39.ttf";
 
     /**
      * Cursor style to draw.
 
     /**
      * Cursor style to draw.
@@ -113,17 +120,9 @@ public final class SwingTerminal extends LogicalScreen
         OUTLINE
     }
 
         OUTLINE
     }
 
-    /**
-     * A cache of previously-rendered glyphs for blinking text, when it is
-     * not visible.
-     */
-    private HashMap<Cell, BufferedImage> glyphCacheBlink;
-
-    /**
-     * A cache of previously-rendered glyphs for non-blinking, or
-     * blinking-and-visible, text.
-     */
-    private HashMap<Cell, BufferedImage> glyphCache;
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     // Colors to map DOS colors to AWT colors.
     private static Color MYBLACK;
 
     // Colors to map DOS colors to AWT colors.
     private static Color MYBLACK;
@@ -149,36 +148,21 @@ public final class SwingTerminal extends LogicalScreen
     private static boolean dosColors = false;
 
     /**
     private static boolean dosColors = false;
 
     /**
-     * Setup Swing colors to match DOS color palette.
+     * The Swing component or frame to draw to.
      */
      */
-    private static void setDOSColors() {
-        if (dosColors) {
-            return;
-        }
-        MYBLACK         = new Color(0x00, 0x00, 0x00);
-        MYRED           = new Color(0xa8, 0x00, 0x00);
-        MYGREEN         = new Color(0x00, 0xa8, 0x00);
-        MYYELLOW        = new Color(0xa8, 0x54, 0x00);
-        MYBLUE          = new Color(0x00, 0x00, 0xa8);
-        MYMAGENTA       = new Color(0xa8, 0x00, 0xa8);
-        MYCYAN          = new Color(0x00, 0xa8, 0xa8);
-        MYWHITE         = new Color(0xa8, 0xa8, 0xa8);
-        MYBOLD_BLACK    = new Color(0x54, 0x54, 0x54);
-        MYBOLD_RED      = new Color(0xfc, 0x54, 0x54);
-        MYBOLD_GREEN    = new Color(0x54, 0xfc, 0x54);
-        MYBOLD_YELLOW   = new Color(0xfc, 0xfc, 0x54);
-        MYBOLD_BLUE     = new Color(0x54, 0x54, 0xfc);
-        MYBOLD_MAGENTA  = new Color(0xfc, 0x54, 0xfc);
-        MYBOLD_CYAN     = new Color(0x54, 0xfc, 0xfc);
-        MYBOLD_WHITE    = new Color(0xfc, 0xfc, 0xfc);
+    private SwingComponent swing;
 
 
-        dosColors = true;
-    }
+    /**
+     * A cache of previously-rendered glyphs for blinking text, when it is
+     * not visible.
+     */
+    private Map<Cell, BufferedImage> glyphCacheBlink;
 
     /**
 
     /**
-     * The terminus font resource filename.
+     * A cache of previously-rendered glyphs for non-blinking, or
+     * blinking-and-visible, text.
      */
      */
-    private static final String FONTFILE = "terminus-ttf-4.39/TerminusTTF-Bold-4.39.ttf";
+    private Map<Cell, BufferedImage> glyphCache;
 
     /**
      * If true, we were successful getting Terminus.
 
     /**
      * If true, we were successful getting Terminus.
@@ -246,17 +230,6 @@ public final class SwingTerminal extends LogicalScreen
      */
     private long blinkMillis = 500;
 
      */
     private long blinkMillis = 500;
 
-    /**
-     * Get the number of millis to wait before switching the blink from
-     * visible to invisible.
-     *
-     * @return the number of milli to wait before switching the blink from
-     * visible to invisible
-     */
-    public long getBlinkMillis() {
-        return blinkMillis;
-    }
-
     /**
      * If true, the cursor should be visible right now based on the blink
      * time.
     /**
      * If true, the cursor should be visible right now based on the blink
      * time.
@@ -270,507 +243,321 @@ public final class SwingTerminal extends LogicalScreen
     private long lastBlinkTime = 0;
 
     /**
     private long lastBlinkTime = 0;
 
     /**
-     * Get the font size in points.
-     *
-     * @return font size in points
+     * The session information.
      */
      */
-    public int getFontSize() {
-        return fontSize;
-    }
+    private SwingSessionInfo sessionInfo;
 
     /**
 
     /**
-     * Set the font size in points.
-     *
-     * @param fontSize font size in points
+     * The listening object that run() wakes up on new input.
      */
      */
-    public void setFontSize(final int fontSize) {
-        this.fontSize = fontSize;
-        Font newFont = font.deriveFont((float) fontSize);
-        setFont(newFont);
-    }
+    private Object listener;
 
     /**
 
     /**
-     * Set to a new font, and resize the screen to match its dimensions.
-     *
-     * @param font the new font
+     * The event queue, filled up by a thread reading on input.
      */
      */
-    public void setFont(final Font font) {
-        this.font = font;
-        getFontDimensions();
-        swing.setFont(font);
-        glyphCacheBlink = new HashMap<Cell, BufferedImage>();
-        glyphCache = new HashMap<Cell, BufferedImage>();
-        resizeToScreen();
-    }
+    private List<TInputEvent> eventQueue;
 
     /**
 
     /**
-     * Set the font to Terminus, the best all-around font for both CP437 and
-     * ISO8859-1.
+     * The last reported mouse X position.
      */
      */
-    public void getDefaultFont() {
-        try {
-            ClassLoader loader = Thread.currentThread().
-            getContextClassLoader();
-            InputStream in = loader.getResourceAsStream(FONTFILE);
-            Font terminusRoot = Font.createFont(Font.TRUETYPE_FONT, in);
-            Font terminus = terminusRoot.deriveFont(Font.PLAIN, fontSize);
-            gotTerminus = true;
-            font = terminus;
-        } catch (Exception e) {
-            e.printStackTrace();
-            font = new Font(Font.MONOSPACED, Font.PLAIN, fontSize);
-        }
-
-        setFont(font);
-    }
+    private int oldMouseX = -1;
 
     /**
 
     /**
-     * Convert a CellAttributes foreground color to an Swing Color.
-     *
-     * @param attr the text attributes
-     * @return the Swing Color
+     * The last reported mouse Y position.
      */
      */
-    private Color attrToForegroundColor(final CellAttributes attr) {
-        if (attr.isBold()) {
-            if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) {
-                return MYBOLD_BLACK;
-            } else if (attr.getForeColor().equals(jexer.bits.Color.RED)) {
-                return MYBOLD_RED;
-            } else if (attr.getForeColor().equals(jexer.bits.Color.BLUE)) {
-                return MYBOLD_BLUE;
-            } else if (attr.getForeColor().equals(jexer.bits.Color.GREEN)) {
-                return MYBOLD_GREEN;
-            } else if (attr.getForeColor().equals(jexer.bits.Color.YELLOW)) {
-                return MYBOLD_YELLOW;
-            } else if (attr.getForeColor().equals(jexer.bits.Color.CYAN)) {
-                return MYBOLD_CYAN;
-            } else if (attr.getForeColor().equals(jexer.bits.Color.MAGENTA)) {
-                return MYBOLD_MAGENTA;
-            } else if (attr.getForeColor().equals(jexer.bits.Color.WHITE)) {
-                return MYBOLD_WHITE;
-            }
-        } else {
-            if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) {
-                return MYBLACK;
-            } else if (attr.getForeColor().equals(jexer.bits.Color.RED)) {
-                return MYRED;
-            } else if (attr.getForeColor().equals(jexer.bits.Color.BLUE)) {
-                return MYBLUE;
-            } else if (attr.getForeColor().equals(jexer.bits.Color.GREEN)) {
-                return MYGREEN;
-            } else if (attr.getForeColor().equals(jexer.bits.Color.YELLOW)) {
-                return MYYELLOW;
-            } else if (attr.getForeColor().equals(jexer.bits.Color.CYAN)) {
-                return MYCYAN;
-            } else if (attr.getForeColor().equals(jexer.bits.Color.MAGENTA)) {
-                return MYMAGENTA;
-            } else if (attr.getForeColor().equals(jexer.bits.Color.WHITE)) {
-                return MYWHITE;
-            }
-        }
-        throw new IllegalArgumentException("Invalid color: " +
-            attr.getForeColor().getValue());
-    }
+    private int oldMouseY = -1;
 
     /**
 
     /**
-     * Convert a CellAttributes background color to an Swing Color.
-     *
-     * @param attr the text attributes
-     * @return the Swing Color
+     * true if mouse1 was down.  Used to report mouse1 on the release event.
      */
      */
-    private Color attrToBackgroundColor(final CellAttributes attr) {
-        if (attr.getBackColor().equals(jexer.bits.Color.BLACK)) {
-            return MYBLACK;
-        } else if (attr.getBackColor().equals(jexer.bits.Color.RED)) {
-            return MYRED;
-        } else if (attr.getBackColor().equals(jexer.bits.Color.BLUE)) {
-            return MYBLUE;
-        } else if (attr.getBackColor().equals(jexer.bits.Color.GREEN)) {
-            return MYGREEN;
-        } else if (attr.getBackColor().equals(jexer.bits.Color.YELLOW)) {
-            return MYYELLOW;
-        } else if (attr.getBackColor().equals(jexer.bits.Color.CYAN)) {
-            return MYCYAN;
-        } else if (attr.getBackColor().equals(jexer.bits.Color.MAGENTA)) {
-            return MYMAGENTA;
-        } else if (attr.getBackColor().equals(jexer.bits.Color.WHITE)) {
-            return MYWHITE;
-        }
-        throw new IllegalArgumentException("Invalid color: " +
-            attr.getBackColor().getValue());
-    }
+    private boolean mouse1 = false;
 
     /**
 
     /**
-     * Figure out what textAdjustX and textAdjustY should be, based on the
-     * location of a vertical bar (to find textAdjustY) and a horizontal bar
-     * (to find textAdjustX).
-     *
-     * @return true if textAdjustX and textAdjustY were guessed at correctly
+     * true if mouse2 was down.  Used to report mouse2 on the release event.
      */
      */
-    private boolean getFontAdjustments() {
-        BufferedImage image = null;
+    private boolean mouse2 = false;
 
 
-        // What SHOULD happen is that the topmost/leftmost white pixel is at
-        // position (gr2x, gr2y).  But it might also be off by a pixel in
-        // either direction.
+    /**
+     * true if mouse3 was down.  Used to report mouse3 on the release event.
+     */
+    private boolean mouse3 = false;
 
 
-        Graphics2D gr2 = null;
-        int gr2x = 3;
-        int gr2y = 3;
-        image = new BufferedImage(textWidth * 2, textHeight * 2,
-            BufferedImage.TYPE_INT_ARGB);
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
 
 
-        gr2 = image.createGraphics();
-        gr2.setFont(swing.getFont());
-        gr2.setColor(java.awt.Color.BLACK);
-        gr2.fillRect(0, 0, textWidth * 2, textHeight * 2);
-        gr2.setColor(java.awt.Color.WHITE);
-        char [] chars = new char[1];
-        chars[0] = jexer.bits.GraphicsChars.VERTICAL_BAR;
-        gr2.drawChars(chars, 0, 1, gr2x, gr2y + textHeight - maxDescent);
-        gr2.dispose();
+    /**
+     * Public constructor creates a new JFrame to render to.
+     *
+     * @param windowWidth the number of text columns to start with
+     * @param windowHeight the number of text rows to start with
+     * @param fontSize the size in points.  Good values to pick are: 16, 20,
+     * 22, and 24.
+     * @param listener the object this backend needs to wake up when new
+     * input comes in
+     */
+    public SwingTerminal(final int windowWidth, final int windowHeight,
+        final int fontSize, final Object listener) {
 
 
-        for (int x = 0; x < textWidth; x++) {
-            for (int y = 0; y < textHeight; y++) {
+        this.fontSize = fontSize;
 
 
-                /*
-                System.err.println("X: " + x + " Y: " + y + " " +
-                    image.getRGB(x, y));
-                 */
+        setDOSColors();
 
 
-                if ((image.getRGB(x, y) & 0xFFFFFF) != 0) {
-                    textAdjustY = (gr2y - y);
+        // Figure out my cursor style.
+        String cursorStyleString = System.getProperty(
+            "jexer.Swing.cursorStyle", "underline").toLowerCase();
+        if (cursorStyleString.equals("underline")) {
+            cursorStyle = CursorStyle.UNDERLINE;
+        } else if (cursorStyleString.equals("outline")) {
+            cursorStyle = CursorStyle.OUTLINE;
+        } else if (cursorStyleString.equals("block")) {
+            cursorStyle = CursorStyle.BLOCK;
+        }
 
 
-                    // System.err.println("textAdjustY: " + textAdjustY);
-                    x = textWidth;
-                    break;
-                }
+        // Pull the system property for triple buffering.
+        if (System.getProperty("jexer.Swing.tripleBuffer") != null) {
+            if (System.getProperty("jexer.Swing.tripleBuffer").equals("true")) {
+                SwingComponent.tripleBuffer = true;
+            } else {
+                SwingComponent.tripleBuffer = false;
             }
         }
 
             }
         }
 
-        gr2 = image.createGraphics();
-        gr2.setFont(swing.getFont());
-        gr2.setColor(java.awt.Color.BLACK);
-        gr2.fillRect(0, 0, textWidth * 2, textHeight * 2);
-        gr2.setColor(java.awt.Color.WHITE);
-        chars[0] = jexer.bits.GraphicsChars.SINGLE_BAR;
-        gr2.drawChars(chars, 0, 1, gr2x, gr2y + textHeight - maxDescent);
-        gr2.dispose();
+        try {
+            SwingUtilities.invokeAndWait(new Runnable() {
+                public void run() {
 
 
-        for (int x = 0; x < textWidth; x++) {
-            for (int y = 0; y < textHeight; y++) {
+                    JFrame frame = new JFrame() {
 
 
-                /*
-                System.err.println("X: " + x + " Y: " + y + " " +
-                    image.getRGB(x, y));
-                 */
+                        /**
+                         * Serializable version.
+                         */
+                        private static final long serialVersionUID = 1;
 
 
-                if ((image.getRGB(x, y) & 0xFFFFFF) != 0) {
-                    textAdjustX = (gr2x - x);
+                        /**
+                         * The code that performs the actual drawing.
+                         */
+                        public SwingTerminal screen = null;
 
 
-                    // System.err.println("textAdjustX: " + textAdjustX);
-                    return true;
-                }
-            }
-        }
+                        /*
+                         * Anonymous class initializer saves the screen
+                         * reference, so that paint() and the like call out
+                         * to SwingTerminal.
+                         */
+                        {
+                            this.screen = SwingTerminal.this;
+                        }
 
 
-        // Something weird happened, don't rely on this function.
-        // System.err.println("getFontAdjustments: false");
-        return false;
-    }
+                        /**
+                         * Update redraws the whole screen.
+                         *
+                         * @param gr the Swing Graphics context
+                         */
+                        @Override
+                        public void update(final Graphics gr) {
+                            // The default update clears the area.  Don't do
+                            // that, instead just paint it directly.
+                            paint(gr);
+                        }
 
 
-    /**
-     * Figure out my font dimensions.  This code path works OK for the JFrame
-     * case, and can be called immediately after JFrame creation.
-     */
-    private void getFontDimensions() {
-        swing.setFont(font);
-        Graphics gr = swing.getGraphics();
-        if (gr == null) {
-            return;
-        }
-        getFontDimensions(gr);
-    }
+                        /**
+                         * Paint redraws the whole screen.
+                         *
+                         * @param gr the Swing Graphics context
+                         */
+                        @Override
+                        public void paint(final Graphics gr) {
+                            if (screen != null) {
+                                screen.paint(gr);
+                            }
+                        }
+                    };
 
 
-    /**
-     * Figure out my font dimensions.  This code path is needed to lazy-load
-     * the information inside paint().
-     *
-     * @param gr Graphics object to use
-     */
-    private void getFontDimensions(final Graphics gr) {
-        swing.setFont(font);
-        FontMetrics fm = gr.getFontMetrics();
-        maxDescent = fm.getMaxDescent();
-        Rectangle2D bounds = fm.getMaxCharBounds(gr);
-        int leading = fm.getLeading();
-        textWidth = (int)Math.round(bounds.getWidth());
-        // textHeight = (int)Math.round(bounds.getHeight()) - maxDescent;
+                    // Set icon
+                    ClassLoader loader = Thread.currentThread().
+                        getContextClassLoader();
+                    frame.setIconImage((new ImageIcon(loader.
+                                getResource(ICONFILE))).getImage());
 
 
-        // This produces the same number, but works better for ugly
-        // monospace.
-        textHeight = fm.getMaxAscent() + maxDescent - leading;
+                    // Get the Swing component
+                    SwingTerminal.this.swing = new SwingComponent(frame);
 
 
-        if (gotTerminus == true) {
-            textHeight++;
-        }
+                    // Hang onto top and left for drawing.
+                    Insets insets = SwingTerminal.this.swing.getInsets();
+                    SwingTerminal.this.left = insets.left;
+                    SwingTerminal.this.top = insets.top;
 
 
-        if (getFontAdjustments() == false) {
-            // We were unable to programmatically determine textAdjustX and
-            // textAdjustY, so try some guesses based on VM vendor.
-            String runtime = System.getProperty("java.runtime.name");
-            if ((runtime != null) && (runtime.contains("Java(TM)"))) {
-                textAdjustY = -1;
-                textAdjustX = 0;
-            }
-        }
+                    // Load the font so that we can set sessionInfo.
+                    getDefaultFont();
 
 
-        if (sessionInfo != null) {
-            sessionInfo.setTextCellDimensions(textWidth, textHeight);
+                    // Get the default cols x rows and set component size
+                    // accordingly.
+                    SwingTerminal.this.sessionInfo =
+                        new SwingSessionInfo(SwingTerminal.this.swing,
+                            SwingTerminal.this.textWidth,
+                            SwingTerminal.this.textHeight,
+                            windowWidth, windowHeight);
+
+                    SwingTerminal.this.setDimensions(sessionInfo.getWindowWidth(),
+                        sessionInfo.getWindowHeight());
+
+                    SwingTerminal.this.resizeToScreen();
+                    SwingTerminal.this.swing.setVisible(true);
+                }
+            });
+        } catch (Exception e) {
+            e.printStackTrace();
         }
         }
-        gotFontDimensions = true;
-    }
 
 
-    /**
-     * Resize to font dimensions.
-     */
-    public void resizeToScreen() {
-        swing.setDimensions(textWidth * width, textHeight * height);
+        this.listener    = listener;
+        mouse1           = false;
+        mouse2           = false;
+        mouse3           = false;
+        eventQueue       = new LinkedList<TInputEvent>();
+
+        // Add listeners to Swing.
+        swing.addKeyListener(this);
+        swing.addWindowListener(this);
+        swing.addComponentListener(this);
+        swing.addMouseListener(this);
+        swing.addMouseMotionListener(this);
+        swing.addMouseWheelListener(this);
     }
 
     /**
     }
 
     /**
-     * Draw one glyph to the screen.
+     * Public constructor renders to an existing JComponent.
      *
      *
-     * @param gr the Swing Graphics context
-     * @param cell the Cell to draw
-     * @param xPixel the x-coordinate to render to.  0 means the
-     * left-most pixel column.
-     * @param yPixel the y-coordinate to render to.  0 means the top-most
-     * pixel row.
+     * @param component the Swing component to render to
+     * @param windowWidth the number of text columns to start with
+     * @param windowHeight the number of text rows to start with
+     * @param fontSize the size in points.  Good values to pick are: 16, 20,
+     * 22, and 24.
+     * @param listener the object this backend needs to wake up when new
+     * input comes in
      */
      */
-    private void drawGlyph(final Graphics gr, final Cell cell,
-        final int xPixel, final int yPixel) {
+    public SwingTerminal(final JComponent component, final int windowWidth,
+        final int windowHeight, final int fontSize, final Object listener) {
 
 
-        /*
-        System.err.println("drawGlyph(): " + xPixel + " " + yPixel +
-            " " + cell);
-        */
+        this.fontSize = fontSize;
 
 
-        BufferedImage image = null;
-        if (cell.isBlink() && !cursorBlinkVisible) {
-            image = glyphCacheBlink.get(cell);
-        } else {
-            image = glyphCache.get(cell);
-        }
-        if (image != null) {
-            if (swing.getFrame() != null) {
-                gr.drawImage(image, xPixel, yPixel, swing.getFrame());
-            } else {
-                gr.drawImage(image, xPixel, yPixel, swing.getComponent());
-            }
-            return;
-        }
+        setDOSColors();
 
 
-        // Generate glyph and draw it.
-        Graphics2D gr2 = null;
-        int gr2x = xPixel;
-        int gr2y = yPixel;
-        if ((SwingComponent.tripleBuffer) && (swing.getFrame() != null)) {
-            image = new BufferedImage(textWidth, textHeight,
-                BufferedImage.TYPE_INT_ARGB);
-            gr2 = image.createGraphics();
-            gr2.setFont(swing.getFont());
-            gr2x = 0;
-            gr2y = 0;
-        } else {
-            gr2 = (Graphics2D) gr;
+        // Figure out my cursor style.
+        String cursorStyleString = System.getProperty(
+            "jexer.Swing.cursorStyle", "underline").toLowerCase();
+        if (cursorStyleString.equals("underline")) {
+            cursorStyle = CursorStyle.UNDERLINE;
+        } else if (cursorStyleString.equals("outline")) {
+            cursorStyle = CursorStyle.OUTLINE;
+        } else if (cursorStyleString.equals("block")) {
+            cursorStyle = CursorStyle.BLOCK;
         }
 
         }
 
-        Cell cellColor = new Cell();
-        cellColor.setTo(cell);
+        try {
+            SwingUtilities.invokeAndWait(new Runnable() {
+                public void run() {
 
 
-        // Check for reverse
-        if (cell.isReverse()) {
-            cellColor.setForeColor(cell.getBackColor());
-            cellColor.setBackColor(cell.getForeColor());
-        }
+                    JComponent newComponent = new JComponent() {
 
 
-        // Draw the background rectangle, then the foreground character.
-        gr2.setColor(attrToBackgroundColor(cellColor));
-        gr2.fillRect(gr2x, gr2y, textWidth, textHeight);
+                        /**
+                         * Serializable version.
+                         */
+                        private static final long serialVersionUID = 1;
 
 
-        // Handle blink and underline
-        if (!cell.isBlink()
-            || (cell.isBlink() && cursorBlinkVisible)
-        ) {
-            gr2.setColor(attrToForegroundColor(cellColor));
-            char [] chars = new char[1];
-            chars[0] = cell.getChar();
-            gr2.drawChars(chars, 0, 1, gr2x + textAdjustX,
-                gr2y + textHeight - maxDescent + textAdjustY);
+                        /**
+                         * The code that performs the actual drawing.
+                         */
+                        public SwingTerminal screen = null;
 
 
-            if (cell.isUnderline()) {
-                gr2.fillRect(gr2x, gr2y + textHeight - 2, textWidth, 2);
-            }
-        }
-
-        if ((SwingComponent.tripleBuffer) && (swing.getFrame() != null)) {
-            gr2.dispose();
-
-            // We need a new key that will not be mutated by
-            // invertCell().
-            Cell key = new Cell();
-            key.setTo(cell);
-            if (cell.isBlink() && !cursorBlinkVisible) {
-                glyphCacheBlink.put(key, image);
-            } else {
-                glyphCache.put(key, image);
-            }
-
-            if (swing.getFrame() != null) {
-                gr.drawImage(image, xPixel, yPixel, swing.getFrame());
-            } else {
-                gr.drawImage(image, xPixel, yPixel, swing.getComponent());
-            }
-        }
-
-    }
+                        /*
+                         * Anonymous class initializer saves the screen
+                         * reference, so that paint() and the like call out
+                         * to SwingTerminal.
+                         */
+                        {
+                            this.screen = SwingTerminal.this;
+                        }
 
 
-    /**
-     * Check if the cursor is visible, and if so draw it.
-     *
-     * @param gr the Swing Graphics context
-     */
-    private void drawCursor(final Graphics gr) {
+                        /**
+                         * Update redraws the whole screen.
+                         *
+                         * @param gr the Swing Graphics context
+                         */
+                        @Override
+                        public void update(final Graphics gr) {
+                            // The default update clears the area.  Don't do
+                            // that, instead just paint it directly.
+                            paint(gr);
+                        }
 
 
-        if (cursorVisible
-            && (cursorY >= 0)
-            && (cursorX >= 0)
-            && (cursorY <= height - 1)
-            && (cursorX <= width - 1)
-            && cursorBlinkVisible
-        ) {
-            int xPixel = cursorX * textWidth + left;
-            int yPixel = cursorY * textHeight + top;
-            Cell lCell = logical[cursorX][cursorY];
-            gr.setColor(attrToForegroundColor(lCell));
-            switch (cursorStyle) {
-            default:
-                // Fall through...
-            case UNDERLINE:
-                gr.fillRect(xPixel, yPixel + textHeight - 2, textWidth, 2);
-                break;
-            case BLOCK:
-                gr.fillRect(xPixel, yPixel, textWidth, textHeight);
-                break;
-            case OUTLINE:
-                gr.drawRect(xPixel, yPixel, textWidth - 1, textHeight - 1);
-                break;
-            }
-        }
-    }
+                        /**
+                         * Paint redraws the whole screen.
+                         *
+                         * @param gr the Swing Graphics context
+                         */
+                        @Override
+                        public void paint(final Graphics gr) {
+                            if (screen != null) {
+                                screen.paint(gr);
+                            }
+                        }
+                    };
+                    component.setLayout(new BorderLayout());
+                    component.add(newComponent);
 
 
-    /**
-     * Reset the blink timer.
-     */
-    private void resetBlinkTimer() {
-        lastBlinkTime = System.currentTimeMillis();
-        cursorBlinkVisible = true;
-    }
+                    // Allow key events to be received
+                    component.setFocusable(true);
 
 
-    /**
-     * Paint redraws the whole screen.
-     *
-     * @param gr the Swing Graphics context
-     */
-    public void paint(final Graphics gr) {
+                    // Get the Swing component
+                    SwingTerminal.this.swing = new SwingComponent(component);
 
 
-        if (gotFontDimensions == false) {
-            // Lazy-load the text width/height
-            getFontDimensions(gr);
-            /*
-            System.err.println("textWidth " + textWidth +
-                " textHeight " + textHeight);
-            System.err.println("FONT: " + swing.getFont() + " font " + font);
-             */
-        }
+                    // Hang onto top and left for drawing.
+                    Insets insets = SwingTerminal.this.swing.getInsets();
+                    SwingTerminal.this.left = insets.left;
+                    SwingTerminal.this.top = insets.top;
 
 
-        int xCellMin = 0;
-        int xCellMax = width;
-        int yCellMin = 0;
-        int yCellMax = height;
+                    // Load the font so that we can set sessionInfo.
+                    getDefaultFont();
 
 
-        Rectangle bounds = gr.getClipBounds();
-        if (bounds != null) {
-            // Only update what is in the bounds
-            xCellMin = textColumn(bounds.x);
-            xCellMax = textColumn(bounds.x + bounds.width);
-            if (xCellMax > width) {
-                xCellMax = width;
-            }
-            if (xCellMin >= xCellMax) {
-                xCellMin = xCellMax - 2;
-            }
-            if (xCellMin < 0) {
-                xCellMin = 0;
-            }
-            yCellMin = textRow(bounds.y);
-            yCellMax = textRow(bounds.y + bounds.height);
-            if (yCellMax > height) {
-                yCellMax = height;
-            }
-            if (yCellMin >= yCellMax) {
-                yCellMin = yCellMax - 2;
-            }
-            if (yCellMin < 0) {
-                yCellMin = 0;
-            }
-        } else {
-            // We need a total repaint
-            reallyCleared = true;
+                    // Get the default cols x rows and set component size
+                    // accordingly.
+                    SwingTerminal.this.sessionInfo =
+                        new SwingSessionInfo(SwingTerminal.this.swing,
+                            SwingTerminal.this.textWidth,
+                            SwingTerminal.this.textHeight);
+                }
+            });
+        } catch (Exception e) {
+            e.printStackTrace();
         }
 
         }
 
-        // Prevent updates to the screen's data from the TApplication
-        // threads.
-        synchronized (this) {
-
-            /*
-            System.err.printf("bounds %s X %d %d Y %d %d\n",
-                 bounds, xCellMin, xCellMax, yCellMin, yCellMax);
-            */
-
-            for (int y = yCellMin; y < yCellMax; y++) {
-                for (int x = xCellMin; x < xCellMax; x++) {
-
-                    int xPixel = x * textWidth + left;
-                    int yPixel = y * textHeight + top;
-
-                    Cell lCell = logical[x][y];
-                    Cell pCell = physical[x][y];
-
-                    if (!lCell.equals(pCell)
-                        || lCell.isBlink()
-                        || reallyCleared
-                        || (swing.getFrame() == null)) {
-
-                        drawGlyph(gr, lCell, xPixel, yPixel);
-
-                        // Physical is always updated
-                        physical[x][y].setTo(lCell);
-                    }
-                }
-            }
-            drawCursor(gr);
+        this.listener    = listener;
+        mouse1           = false;
+        mouse2           = false;
+        mouse3           = false;
+        eventQueue       = new LinkedList<TInputEvent>();
 
 
-            reallyCleared = false;
-        } // synchronized (this)
+        // Add listeners to Swing.
+        swing.addKeyListener(this);
+        swing.addWindowListener(this);
+        swing.addComponentListener(this);
+        swing.addMouseListener(this);
+        swing.addMouseMotionListener(this);
+        swing.addMouseWheelListener(this);
     }
 
     }
 
+    // ------------------------------------------------------------------------
+    // LogicalScreen ----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
     /**
-     * Restore terminal to normal state.
+     * Set the window title.
+     *
+     * @param title the new title
      */
      */
-    public void shutdown() {
-        swing.dispose();
+    @Override
+    public void setTitle(final String title) {
+        swing.setTitle(title);
     }
 
     /**
     }
 
     /**
@@ -804,520 +591,787 @@ public final class SwingTerminal extends LogicalScreen
         }
     }
 
         }
     }
 
+    // ------------------------------------------------------------------------
+    // TerminalReader ---------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
     /**
-     * Push the logical screen to the physical device.
+     * Check if there are events in the queue.
+     *
+     * @return if true, getEvents() has something to return to the backend
      */
      */
-    private void drawToSwing() {
+    public boolean hasEvents() {
+        synchronized (eventQueue) {
+            return (eventQueue.size() > 0);
+        }
+    }
 
 
-        /*
-        System.err.printf("drawToSwing(): reallyCleared %s dirty %s\n",
-            reallyCleared, dirty);
-        */
+    /**
+     * Return any events in the IO queue.
+     *
+     * @param queue list to append new events to
+     */
+    public void getEvents(final List<TInputEvent> queue) {
+        synchronized (eventQueue) {
+            if (eventQueue.size() > 0) {
+                synchronized (queue) {
+                    queue.addAll(eventQueue);
+                }
+                eventQueue.clear();
+            }
+        }
+    }
 
 
-        // If reallyCleared is set, we have to draw everything.
-        if ((swing.getFrame() != null)
-            && (swing.getBufferStrategy() != null)
-            && (reallyCleared == true)
-        ) {
-            // Triple-buffering: we have to redraw everything on this thread.
-            Graphics gr = swing.getBufferStrategy().getDrawGraphics();
-            swing.paint(gr);
-            gr.dispose();
-            swing.getBufferStrategy().show();
-            Toolkit.getDefaultToolkit().sync();
-            return;
-        } else if (((swing.getFrame() != null)
-                && (swing.getBufferStrategy() == null))
-            || (reallyCleared == true)
-        ) {
-            // Repaint everything on the Swing thread.
-            // System.err.println("REPAINT ALL");
-            swing.repaint();
+    /**
+     * Restore terminal to normal state.
+     */
+    public void closeTerminal() {
+        shutdown();
+    }
+
+    /**
+     * Set listener to a different Object.
+     *
+     * @param listener the new listening object that run() wakes up on new
+     * input
+     */
+    public void setListener(final Object listener) {
+        this.listener = listener;
+    }
+
+    // ------------------------------------------------------------------------
+    // SwingTerminal ----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Setup Swing colors to match DOS color palette.
+     */
+    private static void setDOSColors() {
+        if (dosColors) {
             return;
         }
             return;
         }
+        MYBLACK         = new Color(0x00, 0x00, 0x00);
+        MYRED           = new Color(0xa8, 0x00, 0x00);
+        MYGREEN         = new Color(0x00, 0xa8, 0x00);
+        MYYELLOW        = new Color(0xa8, 0x54, 0x00);
+        MYBLUE          = new Color(0x00, 0x00, 0xa8);
+        MYMAGENTA       = new Color(0xa8, 0x00, 0xa8);
+        MYCYAN          = new Color(0x00, 0xa8, 0xa8);
+        MYWHITE         = new Color(0xa8, 0xa8, 0xa8);
+        MYBOLD_BLACK    = new Color(0x54, 0x54, 0x54);
+        MYBOLD_RED      = new Color(0xfc, 0x54, 0x54);
+        MYBOLD_GREEN    = new Color(0x54, 0xfc, 0x54);
+        MYBOLD_YELLOW   = new Color(0xfc, 0xfc, 0x54);
+        MYBOLD_BLUE     = new Color(0x54, 0x54, 0xfc);
+        MYBOLD_MAGENTA  = new Color(0xfc, 0x54, 0xfc);
+        MYBOLD_CYAN     = new Color(0x54, 0xfc, 0xfc);
+        MYBOLD_WHITE    = new Color(0xfc, 0xfc, 0xfc);
 
 
-        if ((swing.getFrame() != null) && (swing.getBufferStrategy() != null)) {
-            Graphics gr = swing.getBufferStrategy().getDrawGraphics();
+        dosColors = true;
+    }
 
 
-            synchronized (this) {
-                for (int y = 0; y < height; y++) {
-                    for (int x = 0; x < width; x++) {
-                        Cell lCell = logical[x][y];
-                        Cell pCell = physical[x][y];
+    /**
+     * Get the number of millis to wait before switching the blink from
+     * visible to invisible.
+     *
+     * @return the number of milli to wait before switching the blink from
+     * visible to invisible
+     */
+    public long getBlinkMillis() {
+        return blinkMillis;
+    }
 
 
-                        int xPixel = x * textWidth + left;
-                        int yPixel = y * textHeight + top;
+    /**
+     * Get the font size in points.
+     *
+     * @return font size in points
+     */
+    public int getFontSize() {
+        return fontSize;
+    }
 
 
-                        if (!lCell.equals(pCell)
-                            || ((x == cursorX)
-                                && (y == cursorY)
-                                && cursorVisible)
-                            || (lCell.isBlink())
-                        ) {
-                            drawGlyph(gr, lCell, xPixel, yPixel);
-                            physical[x][y].setTo(lCell);
-                        }
-                    }
-                }
-                drawCursor(gr);
-            } // synchronized (this)
+    /**
+     * Set the font size in points.
+     *
+     * @param fontSize font size in points
+     */
+    public void setFontSize(final int fontSize) {
+        this.fontSize = fontSize;
+        Font newFont = font.deriveFont((float) fontSize);
+        setFont(newFont);
+    }
 
 
-            gr.dispose();
-            swing.getBufferStrategy().show();
-            Toolkit.getDefaultToolkit().sync();
-            return;
+    /**
+     * Set to a new font, and resize the screen to match its dimensions.
+     *
+     * @param font the new font
+     */
+    public void setFont(final Font font) {
+        this.font = font;
+        getFontDimensions();
+        swing.setFont(font);
+        glyphCacheBlink = new HashMap<Cell, BufferedImage>();
+        glyphCache = new HashMap<Cell, BufferedImage>();
+        resizeToScreen();
+    }
+
+    /**
+     * Set the font to Terminus, the best all-around font for both CP437 and
+     * ISO8859-1.
+     */
+    public void getDefaultFont() {
+        try {
+            ClassLoader loader = Thread.currentThread().getContextClassLoader();
+            InputStream in = loader.getResourceAsStream(FONTFILE);
+            Font terminusRoot = Font.createFont(Font.TRUETYPE_FONT, in);
+            Font terminus = terminusRoot.deriveFont(Font.PLAIN, fontSize);
+            gotTerminus = true;
+            font = terminus;
+        } catch (Exception e) {
+            e.printStackTrace();
+            font = new Font(Font.MONOSPACED, Font.PLAIN, fontSize);
+        }
+
+        setFont(font);
+    }
+
+    /**
+     * Convert a CellAttributes foreground color to an Swing Color.
+     *
+     * @param attr the text attributes
+     * @return the Swing Color
+     */
+    private Color attrToForegroundColor(final CellAttributes attr) {
+        if (attr.isBold()) {
+            if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) {
+                return MYBOLD_BLACK;
+            } else if (attr.getForeColor().equals(jexer.bits.Color.RED)) {
+                return MYBOLD_RED;
+            } else if (attr.getForeColor().equals(jexer.bits.Color.BLUE)) {
+                return MYBOLD_BLUE;
+            } else if (attr.getForeColor().equals(jexer.bits.Color.GREEN)) {
+                return MYBOLD_GREEN;
+            } else if (attr.getForeColor().equals(jexer.bits.Color.YELLOW)) {
+                return MYBOLD_YELLOW;
+            } else if (attr.getForeColor().equals(jexer.bits.Color.CYAN)) {
+                return MYBOLD_CYAN;
+            } else if (attr.getForeColor().equals(jexer.bits.Color.MAGENTA)) {
+                return MYBOLD_MAGENTA;
+            } else if (attr.getForeColor().equals(jexer.bits.Color.WHITE)) {
+                return MYBOLD_WHITE;
+            }
+        } else {
+            if (attr.getForeColor().equals(jexer.bits.Color.BLACK)) {
+                return MYBLACK;
+            } else if (attr.getForeColor().equals(jexer.bits.Color.RED)) {
+                return MYRED;
+            } else if (attr.getForeColor().equals(jexer.bits.Color.BLUE)) {
+                return MYBLUE;
+            } else if (attr.getForeColor().equals(jexer.bits.Color.GREEN)) {
+                return MYGREEN;
+            } else if (attr.getForeColor().equals(jexer.bits.Color.YELLOW)) {
+                return MYYELLOW;
+            } else if (attr.getForeColor().equals(jexer.bits.Color.CYAN)) {
+                return MYCYAN;
+            } else if (attr.getForeColor().equals(jexer.bits.Color.MAGENTA)) {
+                return MYMAGENTA;
+            } else if (attr.getForeColor().equals(jexer.bits.Color.WHITE)) {
+                return MYWHITE;
+            }
+        }
+        throw new IllegalArgumentException("Invalid color: " +
+            attr.getForeColor().getValue());
+    }
+
+    /**
+     * Convert a CellAttributes background color to an Swing Color.
+     *
+     * @param attr the text attributes
+     * @return the Swing Color
+     */
+    private Color attrToBackgroundColor(final CellAttributes attr) {
+        if (attr.getBackColor().equals(jexer.bits.Color.BLACK)) {
+            return MYBLACK;
+        } else if (attr.getBackColor().equals(jexer.bits.Color.RED)) {
+            return MYRED;
+        } else if (attr.getBackColor().equals(jexer.bits.Color.BLUE)) {
+            return MYBLUE;
+        } else if (attr.getBackColor().equals(jexer.bits.Color.GREEN)) {
+            return MYGREEN;
+        } else if (attr.getBackColor().equals(jexer.bits.Color.YELLOW)) {
+            return MYYELLOW;
+        } else if (attr.getBackColor().equals(jexer.bits.Color.CYAN)) {
+            return MYCYAN;
+        } else if (attr.getBackColor().equals(jexer.bits.Color.MAGENTA)) {
+            return MYMAGENTA;
+        } else if (attr.getBackColor().equals(jexer.bits.Color.WHITE)) {
+            return MYWHITE;
+        }
+        throw new IllegalArgumentException("Invalid color: " +
+            attr.getBackColor().getValue());
+    }
+
+    /**
+     * Figure out what textAdjustX and textAdjustY should be, based on the
+     * location of a vertical bar (to find textAdjustY) and a horizontal bar
+     * (to find textAdjustX).
+     *
+     * @return true if textAdjustX and textAdjustY were guessed at correctly
+     */
+    private boolean getFontAdjustments() {
+        BufferedImage image = null;
+
+        // What SHOULD happen is that the topmost/leftmost white pixel is at
+        // position (gr2x, gr2y).  But it might also be off by a pixel in
+        // either direction.
+
+        Graphics2D gr2 = null;
+        int gr2x = 3;
+        int gr2y = 3;
+        image = new BufferedImage(textWidth * 2, textHeight * 2,
+            BufferedImage.TYPE_INT_ARGB);
+
+        gr2 = image.createGraphics();
+        gr2.setFont(swing.getFont());
+        gr2.setColor(java.awt.Color.BLACK);
+        gr2.fillRect(0, 0, textWidth * 2, textHeight * 2);
+        gr2.setColor(java.awt.Color.WHITE);
+        char [] chars = new char[1];
+        chars[0] = jexer.bits.GraphicsChars.VERTICAL_BAR;
+        gr2.drawChars(chars, 0, 1, gr2x, gr2y + textHeight - maxDescent);
+        gr2.dispose();
+
+        for (int x = 0; x < textWidth; x++) {
+            for (int y = 0; y < textHeight; y++) {
+
+                /*
+                System.err.println("X: " + x + " Y: " + y + " " +
+                    image.getRGB(x, y));
+                 */
+
+                if ((image.getRGB(x, y) & 0xFFFFFF) != 0) {
+                    textAdjustY = (gr2y - y);
+
+                    // System.err.println("textAdjustY: " + textAdjustY);
+                    x = textWidth;
+                    break;
+                }
+            }
         }
 
         }
 
-        // Swing thread version: request a repaint, but limit it to the area
-        // that has changed.
+        gr2 = image.createGraphics();
+        gr2.setFont(swing.getFont());
+        gr2.setColor(java.awt.Color.BLACK);
+        gr2.fillRect(0, 0, textWidth * 2, textHeight * 2);
+        gr2.setColor(java.awt.Color.WHITE);
+        chars[0] = jexer.bits.GraphicsChars.SINGLE_BAR;
+        gr2.drawChars(chars, 0, 1, gr2x, gr2y + textHeight - maxDescent);
+        gr2.dispose();
 
 
-        // Find the minimum-size damaged region.
-        int xMin = swing.getWidth();
-        int xMax = 0;
-        int yMin = swing.getHeight();
-        int yMax = 0;
+        for (int x = 0; x < textWidth; x++) {
+            for (int y = 0; y < textHeight; y++) {
 
 
-        synchronized (this) {
-            for (int y = 0; y < height; y++) {
-                for (int x = 0; x < width; x++) {
-                    Cell lCell = logical[x][y];
-                    Cell pCell = physical[x][y];
+                /*
+                System.err.println("X: " + x + " Y: " + y + " " +
+                    image.getRGB(x, y));
+                 */
 
 
-                    int xPixel = x * textWidth + left;
-                    int yPixel = y * textHeight + top;
+                if ((image.getRGB(x, y) & 0xFFFFFF) != 0) {
+                    textAdjustX = (gr2x - x);
 
 
-                    if (!lCell.equals(pCell)
-                        || ((x == cursorX)
-                            && (y == cursorY)
-                            && cursorVisible)
-                        || lCell.isBlink()
-                    ) {
-                        if (xPixel < xMin) {
-                            xMin = xPixel;
-                        }
-                        if (xPixel + textWidth > xMax) {
-                            xMax = xPixel + textWidth;
-                        }
-                        if (yPixel < yMin) {
-                            yMin = yPixel;
-                        }
-                        if (yPixel + textHeight > yMax) {
-                            yMax = yPixel + textHeight;
-                        }
-                    }
+                    // System.err.println("textAdjustX: " + textAdjustX);
+                    return true;
                 }
             }
         }
                 }
             }
         }
-        if (xMin + textWidth >= xMax) {
-            xMax += textWidth;
-        }
-        if (yMin + textHeight >= yMax) {
-            yMax += textHeight;
-        }
-
-        // Repaint the desired area
-        /*
-        System.err.printf("REPAINT X %d %d Y %d %d\n", xMin, xMax,
-            yMin, yMax);
-        */
 
 
-        if ((swing.getFrame() != null) && (swing.getBufferStrategy() != null)) {
-            // This path should never be taken, but is left here for
-            // completeness.
-            Graphics gr = swing.getBufferStrategy().getDrawGraphics();
-            Rectangle bounds = new Rectangle(xMin, yMin, xMax - xMin,
-                yMax - yMin);
-            gr.setClip(bounds);
-            swing.paint(gr);
-            gr.dispose();
-            swing.getBufferStrategy().show();
-            Toolkit.getDefaultToolkit().sync();
-        } else {
-            // Repaint on the Swing thread.
-            swing.repaint(xMin, yMin, xMax - xMin, yMax - yMin);
-        }
+        // Something weird happened, don't rely on this function.
+        // System.err.println("getFontAdjustments: false");
+        return false;
     }
 
     /**
     }
 
     /**
-     * Convert pixel column position to text cell column position.
-     *
-     * @param x pixel column position
-     * @return text cell column position
+     * Figure out my font dimensions.  This code path works OK for the JFrame
+     * case, and can be called immediately after JFrame creation.
      */
      */
-    public int textColumn(final int x) {
-        return ((x - left) / textWidth);
+    private void getFontDimensions() {
+        swing.setFont(font);
+        Graphics gr = swing.getGraphics();
+        if (gr == null) {
+            return;
+        }
+        getFontDimensions(gr);
     }
 
     /**
     }
 
     /**
-     * Convert pixel row position to text cell row position.
+     * Figure out my font dimensions.  This code path is needed to lazy-load
+     * the information inside paint().
      *
      *
-     * @param y pixel row position
-     * @return text cell row position
+     * @param gr Graphics object to use
      */
      */
-    public int textRow(final int y) {
-        return ((y - top) / textHeight);
-    }
+    private void getFontDimensions(final Graphics gr) {
+        swing.setFont(font);
+        FontMetrics fm = gr.getFontMetrics();
+        maxDescent = fm.getMaxDescent();
+        Rectangle2D bounds = fm.getMaxCharBounds(gr);
+        int leading = fm.getLeading();
+        textWidth = (int)Math.round(bounds.getWidth());
+        // textHeight = (int)Math.round(bounds.getHeight()) - maxDescent;
 
 
-    /**
-     * Set the window title.
-     *
-     * @param title the new title
-     */
-    public void setTitle(final String title) {
-        swing.setTitle(title);
-    }
+        // This produces the same number, but works better for ugly
+        // monospace.
+        textHeight = fm.getMaxAscent() + maxDescent - leading;
 
 
-    // ------------------------------------------------------------------------
-    // TerminalReader ---------------------------------------------------------
-    // ------------------------------------------------------------------------
+        if (gotTerminus == true) {
+            textHeight++;
+        }
 
 
-    /**
-     * The session information.
-     */
-    private SwingSessionInfo sessionInfo;
+        if (getFontAdjustments() == false) {
+            // We were unable to programmatically determine textAdjustX and
+            // textAdjustY, so try some guesses based on VM vendor.
+            String runtime = System.getProperty("java.runtime.name");
+            if ((runtime != null) && (runtime.contains("Java(TM)"))) {
+                textAdjustY = -1;
+                textAdjustX = 0;
+            }
+        }
 
 
-    /**
-     * Getter for sessionInfo.
-     *
-     * @return the SessionInfo
-     */
-    public SessionInfo getSessionInfo() {
-        return sessionInfo;
+        if (sessionInfo != null) {
+            sessionInfo.setTextCellDimensions(textWidth, textHeight);
+        }
+        gotFontDimensions = true;
     }
 
     /**
     }
 
     /**
-     * The listening object that run() wakes up on new input.
+     * Resize to font dimensions.
      */
      */
-    private Object listener;
+    public void resizeToScreen() {
+        swing.setDimensions(textWidth * width, textHeight * height);
+    }
 
     /**
 
     /**
-     * Set listener to a different Object.
+     * Draw one glyph to the screen.
      *
      *
-     * @param listener the new listening object that run() wakes up on new
-     * input
+     * @param gr the Swing Graphics context
+     * @param cell the Cell to draw
+     * @param xPixel the x-coordinate to render to.  0 means the
+     * left-most pixel column.
+     * @param yPixel the y-coordinate to render to.  0 means the top-most
+     * pixel row.
      */
      */
-    public void setListener(final Object listener) {
-        this.listener = listener;
-    }
+    private void drawGlyph(final Graphics gr, final Cell cell,
+        final int xPixel, final int yPixel) {
 
 
-    /**
-     * The event queue, filled up by a thread reading on input.
-     */
-    private List<TInputEvent> eventQueue;
+        /*
+        System.err.println("drawGlyph(): " + xPixel + " " + yPixel +
+            " " + cell);
+        */
+
+        BufferedImage image = null;
+        if (cell.isBlink() && !cursorBlinkVisible) {
+            image = glyphCacheBlink.get(cell);
+        } else {
+            image = glyphCache.get(cell);
+        }
+        if (image != null) {
+            if (swing.getFrame() != null) {
+                gr.drawImage(image, xPixel, yPixel, swing.getFrame());
+            } else {
+                gr.drawImage(image, xPixel, yPixel, swing.getComponent());
+            }
+            return;
+        }
+
+        // Generate glyph and draw it.
+        Graphics2D gr2 = null;
+        int gr2x = xPixel;
+        int gr2y = yPixel;
+        if ((SwingComponent.tripleBuffer) && (swing.getFrame() != null)) {
+            image = new BufferedImage(textWidth, textHeight,
+                BufferedImage.TYPE_INT_ARGB);
+            gr2 = image.createGraphics();
+            gr2.setFont(swing.getFont());
+            gr2x = 0;
+            gr2y = 0;
+        } else {
+            gr2 = (Graphics2D) gr;
+        }
+
+        Cell cellColor = new Cell();
+        cellColor.setTo(cell);
+
+        // Check for reverse
+        if (cell.isReverse()) {
+            cellColor.setForeColor(cell.getBackColor());
+            cellColor.setBackColor(cell.getForeColor());
+        }
+
+        // Draw the background rectangle, then the foreground character.
+        gr2.setColor(attrToBackgroundColor(cellColor));
+        gr2.fillRect(gr2x, gr2y, textWidth, textHeight);
+
+        // Handle blink and underline
+        if (!cell.isBlink()
+            || (cell.isBlink() && cursorBlinkVisible)
+        ) {
+            gr2.setColor(attrToForegroundColor(cellColor));
+            char [] chars = new char[1];
+            chars[0] = cell.getChar();
+            gr2.drawChars(chars, 0, 1, gr2x + textAdjustX,
+                gr2y + textHeight - maxDescent + textAdjustY);
+
+            if (cell.isUnderline()) {
+                gr2.fillRect(gr2x, gr2y + textHeight - 2, textWidth, 2);
+            }
+        }
+
+        if ((SwingComponent.tripleBuffer) && (swing.getFrame() != null)) {
+            gr2.dispose();
+
+            // We need a new key that will not be mutated by
+            // invertCell().
+            Cell key = new Cell();
+            key.setTo(cell);
+            if (cell.isBlink() && !cursorBlinkVisible) {
+                glyphCacheBlink.put(key, image);
+            } else {
+                glyphCache.put(key, image);
+            }
 
 
-    /**
-     * The last reported mouse X position.
-     */
-    private int oldMouseX = -1;
+            if (swing.getFrame() != null) {
+                gr.drawImage(image, xPixel, yPixel, swing.getFrame());
+            } else {
+                gr.drawImage(image, xPixel, yPixel, swing.getComponent());
+            }
+        }
 
 
-    /**
-     * The last reported mouse Y position.
-     */
-    private int oldMouseY = -1;
+    }
 
     /**
 
     /**
-     * true if mouse1 was down.  Used to report mouse1 on the release event.
+     * Check if the cursor is visible, and if so draw it.
+     *
+     * @param gr the Swing Graphics context
      */
      */
-    private boolean mouse1 = false;
+    private void drawCursor(final Graphics gr) {
 
 
-    /**
-     * true if mouse2 was down.  Used to report mouse2 on the release event.
-     */
-    private boolean mouse2 = false;
+        if (cursorVisible
+            && (cursorY >= 0)
+            && (cursorX >= 0)
+            && (cursorY <= height - 1)
+            && (cursorX <= width - 1)
+            && cursorBlinkVisible
+        ) {
+            int xPixel = cursorX * textWidth + left;
+            int yPixel = cursorY * textHeight + top;
+            Cell lCell = logical[cursorX][cursorY];
+            gr.setColor(attrToForegroundColor(lCell));
+            switch (cursorStyle) {
+            default:
+                // Fall through...
+            case UNDERLINE:
+                gr.fillRect(xPixel, yPixel + textHeight - 2, textWidth, 2);
+                break;
+            case BLOCK:
+                gr.fillRect(xPixel, yPixel, textWidth, textHeight);
+                break;
+            case OUTLINE:
+                gr.drawRect(xPixel, yPixel, textWidth - 1, textHeight - 1);
+                break;
+            }
+        }
+    }
 
     /**
 
     /**
-     * true if mouse3 was down.  Used to report mouse3 on the release event.
+     * Reset the blink timer.
      */
      */
-    private boolean mouse3 = false;
+    private void resetBlinkTimer() {
+        lastBlinkTime = System.currentTimeMillis();
+        cursorBlinkVisible = true;
+    }
 
     /**
 
     /**
-     * Public constructor creates a new JFrame to render to.
+     * Paint redraws the whole screen.
      *
      *
-     * @param windowWidth the number of text columns to start with
-     * @param windowHeight the number of text rows to start with
-     * @param fontSize the size in points.  Good values to pick are: 16, 20,
-     * 22, and 24.
-     * @param listener the object this backend needs to wake up when new
-     * input comes in
+     * @param gr the Swing Graphics context
      */
      */
-    public SwingTerminal(final int windowWidth, final int windowHeight,
-        final int fontSize, final Object listener) {
-
-        this.fontSize = fontSize;
-
-        setDOSColors();
+    public void paint(final Graphics gr) {
 
 
-        // Figure out my cursor style.
-        String cursorStyleString = System.getProperty(
-            "jexer.Swing.cursorStyle", "underline").toLowerCase();
-        if (cursorStyleString.equals("underline")) {
-            cursorStyle = CursorStyle.UNDERLINE;
-        } else if (cursorStyleString.equals("outline")) {
-            cursorStyle = CursorStyle.OUTLINE;
-        } else if (cursorStyleString.equals("block")) {
-            cursorStyle = CursorStyle.BLOCK;
+        if (gotFontDimensions == false) {
+            // Lazy-load the text width/height
+            getFontDimensions(gr);
+            /*
+            System.err.println("textWidth " + textWidth +
+                " textHeight " + textHeight);
+            System.err.println("FONT: " + swing.getFont() + " font " + font);
+             */
         }
 
         }
 
-        // Pull the system property for triple buffering.
-        if (System.getProperty("jexer.Swing.tripleBuffer") != null) {
-            if (System.getProperty("jexer.Swing.tripleBuffer").equals("true")) {
-                SwingComponent.tripleBuffer = true;
-            } else {
-                SwingComponent.tripleBuffer = false;
-            }
+        if ((swing.getBufferStrategy() != null)
+            && (SwingUtilities.isEventDispatchThread())
+        ) {
+            // System.err.println("paint(), skip first paint on swing thread");
+            return;
         }
 
         }
 
-        try {
-            SwingUtilities.invokeAndWait(new Runnable() {
-                public void run() {
-
-                    JFrame frame = new JFrame() {
-
-                        /**
-                         * Serializable version.
-                         */
-                        private static final long serialVersionUID = 1;
-
-                        /**
-                         * The code that performs the actual drawing.
-                         */
-                        public SwingTerminal screen = null;
+        int xCellMin = 0;
+        int xCellMax = width;
+        int yCellMin = 0;
+        int yCellMax = height;
 
 
-                        /*
-                         * Anonymous class initializer saves the screen
-                         * reference, so that paint() and the like call out
-                         * to SwingTerminal.
-                         */
-                        {
-                            this.screen = SwingTerminal.this;
-                        }
+        Rectangle bounds = gr.getClipBounds();
+        if (bounds != null) {
+            // Only update what is in the bounds
+            xCellMin = textColumn(bounds.x);
+            xCellMax = textColumn(bounds.x + bounds.width);
+            if (xCellMax > width) {
+                xCellMax = width;
+            }
+            if (xCellMin >= xCellMax) {
+                xCellMin = xCellMax - 2;
+            }
+            if (xCellMin < 0) {
+                xCellMin = 0;
+            }
+            yCellMin = textRow(bounds.y);
+            yCellMax = textRow(bounds.y + bounds.height);
+            if (yCellMax > height) {
+                yCellMax = height;
+            }
+            if (yCellMin >= yCellMax) {
+                yCellMin = yCellMax - 2;
+            }
+            if (yCellMin < 0) {
+                yCellMin = 0;
+            }
+        } else {
+            // We need a total repaint
+            reallyCleared = true;
+        }
 
 
-                        /**
-                         * Update redraws the whole screen.
-                         *
-                         * @param gr the Swing Graphics context
-                         */
-                        @Override
-                        public void update(final Graphics gr) {
-                            // The default update clears the area.  Don't do
-                            // that, instead just paint it directly.
-                            paint(gr);
-                        }
+        // Prevent updates to the screen's data from the TApplication
+        // threads.
+        synchronized (this) {
 
 
-                        /**
-                         * Paint redraws the whole screen.
-                         *
-                         * @param gr the Swing Graphics context
-                         */
-                        @Override
-                        public void paint(final Graphics gr) {
-                            if (screen != null) {
-                                screen.paint(gr);
-                            }
-                        }
-                    };
+            /*
+            System.err.printf("bounds %s X %d %d Y %d %d\n",
+                 bounds, xCellMin, xCellMax, yCellMin, yCellMax);
+            */
 
 
-                    // Get the Swing component
-                    SwingTerminal.this.swing = new SwingComponent(frame);
+            for (int y = yCellMin; y < yCellMax; y++) {
+                for (int x = xCellMin; x < xCellMax; x++) {
 
 
-                    // Hang onto top and left for drawing.
-                    Insets insets = SwingTerminal.this.swing.getInsets();
-                    SwingTerminal.this.left = insets.left;
-                    SwingTerminal.this.top = insets.top;
+                    int xPixel = x * textWidth + left;
+                    int yPixel = y * textHeight + top;
 
 
-                    // Load the font so that we can set sessionInfo.
-                    getDefaultFont();
+                    Cell lCell = logical[x][y];
+                    Cell pCell = physical[x][y];
 
 
-                    // Get the default cols x rows and set component size
-                    // accordingly.
-                    SwingTerminal.this.sessionInfo =
-                        new SwingSessionInfo(SwingTerminal.this.swing,
-                            SwingTerminal.this.textWidth,
-                            SwingTerminal.this.textHeight,
-                            windowWidth, windowHeight);
+                    if (!lCell.equals(pCell)
+                        || lCell.isBlink()
+                        || reallyCleared
+                        || (swing.getFrame() == null)) {
 
 
-                    SwingTerminal.this.setDimensions(sessionInfo.getWindowWidth(),
-                        sessionInfo.getWindowHeight());
+                        drawGlyph(gr, lCell, xPixel, yPixel);
 
 
-                    SwingTerminal.this.resizeToScreen();
-                    SwingTerminal.this.swing.setVisible(true);
+                        // Physical is always updated
+                        physical[x][y].setTo(lCell);
+                    }
                 }
                 }
-            });
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-
-        this.listener    = listener;
-        mouse1           = false;
-        mouse2           = false;
-        mouse3           = false;
-        eventQueue       = new LinkedList<TInputEvent>();
+            }
+            drawCursor(gr);
 
 
-        // Add listeners to Swing.
-        swing.addKeyListener(this);
-        swing.addWindowListener(this);
-        swing.addComponentListener(this);
-        swing.addMouseListener(this);
-        swing.addMouseMotionListener(this);
-        swing.addMouseWheelListener(this);
+            reallyCleared = false;
+        } // synchronized (this)
     }
 
     /**
     }
 
     /**
-     * Public constructor renders to an existing JComponent.
-     *
-     * @param component the Swing component to render to
-     * @param windowWidth the number of text columns to start with
-     * @param windowHeight the number of text rows to start with
-     * @param fontSize the size in points.  Good values to pick are: 16, 20,
-     * 22, and 24.
-     * @param listener the object this backend needs to wake up when new
-     * input comes in
+     * Restore terminal to normal state.
      */
      */
-    public SwingTerminal(final JComponent component, final int windowWidth,
-        final int windowHeight, final int fontSize, final Object listener) {
+    public void shutdown() {
+        swing.dispose();
+    }
 
 
-        this.fontSize = fontSize;
+    /**
+     * Push the logical screen to the physical device.
+     */
+    private void drawToSwing() {
 
 
-        setDOSColors();
+        /*
+        System.err.printf("drawToSwing(): reallyCleared %s dirty %s\n",
+            reallyCleared, dirty);
+        */
 
 
-        // Figure out my cursor style.
-        String cursorStyleString = System.getProperty(
-            "jexer.Swing.cursorStyle", "underline").toLowerCase();
-        if (cursorStyleString.equals("underline")) {
-            cursorStyle = CursorStyle.UNDERLINE;
-        } else if (cursorStyleString.equals("outline")) {
-            cursorStyle = CursorStyle.OUTLINE;
-        } else if (cursorStyleString.equals("block")) {
-            cursorStyle = CursorStyle.BLOCK;
+        // If reallyCleared is set, we have to draw everything.
+        if ((swing.getFrame() != null)
+            && (swing.getBufferStrategy() != null)
+            && (reallyCleared == true)
+        ) {
+            // Triple-buffering: we have to redraw everything on this thread.
+            Graphics gr = swing.getBufferStrategy().getDrawGraphics();
+            swing.paint(gr);
+            gr.dispose();
+            swing.getBufferStrategy().show();
+            Toolkit.getDefaultToolkit().sync();
+            return;
+        } else if (((swing.getFrame() != null)
+                && (swing.getBufferStrategy() == null))
+            || (reallyCleared == true)
+        ) {
+            // Repaint everything on the Swing thread.
+            // System.err.println("REPAINT ALL");
+            swing.repaint();
+            return;
         }
 
         }
 
-        try {
-            SwingUtilities.invokeAndWait(new Runnable() {
-                public void run() {
-
-                    JComponent newComponent = new JComponent() {
-
-                        /**
-                         * Serializable version.
-                         */
-                        private static final long serialVersionUID = 1;
+        if ((swing.getFrame() != null) && (swing.getBufferStrategy() != null)) {
+            Graphics gr = swing.getBufferStrategy().getDrawGraphics();
 
 
-                        /**
-                         * The code that performs the actual drawing.
-                         */
-                        public SwingTerminal screen = null;
+            synchronized (this) {
+                for (int y = 0; y < height; y++) {
+                    for (int x = 0; x < width; x++) {
+                        Cell lCell = logical[x][y];
+                        Cell pCell = physical[x][y];
 
 
-                        /*
-                         * Anonymous class initializer saves the screen
-                         * reference, so that paint() and the like call out
-                         * to SwingTerminal.
-                         */
-                        {
-                            this.screen = SwingTerminal.this;
-                        }
+                        int xPixel = x * textWidth + left;
+                        int yPixel = y * textHeight + top;
 
 
-                        /**
-                         * Update redraws the whole screen.
-                         *
-                         * @param gr the Swing Graphics context
-                         */
-                        @Override
-                        public void update(final Graphics gr) {
-                            // The default update clears the area.  Don't do
-                            // that, instead just paint it directly.
-                            paint(gr);
+                        if (!lCell.equals(pCell)
+                            || ((x == cursorX)
+                                && (y == cursorY)
+                                && cursorVisible)
+                            || (lCell.isBlink())
+                        ) {
+                            drawGlyph(gr, lCell, xPixel, yPixel);
+                            physical[x][y].setTo(lCell);
                         }
                         }
+                    }
+                }
+                drawCursor(gr);
+            } // synchronized (this)
 
 
-                        /**
-                         * Paint redraws the whole screen.
-                         *
-                         * @param gr the Swing Graphics context
-                         */
-                        @Override
-                        public void paint(final Graphics gr) {
-                            if (screen != null) {
-                                screen.paint(gr);
-                            }
-                        }
-                    };
-                    component.setLayout(new BorderLayout());
-                    component.add(newComponent);
+            gr.dispose();
+            swing.getBufferStrategy().show();
+            Toolkit.getDefaultToolkit().sync();
+            return;
+        }
 
 
-                    // Allow key events to be received
-                    component.setFocusable(true);
+        // Swing thread version: request a repaint, but limit it to the area
+        // that has changed.
 
 
-                    // Get the Swing component
-                    SwingTerminal.this.swing = new SwingComponent(component);
+        // Find the minimum-size damaged region.
+        int xMin = swing.getWidth();
+        int xMax = 0;
+        int yMin = swing.getHeight();
+        int yMax = 0;
 
 
-                    // Hang onto top and left for drawing.
-                    Insets insets = SwingTerminal.this.swing.getInsets();
-                    SwingTerminal.this.left = insets.left;
-                    SwingTerminal.this.top = insets.top;
+        synchronized (this) {
+            for (int y = 0; y < height; y++) {
+                for (int x = 0; x < width; x++) {
+                    Cell lCell = logical[x][y];
+                    Cell pCell = physical[x][y];
 
 
-                    // Load the font so that we can set sessionInfo.
-                    getDefaultFont();
+                    int xPixel = x * textWidth + left;
+                    int yPixel = y * textHeight + top;
 
 
-                    // Get the default cols x rows and set component size
-                    // accordingly.
-                    SwingTerminal.this.sessionInfo =
-                        new SwingSessionInfo(SwingTerminal.this.swing,
-                            SwingTerminal.this.textWidth,
-                            SwingTerminal.this.textHeight);
+                    if (!lCell.equals(pCell)
+                        || ((x == cursorX)
+                            && (y == cursorY)
+                            && cursorVisible)
+                        || lCell.isBlink()
+                    ) {
+                        if (xPixel < xMin) {
+                            xMin = xPixel;
+                        }
+                        if (xPixel + textWidth > xMax) {
+                            xMax = xPixel + textWidth;
+                        }
+                        if (yPixel < yMin) {
+                            yMin = yPixel;
+                        }
+                        if (yPixel + textHeight > yMax) {
+                            yMax = yPixel + textHeight;
+                        }
+                    }
                 }
                 }
-            });
-        } catch (Exception e) {
-            e.printStackTrace();
+            }
+        }
+        if (xMin + textWidth >= xMax) {
+            xMax += textWidth;
+        }
+        if (yMin + textHeight >= yMax) {
+            yMax += textHeight;
         }
 
         }
 
-        this.listener    = listener;
-        mouse1           = false;
-        mouse2           = false;
-        mouse3           = false;
-        eventQueue       = new LinkedList<TInputEvent>();
+        // Repaint the desired area
+        /*
+        System.err.printf("REPAINT X %d %d Y %d %d\n", xMin, xMax,
+            yMin, yMax);
+        */
 
 
-        // Add listeners to Swing.
-        swing.addKeyListener(this);
-        swing.addWindowListener(this);
-        swing.addComponentListener(this);
-        swing.addMouseListener(this);
-        swing.addMouseMotionListener(this);
-        swing.addMouseWheelListener(this);
+        if ((swing.getFrame() != null) && (swing.getBufferStrategy() != null)) {
+            // This path should never be taken, but is left here for
+            // completeness.
+            Graphics gr = swing.getBufferStrategy().getDrawGraphics();
+            Rectangle bounds = new Rectangle(xMin, yMin, xMax - xMin,
+                yMax - yMin);
+            gr.setClip(bounds);
+            swing.paint(gr);
+            gr.dispose();
+            swing.getBufferStrategy().show();
+            Toolkit.getDefaultToolkit().sync();
+        } else {
+            // Repaint on the Swing thread.
+            swing.repaint(xMin, yMin, xMax - xMin, yMax - yMin);
+        }
     }
 
     /**
     }
 
     /**
-     * Check if there are events in the queue.
+     * Convert pixel column position to text cell column position.
      *
      *
-     * @return if true, getEvents() has something to return to the backend
+     * @param x pixel column position
+     * @return text cell column position
      */
      */
-    public boolean hasEvents() {
-        synchronized (eventQueue) {
-            return (eventQueue.size() > 0);
+    public int textColumn(final int x) {
+        int column = ((x - left) / textWidth);
+        if (column < 0) {
+            column = 0;
         }
         }
+        if (column > width - 1) {
+            column = width - 1;
+        }
+        return column;
     }
 
     /**
     }
 
     /**
-     * Return any events in the IO queue.
+     * Convert pixel row position to text cell row position.
      *
      *
-     * @param queue list to append new events to
+     * @param y pixel row position
+     * @return text cell row position
      */
      */
-    public void getEvents(final List<TInputEvent> queue) {
-        synchronized (eventQueue) {
-            if (eventQueue.size() > 0) {
-                synchronized (queue) {
-                    queue.addAll(eventQueue);
-                }
-                eventQueue.clear();
-            }
+    public int textRow(final int y) {
+        int row = ((y - top) / textHeight);
+        if (row < 0) {
+            row = 0;
+        }
+        if (row > height - 1) {
+            row = height - 1;
         }
         }
+        return row;
     }
 
     /**
     }
 
     /**
-     * Restore terminal to normal state.
+     * Getter for sessionInfo.
+     *
+     * @return the SessionInfo
      */
      */
-    public void closeTerminal() {
-        shutdown();
+    public SessionInfo getSessionInfo() {
+        return sessionInfo;
     }
 
     }
 
+    // ------------------------------------------------------------------------
+    // KeyListener ------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Pass Swing keystrokes into the event queue.
      *
     /**
      * Pass Swing keystrokes into the event queue.
      *
@@ -1550,6 +1604,10 @@ public final class SwingTerminal extends LogicalScreen
         }
     }
 
         }
     }
 
+    // ------------------------------------------------------------------------
+    // WindowListener ---------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Pass window events into the event queue.
      *
     /**
      * Pass window events into the event queue.
      *
@@ -1625,6 +1683,10 @@ public final class SwingTerminal extends LogicalScreen
         // Ignore
     }
 
         // Ignore
     }
 
+    // ------------------------------------------------------------------------
+    // ComponentListener ------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Pass component events into the event queue.
      *
     /**
      * Pass component events into the event queue.
      *
@@ -1672,6 +1734,10 @@ public final class SwingTerminal extends LogicalScreen
                 sessionInfo.getWindowWidth(), sessionInfo.getWindowHeight());
             eventQueue.add(windowResize);
             resetBlinkTimer();
                 sessionInfo.getWindowWidth(), sessionInfo.getWindowHeight());
             eventQueue.add(windowResize);
             resetBlinkTimer();
+            /*
+            System.err.println("Add resize event: " + windowResize.getWidth() +
+                " x " + windowResize.getHeight());
+             */
         }
         if (listener != null) {
             synchronized (listener) {
         }
         if (listener != null) {
             synchronized (listener) {
@@ -1680,6 +1746,10 @@ public final class SwingTerminal extends LogicalScreen
         }
     }
 
         }
     }
 
+    // ------------------------------------------------------------------------
+    // MouseMotionListener ----------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Pass mouse events into the event queue.
      *
     /**
      * Pass mouse events into the event queue.
      *
@@ -1748,6 +1818,10 @@ public final class SwingTerminal extends LogicalScreen
         }
     }
 
         }
     }
 
+    // ------------------------------------------------------------------------
+    // MouseListener ----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Pass mouse events into the event queue.
      *
     /**
      * Pass mouse events into the event queue.
      *
@@ -1862,6 +1936,10 @@ public final class SwingTerminal extends LogicalScreen
         }
     }
 
         }
     }
 
+    // ------------------------------------------------------------------------
+    // MouseWheelListener -----------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Pass mouse events into the event queue.
      *
     /**
      * Pass mouse events into the event queue.
      *
index bc32175398ddc8c77d91a08deb4c7824710baa30..63c12f565a9c24f63d6846ca92a09c4cdce77b45 100644 (file)
@@ -34,6 +34,10 @@ package jexer.backend;
  */
 public final class TSessionInfo implements SessionInfo {
 
  */
 public final class TSessionInfo implements SessionInfo {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * User name.
      */
     /**
      * User name.
      */
@@ -54,6 +58,32 @@ public final class TSessionInfo implements SessionInfo {
      */
     private int windowHeight = 24;
 
      */
     private int windowHeight = 24;
 
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Public constructor.
+     */
+    public TSessionInfo() {
+        this(80, 24);
+    }
+
+    /**
+     * Public constructor.
+     *
+     * @param width the number of columns
+     * @param height the number of rows
+     */
+    public TSessionInfo(final int width, final int height) {
+        this.windowWidth        = width;
+        this.windowHeight       = height;
+    }
+
+    // ------------------------------------------------------------------------
+    // SessionInfo ------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Username getter.
      *
     /**
      * Username getter.
      *
@@ -115,22 +145,4 @@ public final class TSessionInfo implements SessionInfo {
         // NOP
     }
 
         // NOP
     }
 
-    /**
-     * Public constructor.
-     */
-    public TSessionInfo() {
-        this(80, 24);
-    }
-
-    /**
-     * Public constructor.
-     *
-     * @param width the number of columns
-     * @param height the number of rows
-     */
-    public TSessionInfo(final int width, final int height) {
-        this.windowWidth        = width;
-        this.windowHeight       = height;
-    }
-
 }
 }
index 67b2e5c4b9f357e125cb4077ec1e38a0d76e49fc..28b4bd65d793f3652e69865155bbc61e07fcf1fb 100644 (file)
@@ -40,6 +40,10 @@ import java.util.StringTokenizer;
  */
 public final class TTYSessionInfo implements SessionInfo {
 
  */
 public final class TTYSessionInfo implements SessionInfo {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * User name.
      */
     /**
      * User name.
      */
@@ -65,6 +69,24 @@ public final class TTYSessionInfo implements SessionInfo {
      */
     private long lastQueryWindowTime;
 
      */
     private long lastQueryWindowTime;
 
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Public constructor.
+     */
+    public TTYSessionInfo() {
+        // Populate lang and user from the environment
+        username = System.getProperty("user.name");
+        language = System.getProperty("user.language");
+        queryWindowSize();
+    }
+
+    // ------------------------------------------------------------------------
+    // SessionInfo ------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Username getter.
      *
     /**
      * Username getter.
      *
@@ -101,54 +123,6 @@ public final class TTYSessionInfo implements SessionInfo {
         this.language = language;
     }
 
         this.language = language;
     }
 
-    /**
-     * Call 'stty size' to obtain the tty window size.  windowWidth and
-     * windowHeight are set automatically.
-     */
-    private void sttyWindowSize() {
-        String [] cmd = {
-            "/bin/sh", "-c", "stty size < /dev/tty"
-        };
-        try {
-            Process process = Runtime.getRuntime().exec(cmd);
-            BufferedReader in = new BufferedReader(
-                new InputStreamReader(process.getInputStream(), "UTF-8"));
-            String line = in.readLine();
-            if ((line != null) && (line.length() > 0)) {
-                StringTokenizer tokenizer = new StringTokenizer(line);
-                int rc = Integer.parseInt(tokenizer.nextToken());
-                if (rc > 0) {
-                    windowHeight = rc;
-                }
-                rc = Integer.parseInt(tokenizer.nextToken());
-                if (rc > 0) {
-                    windowWidth = rc;
-                }
-            }
-            while (true) {
-                BufferedReader err = new BufferedReader(
-                        new InputStreamReader(process.getErrorStream(),
-                            "UTF-8"));
-                line = err.readLine();
-                if ((line != null) && (line.length() > 0)) {
-                    System.err.println("Error output from stty: " + line);
-                }
-                try {
-                    process.waitFor();
-                    break;
-                } catch (InterruptedException e) {
-                    e.printStackTrace();
-                }
-            }
-            int rc = process.exitValue();
-            if (rc != 0) {
-                System.err.println("stty returned error code: " + rc);
-            }
-        } catch (IOException e) {
-            e.printStackTrace();
-        }
-    }
-
     /**
      * Text window width getter.
      *
     /**
      * Text window width getter.
      *
@@ -199,13 +173,56 @@ public final class TTYSessionInfo implements SessionInfo {
         }
     }
 
         }
     }
 
+    // ------------------------------------------------------------------------
+    // TTYSessionInfo ---------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
     /**
-     * Public constructor.
+     * Call 'stty size' to obtain the tty window size.  windowWidth and
+     * windowHeight are set automatically.
      */
      */
-    public TTYSessionInfo() {
-        // Populate lang and user from the environment
-        username = System.getProperty("user.name");
-        language = System.getProperty("user.language");
-        queryWindowSize();
+    private void sttyWindowSize() {
+        String [] cmd = {
+            "/bin/sh", "-c", "stty size < /dev/tty"
+        };
+        try {
+            Process process = Runtime.getRuntime().exec(cmd);
+            BufferedReader in = new BufferedReader(
+                new InputStreamReader(process.getInputStream(), "UTF-8"));
+            String line = in.readLine();
+            if ((line != null) && (line.length() > 0)) {
+                StringTokenizer tokenizer = new StringTokenizer(line);
+                int rc = Integer.parseInt(tokenizer.nextToken());
+                if (rc > 0) {
+                    windowHeight = rc;
+                }
+                rc = Integer.parseInt(tokenizer.nextToken());
+                if (rc > 0) {
+                    windowWidth = rc;
+                }
+            }
+            while (true) {
+                BufferedReader err = new BufferedReader(
+                        new InputStreamReader(process.getErrorStream(),
+                            "UTF-8"));
+                line = err.readLine();
+                if ((line != null) && (line.length() > 0)) {
+                    System.err.println("Error output from stty: " + line);
+                }
+                try {
+                    process.waitFor();
+                    break;
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+            int rc = process.exitValue();
+            if (rc != 0) {
+                System.err.println("stty returned error code: " + rc);
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
     }
     }
+
 }
 }
index 7652d3f2300811f5e349916bc214f987714259f3..41809cff5c5cd0452b8d9d508de8c02fe9d3bfb9 100644 (file)
@@ -48,6 +48,10 @@ import jexer.TWindow;
  */
 public class TWindowBackend extends TWindow implements Backend {
 
  */
 public class TWindowBackend extends TWindow implements Backend {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * The listening object that run() wakes up on new input.
      */
     /**
      * The listening object that run() wakes up on new input.
      */
@@ -84,32 +88,9 @@ public class TWindowBackend extends TWindow implements Backend {
      */
     private SessionInfo sessionInfo;
 
      */
     private SessionInfo sessionInfo;
 
-    /**
-     * Set the object to sync to in draw().
-     *
-     * @param drawLock the object to synchronize on
-     */
-    public void setDrawLock(final Object drawLock) {
-        this.drawLock = drawLock;
-    }
-
-    /**
-     * Getter for the other application's screen.
-     *
-     * @return the Screen
-     */
-    public Screen getOtherScreen() {
-        return otherScreen;
-    }
-
-    /**
-     * Getter for sessionInfo.
-     *
-     * @return the SessionInfo
-     */
-    public final SessionInfo getSessionInfo() {
-        return sessionInfo;
-    }
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Public constructor.  Window will be located at (0, 0).
 
     /**
      * Public constructor.  Window will be located at (0, 0).
@@ -214,97 +195,9 @@ public class TWindowBackend extends TWindow implements Backend {
         drawLock = otherScreen;
     }
 
         drawLock = otherScreen;
     }
 
-    /**
-     * Subclasses must provide an implementation that syncs the logical
-     * screen to the physical device.
-     */
-    public void flushScreen() {
-        getApplication().doRepaint();
-    }
-
-    /**
-     * Subclasses must provide an implementation to get keyboard, mouse, and
-     * screen resize events.
-     *
-     * @param queue list to append new events to
-     */
-    public void getEvents(List<TInputEvent> queue) {
-        synchronized (eventQueue) {
-            if (eventQueue.size() > 0) {
-                synchronized (queue) {
-                    queue.addAll(eventQueue);
-                }
-                eventQueue.clear();
-            }
-        }
-    }
-
-    /**
-     * Subclasses must provide an implementation that closes sockets,
-     * restores console, etc.
-     */
-    public void shutdown() {
-        // NOP
-    }
-
-    /**
-     * Set listener to a different Object.
-     *
-     * @param listener the new listening object that run() wakes up on new
-     * input
-     */
-    public void setListener(final Object listener) {
-        this.listener = listener;
-    }
-
-    /**
-     * Draw the foreground colors grid.
-     */
-    @Override
-    public void draw() {
-
-        // Sync on other screen, so that we do not draw in the middle of
-        // their screen update.
-        synchronized (drawLock) {
-            // Draw the box
-            super.draw();
-
-            // Draw every cell of the other screen
-            for (int y = 0; y < otherScreen.getHeight(); y++) {
-                for (int x = 0; x < otherScreen.getWidth(); x++) {
-                    putCharXY(x + 1, y + 1, otherScreen.getCharXY(x, y));
-                }
-            }
-
-            // If the mouse pointer is over the other window, draw its
-            // pointer again here.  (Their TApplication drew it, then our
-            // TApplication drew it again (undo-ing it), so now we draw it a
-            // third time so that it is visible.)
-            if ((otherMouseX != -1) && (otherMouseY != -1)) {
-                CellAttributes attr = getAttrXY(otherMouseX, otherMouseY);
-                attr.setForeColor(attr.getForeColor().invert());
-                attr.setBackColor(attr.getBackColor().invert());
-                putAttrXY(otherMouseX, otherMouseY, attr, false);
-            }
-
-            // If their cursor is visible, draw that here too.
-            if (otherScreen.isCursorVisible()) {
-                setCursorX(otherScreen.getCursorX() + 1);
-                setCursorY(otherScreen.getCursorY() + 1);
-                setCursorVisible(true);
-            } else {
-                setCursorVisible(false);
-            }
-        }
-    }
-
-    /**
-     * Subclasses should override this method to cleanup resources.  This is
-     * called by application.closeWindow().
-     */
-    public void onClose() {
-        // TODO: send a screen disconnect
-    }
+    // ------------------------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Returns true if the mouse is currently in the otherScreen window.
 
     /**
      * Returns true if the mouse is currently in the otherScreen window.
@@ -413,4 +306,147 @@ public class TWindowBackend extends TWindow implements Backend {
         }
     }
 
         }
     }
 
+    // ------------------------------------------------------------------------
+    // TWindow ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Draw the foreground colors grid.
+     */
+    @Override
+    public void draw() {
+
+        // Sync on other screen, so that we do not draw in the middle of
+        // their screen update.
+        synchronized (drawLock) {
+            // Draw the box
+            super.draw();
+
+            // Draw every cell of the other screen
+            for (int y = 0; y < otherScreen.getHeight(); y++) {
+                for (int x = 0; x < otherScreen.getWidth(); x++) {
+                    putCharXY(x + 1, y + 1, otherScreen.getCharXY(x, y));
+                }
+            }
+
+            // If the mouse pointer is over the other window, draw its
+            // pointer again here.  (Their TApplication drew it, then our
+            // TApplication drew it again (undo-ing it), so now we draw it a
+            // third time so that it is visible.)
+            if ((otherMouseX != -1) && (otherMouseY != -1)) {
+                CellAttributes attr = getAttrXY(otherMouseX, otherMouseY);
+                attr.setForeColor(attr.getForeColor().invert());
+                attr.setBackColor(attr.getBackColor().invert());
+                putAttrXY(otherMouseX, otherMouseY, attr, false);
+            }
+
+            // If their cursor is visible, draw that here too.
+            if (otherScreen.isCursorVisible()) {
+                setCursorX(otherScreen.getCursorX() + 1);
+                setCursorY(otherScreen.getCursorY() + 1);
+                setCursorVisible(true);
+            } else {
+                setCursorVisible(false);
+            }
+        }
+    }
+
+    /**
+     * Subclasses should override this method to cleanup resources.  This is
+     * called by application.closeWindow().
+     */
+    @Override
+    public void onClose() {
+        // TODO: send a screen disconnect
+    }
+
+    // ------------------------------------------------------------------------
+    // Backend ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Getter for sessionInfo.
+     *
+     * @return the SessionInfo
+     */
+    public final SessionInfo getSessionInfo() {
+        return sessionInfo;
+    }
+
+    /**
+     * Subclasses must provide an implementation that syncs the logical
+     * screen to the physical device.
+     */
+    public void flushScreen() {
+        getApplication().doRepaint();
+    }
+
+    /**
+     * Check if there are events in the queue.
+     *
+     * @return if true, getEvents() has something to return to the application
+     */
+    public boolean hasEvents() {
+        synchronized (eventQueue) {
+            return (eventQueue.size() > 0);
+        }
+    }
+
+    /**
+     * Subclasses must provide an implementation to get keyboard, mouse, and
+     * screen resize events.
+     *
+     * @param queue list to append new events to
+     */
+    public void getEvents(List<TInputEvent> queue) {
+        synchronized (eventQueue) {
+            if (eventQueue.size() > 0) {
+                synchronized (queue) {
+                    queue.addAll(eventQueue);
+                }
+                eventQueue.clear();
+            }
+        }
+    }
+
+    /**
+     * Subclasses must provide an implementation that closes sockets,
+     * restores console, etc.
+     */
+    public void shutdown() {
+        // NOP
+    }
+
+    /**
+     * Set listener to a different Object.
+     *
+     * @param listener the new listening object that run() wakes up on new
+     * input
+     */
+    public void setListener(final Object listener) {
+        this.listener = listener;
+    }
+
+    // ------------------------------------------------------------------------
+    // TWindowBackend ---------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Set the object to sync to in draw().
+     *
+     * @param drawLock the object to synchronize on
+     */
+    public void setDrawLock(final Object drawLock) {
+        this.drawLock = drawLock;
+    }
+
+    /**
+     * Getter for the other application's screen.
+     *
+     * @return the Screen
+     */
+    public Screen getOtherScreen() {
+        return otherScreen;
+    }
+
 }
 }
index eaaab6b7695c5a17b16d775545e0264df8a5df8c..56633e779b245a0b2ba90df3e34d7d51414af851 100644 (file)
@@ -33,11 +33,45 @@ package jexer.bits;
  */
 public final class Cell extends CellAttributes {
 
  */
 public final class Cell extends CellAttributes {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * The character at this cell.
      */
     private char ch;
 
     /**
      * The character at this cell.
      */
     private char ch;
 
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Public constructor sets default values of the cell to blank.
+     *
+     * @see #isBlank()
+     * @see #reset()
+     */
+    public Cell() {
+        reset();
+    }
+
+    /**
+     * Public constructor sets the character.  Attributes are the same as
+     * default.
+     *
+     * @param ch character to set to
+     * @see #reset()
+     */
+    public Cell(final char ch) {
+        reset();
+        this.ch = ch;
+    }
+
+    // ------------------------------------------------------------------------
+    // Cell -------------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Getter for cell character.
      *
     /**
      * Getter for cell character.
      *
@@ -149,28 +183,6 @@ public final class Cell extends CellAttributes {
         super.setTo(that);
     }
 
         super.setTo(that);
     }
 
-    /**
-     * Public constructor sets default values of the cell to blank.
-     *
-     * @see #isBlank()
-     * @see #reset()
-     */
-    public Cell() {
-        reset();
-    }
-
-    /**
-     * Public constructor sets the character.  Attributes are the same as
-     * default.
-     *
-     * @param ch character to set to
-     * @see #reset()
-     */
-    public Cell(final char ch) {
-        reset();
-        this.ch = ch;
-    }
-
     /**
      * Make human-readable description of this Cell.
      *
     /**
      * Make human-readable description of this Cell.
      *
index c14557679ee4ff28298021d492b502661ebd0e2a..ec3f6ddb6d9518be3302c8aa49ee07a2a0433310 100644 (file)
@@ -33,11 +33,73 @@ package jexer.bits;
  */
 public class CellAttributes {
 
  */
 public class CellAttributes {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Bold attribute.
      */
     private boolean bold;
 
     /**
      * Bold attribute.
      */
     private boolean bold;
 
+    /**
+     * Blink attribute.
+     */
+    private boolean blink;
+
+    /**
+     * Reverse attribute.
+     */
+    private boolean reverse;
+
+    /**
+     * Underline attribute.
+     */
+    private boolean underline;
+
+    /**
+     * Protected attribute.
+     */
+    private boolean protect;
+
+    /**
+     * Foreground color.  Color.WHITE, Color.RED, etc.
+     */
+    private Color foreColor;
+
+    /**
+     * Background color.  Color.WHITE, Color.RED, etc.
+     */
+    private Color backColor;
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Public constructor sets default values of the cell to white-on-black,
+     * no bold/blink/reverse/underline/protect.
+     *
+     * @see #reset()
+     */
+    public CellAttributes() {
+        reset();
+    }
+
+    /**
+     * Public constructor makes a copy from another instance.
+     *
+     * @param that another CellAttributes instance
+     * @see #reset()
+     */
+    public CellAttributes(final CellAttributes that) {
+        setTo(that);
+    }
+
+    // ------------------------------------------------------------------------
+    // CellAttributes ---------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Getter for bold.
      *
     /**
      * Getter for bold.
      *
@@ -56,11 +118,6 @@ public class CellAttributes {
         this.bold = bold;
     }
 
         this.bold = bold;
     }
 
-    /**
-     * Blink attribute.
-     */
-    private boolean blink;
-
     /**
      * Getter for blink.
      *
     /**
      * Getter for blink.
      *
@@ -79,11 +136,6 @@ public class CellAttributes {
         this.blink = blink;
     }
 
         this.blink = blink;
     }
 
-    /**
-     * Reverse attribute.
-     */
-    private boolean reverse;
-
     /**
      * Getter for reverse.
      *
     /**
      * Getter for reverse.
      *
@@ -102,11 +154,6 @@ public class CellAttributes {
         this.reverse = reverse;
     }
 
         this.reverse = reverse;
     }
 
-    /**
-     * Underline attribute.
-     */
-    private boolean underline;
-
     /**
      * Getter for underline.
      *
     /**
      * Getter for underline.
      *
@@ -125,11 +172,6 @@ public class CellAttributes {
         this.underline = underline;
     }
 
         this.underline = underline;
     }
 
-    /**
-     * Protected attribute.
-     */
-    private boolean protect;
-
     /**
      * Getter for protect.
      *
     /**
      * Getter for protect.
      *
@@ -148,11 +190,6 @@ public class CellAttributes {
         this.protect = protect;
     }
 
         this.protect = protect;
     }
 
-    /**
-     * Foreground color.  Color.WHITE, Color.RED, etc.
-     */
-    private Color foreColor;
-
     /**
      * Getter for foreColor.
      *
     /**
      * Getter for foreColor.
      *
@@ -171,11 +208,6 @@ public class CellAttributes {
         this.foreColor = foreColor;
     }
 
         this.foreColor = foreColor;
     }
 
-    /**
-     * Background color.  Color.WHITE, Color.RED, etc.
-     */
-    private Color backColor;
-
     /**
      * Getter for backColor.
      *
     /**
      * Getter for backColor.
      *
@@ -208,26 +240,6 @@ public class CellAttributes {
         backColor = Color.BLACK;
     }
 
         backColor = Color.BLACK;
     }
 
-    /**
-     * Public constructor sets default values of the cell to white-on-black,
-     * no bold/blink/reverse/underline/protect.
-     *
-     * @see #reset()
-     */
-    public CellAttributes() {
-        reset();
-    }
-
-    /**
-     * Public constructor makes a copy from another instance.
-     *
-     * @param that another CellAttributes instance
-     * @see #reset()
-     */
-    public CellAttributes(final CellAttributes that) {
-        setTo(that);
-    }
-
     /**
      * Comparison check.  All fields must match to return true.
      *
     /**
      * Comparison check.  All fields must match to return true.
      *
index 0f4e6b32bf35229f3d018084eee52287f1dd5c52..120ff64a1c75f3dc7aeaf9da81aeb716c8787ff2 100644 (file)
@@ -33,62 +33,9 @@ package jexer.bits;
  */
 public final class Color {
 
  */
 public final class Color {
 
-    /**
-     * The color value.  Default is SGRWHITE.
-     */
-    private int value = SGRWHITE;
-
-    /**
-     * Get color value.  Note that these deliberately match the color values
-     * of the ECMA-48 / ANSI X3.64 / VT100-ish SGR function ("ANSI colors").
-     *
-     * @return the value
-     */
-    public int getValue() {
-        return value;
-    }
-
-    /**
-     * Private constructor used to make the static Color instances.
-     *
-     * @param value the integer Color value
-     */
-    private Color(final int value) {
-        this.value = value;
-    }
-
-    /**
-     * Public constructor returns one of the static Color instances.
-     *
-     * @param colorName "red", "blue", etc.
-     * @return Color.RED, Color.BLUE, etc.
-     */
-    static Color getColor(final String colorName) {
-        String str = colorName.toLowerCase();
-
-        if (str.equals("black")) {
-            return Color.BLACK;
-        } else if (str.equals("white")) {
-            return Color.WHITE;
-        } else if (str.equals("red")) {
-            return Color.RED;
-        } else if (str.equals("cyan")) {
-            return Color.CYAN;
-        } else if (str.equals("green")) {
-            return Color.GREEN;
-        } else if (str.equals("magenta")) {
-            return Color.MAGENTA;
-        } else if (str.equals("blue")) {
-            return Color.BLUE;
-        } else if (str.equals("yellow")) {
-            return Color.YELLOW;
-        } else if (str.equals("brown")) {
-            return Color.YELLOW;
-        } else {
-            // Let unknown strings become white
-            return Color.WHITE;
-        }
-    }
+    // ------------------------------------------------------------------------
+    // Constants --------------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * SGR black value = 0.
 
     /**
      * SGR black value = 0.
@@ -170,6 +117,75 @@ public final class Color {
      */
     public static final Color WHITE = new Color(SGRWHITE);
 
      */
     public static final Color WHITE = new Color(SGRWHITE);
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * The color value.  Default is SGRWHITE.
+     */
+    private int value = SGRWHITE;
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Private constructor used to make the static Color instances.
+     *
+     * @param value the integer Color value
+     */
+    private Color(final int value) {
+        this.value = value;
+    }
+
+    // ------------------------------------------------------------------------
+    // Color ------------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Get color value.  Note that these deliberately match the color values
+     * of the ECMA-48 / ANSI X3.64 / VT100-ish SGR function ("ANSI colors").
+     *
+     * @return the value
+     */
+    public int getValue() {
+        return value;
+    }
+
+    /**
+     * Public constructor returns one of the static Color instances.
+     *
+     * @param colorName "red", "blue", etc.
+     * @return Color.RED, Color.BLUE, etc.
+     */
+    static Color getColor(final String colorName) {
+        String str = colorName.toLowerCase();
+
+        if (str.equals("black")) {
+            return Color.BLACK;
+        } else if (str.equals("white")) {
+            return Color.WHITE;
+        } else if (str.equals("red")) {
+            return Color.RED;
+        } else if (str.equals("cyan")) {
+            return Color.CYAN;
+        } else if (str.equals("green")) {
+            return Color.GREEN;
+        } else if (str.equals("magenta")) {
+            return Color.MAGENTA;
+        } else if (str.equals("blue")) {
+            return Color.BLUE;
+        } else if (str.equals("yellow")) {
+            return Color.YELLOW;
+        } else if (str.equals("brown")) {
+            return Color.YELLOW;
+        } else {
+            // Let unknown strings become white
+            return Color.WHITE;
+        }
+    }
+
     /**
      * Invert a color in the same way as (CGA/VGA color XOR 0x7).
      *
     /**
      * Invert a color in the same way as (CGA/VGA color XOR 0x7).
      *
index baf7685b4ef2331a2cb07a41e67700bc745a4c00..0c6f6e4a1ea35eccb6c1906948fa41ef6be75640 100644 (file)
@@ -46,11 +46,19 @@ import java.util.TreeMap;
  */
 public final class ColorTheme {
 
  */
 public final class ColorTheme {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * The current theme colors.
      */
     private SortedMap<String, CellAttributes> colors;
 
     /**
      * The current theme colors.
      */
     private SortedMap<String, CellAttributes> colors;
 
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Public constructor sets the theme to the default.
      */
     /**
      * Public constructor sets the theme to the default.
      */
@@ -59,6 +67,10 @@ public final class ColorTheme {
         setDefaultTheme();
     }
 
         setDefaultTheme();
     }
 
+    // ------------------------------------------------------------------------
+    // ColorTheme -------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Retrieve the CellAttributes for a named theme color.
      *
     /**
      * Retrieve the CellAttributes for a named theme color.
      *
@@ -454,6 +466,11 @@ public final class ColorTheme {
         color.setBackColor(Color.BLUE);
         color.setBold(false);
         colors.put("ttreeview.inactive", color);
         color.setBackColor(Color.BLUE);
         color.setBold(false);
         colors.put("ttreeview.inactive", color);
+        color = new CellAttributes();
+        color.setForeColor(Color.BLACK);
+        color.setBackColor(Color.WHITE);
+        color.setBold(false);
+        colors.put("ttreeview.selected.inactive", color);
 
         // TList
         color = new CellAttributes();
 
         // TList
         color = new CellAttributes();
@@ -479,6 +496,11 @@ public final class ColorTheme {
         color.setBackColor(Color.BLUE);
         color.setBold(false);
         colors.put("tlist.inactive", color);
         color.setBackColor(Color.BLUE);
         color.setBold(false);
         colors.put("tlist.inactive", color);
+        color = new CellAttributes();
+        color.setForeColor(Color.BLACK);
+        color.setBackColor(Color.WHITE);
+        color.setBold(false);
+        colors.put("tlist.selected.inactive", color);
 
         // TStatusBar
         color = new CellAttributes();
 
         // TStatusBar
         color = new CellAttributes();
index 32403090a4301a0ce800b1dfe1fe3668cd335b20..b571639a0384d4e73fdbb8313da22e55c49f994b 100644 (file)
@@ -34,11 +34,9 @@ package jexer.bits;
  */
 public final class GraphicsChars {
 
  */
 public final class GraphicsChars {
 
-    /**
-     * Private constructor prevents accidental creation of this class.
-     */
-    private GraphicsChars() {
-    }
+    // ------------------------------------------------------------------------
+    // Constants --------------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * The CP437 to Unicode translation map.
 
     /**
      * The CP437 to Unicode translation map.
@@ -147,4 +145,15 @@ public final class GraphicsChars {
     public static final char WINDOW_RIGHT_BOTTOM_DOUBLE = CP437[0xBC];
     public static final char VERTICAL_BAR               = CP437[0xB3];
     public static final char OCTOSTAR                   = CP437[0x0F];
     public static final char WINDOW_RIGHT_BOTTOM_DOUBLE = CP437[0xBC];
     public static final char VERTICAL_BAR               = CP437[0xB3];
     public static final char OCTOSTAR                   = CP437[0x0F];
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Private constructor prevents accidental creation of this class.
+     */
+    private GraphicsChars() {
+    }
+
 }
 }
index 327987987a84b0d0410100a382f4f86f34ff4f79..edd5227b7136349be47211f3e814a16cbd70c87b 100644 (file)
@@ -36,47 +36,28 @@ package jexer.bits;
  */
 public final class MnemonicString {
 
  */
 public final class MnemonicString {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Keyboard shortcut to activate this item.
      */
     private char shortcut;
 
     /**
      * Keyboard shortcut to activate this item.
      */
     private char shortcut;
 
-    /**
-     * Get the keyboard shortcut character.
-     *
-     * @return the highlighted character
-     */
-    public char getShortcut() {
-        return shortcut;
-    }
-
     /**
      * Location of the highlighted character.
      */
     private int shortcutIdx = -1;
 
     /**
      * Location of the highlighted character.
      */
     private int shortcutIdx = -1;
 
-    /**
-     * Get location of the highlighted character.
-     *
-     * @return location of the highlighted character
-     */
-    public int getShortcutIdx() {
-        return shortcutIdx;
-    }
-
     /**
      * The raw (uncolored) string.
      */
     private String rawLabel;
 
     /**
      * The raw (uncolored) string.
      */
     private String rawLabel;
 
-    /**
-     * Get the raw (uncolored) string.
-     *
-     * @return the raw (uncolored) string
-     */
-    public String getRawLabel() {
-        return rawLabel;
-    }
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Public constructor.
 
     /**
      * Public constructor.
@@ -116,4 +97,36 @@ public final class MnemonicString {
         }
         this.rawLabel = newLabel;
     }
         }
         this.rawLabel = newLabel;
     }
+
+    // ------------------------------------------------------------------------
+    // MnemonicString ---------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Get the keyboard shortcut character.
+     *
+     * @return the highlighted character
+     */
+    public char getShortcut() {
+        return shortcut;
+    }
+
+    /**
+     * Get location of the highlighted character.
+     *
+     * @return location of the highlighted character
+     */
+    public int getShortcutIdx() {
+        return shortcutIdx;
+    }
+
+    /**
+     * Get the raw (uncolored) string.
+     *
+     * @return the raw (uncolored) string
+     */
+    public String getRawLabel() {
+        return rawLabel;
+    }
+
 }
 }
similarity index 85%
rename from src/jexer/bits/StringJustifier.java
rename to src/jexer/bits/StringUtils.java
index bbaa9cb18722f7104c6996c3160d577dce122025..535720da7f82d235eb52465500ab802e2c84f97c 100644 (file)
@@ -32,10 +32,15 @@ import java.util.List;
 import java.util.LinkedList;
 
 /**
 import java.util.LinkedList;
 
 /**
- * StringJustifier contains methods to convert one or more long lines of
- * strings into justified text paragraphs.
+ * StringUtils contains methods to:
+ *
+ *    - Convert one or more long lines of strings into justified text
+ *      paragraphs.
+ *
+ *    - Unescape C0 control codes.
+ *
  */
  */
-public final class StringJustifier {
+public final class StringUtils {
 
     /**
      * Left-justify a string into a list of lines.
 
     /**
      * Left-justify a string into a list of lines.
@@ -237,4 +242,45 @@ public final class StringJustifier {
         return result;
     }
 
         return result;
     }
 
+    /**
+     * Convert raw strings into escaped strings that be splatted on the
+     * screen.
+     *
+     * @param str the string
+     * @return a string that can be passed into Screen.putStringXY()
+     */
+    public static String unescape(final String str) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < str.length(); i++) {
+            char ch = str.charAt(i);
+            if ((ch < 0x20) || (ch == 0x7F)) {
+                switch (ch) {
+                case '\b':
+                    sb.append("\\b");
+                    continue;
+                case '\f':
+                    sb.append("\\f");
+                    continue;
+                case '\n':
+                    sb.append("\\n");
+                    continue;
+                case '\r':
+                    sb.append("\\r");
+                    continue;
+                case '\t':
+                    sb.append("\\t");
+                    continue;
+                case 0x7f:
+                    sb.append("^?");
+                    continue;
+                default:
+                    sb.append(' ');
+                    continue;
+                }
+            }
+            sb.append(ch);
+        }
+        return sb.toString();
+    }
+
 }
 }
index 7697981c015bb8b0db59874b0091ee3697ba8332..a4bcbe25ef04ee854d85f99bd4ed3f9a54dbd27e 100644 (file)
@@ -32,6 +32,7 @@ import java.io.IOException;
 
 import jexer.*;
 import jexer.event.*;
 
 import jexer.*;
 import jexer.event.*;
+import jexer.ttree.*;
 import static jexer.TCommand.*;
 import static jexer.TKeypress.*;
 
 import static jexer.TCommand.*;
 import static jexer.TKeypress.*;
 
@@ -43,7 +44,7 @@ public class DemoTreeViewWindow extends TWindow {
     /**
      * Hang onto my TTreeView so I can resize it with the window.
      */
     /**
      * Hang onto my TTreeView so I can resize it with the window.
      */
-    private TTreeView treeView;
+    private TTreeViewWidget treeView;
 
     /**
      * Public constructor.
 
     /**
      * Public constructor.
@@ -55,7 +56,7 @@ public class DemoTreeViewWindow extends TWindow {
         super(parent, "Tree View", 0, 0, 44, 16, TWindow.RESIZABLE);
 
         // Load the treeview with "stuff"
         super(parent, "Tree View", 0, 0, 44, 16, TWindow.RESIZABLE);
 
         // Load the treeview with "stuff"
-        treeView = addTreeView(1, 1, 40, 12);
+        treeView = addTreeViewWidget(1, 1, 40, 12);
         new TDirectoryTreeItem(treeView, ".", true);
 
         statusBar = newStatusBar("Treeview demonstration");
         new TDirectoryTreeItem(treeView, ".", true);
 
         statusBar = newStatusBar("Treeview demonstration");
index e4b44833fe4a0893e58de6c53df28858069a6ca8..a6a0df2a98a90a7c0ca5f42e706f784de72ca6d0 100644 (file)
@@ -35,6 +35,10 @@ package jexer.event;
  */
 public final class TMouseEvent extends TInputEvent {
 
  */
 public final class TMouseEvent extends TInputEvent {
 
+    // ------------------------------------------------------------------------
+    // Constants --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * The type of event generated.
      */
     /**
      * The type of event generated.
      */
@@ -60,11 +64,99 @@ public final class TMouseEvent extends TInputEvent {
         MOUSE_DOUBLE_CLICK
     }
 
         MOUSE_DOUBLE_CLICK
     }
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Type of event, one of MOUSE_MOTION, MOUSE_UP, or MOUSE_DOWN.
      */
     private Type type;
 
     /**
      * Type of event, one of MOUSE_MOTION, MOUSE_UP, or MOUSE_DOWN.
      */
     private Type type;
 
+    /**
+     * Mouse X - relative coordinates.
+     */
+    private int x;
+
+    /**
+     * Mouse Y - relative coordinates.
+     */
+    private int y;
+
+    /**
+     * Mouse X - absolute screen coordinates.
+     */
+    private int absoluteX;
+
+    /**
+     * Mouse Y - absolute screen coordinate.
+     */
+    private int absoluteY;
+
+    /**
+     * Mouse button 1 (left button).
+     */
+    private boolean mouse1;
+
+    /**
+     * Mouse button 2 (right button).
+     */
+    private boolean mouse2;
+
+    /**
+     * Mouse button 3 (middle button).
+     */
+    private boolean mouse3;
+
+    /**
+     * Mouse wheel UP (button 4).
+     */
+    private boolean mouseWheelUp;
+
+    /**
+     * Mouse wheel DOWN (button 5).
+     */
+    private boolean mouseWheelDown;
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Public contructor.
+     *
+     * @param type the type of event, MOUSE_MOTION, MOUSE_DOWN, or MOUSE_UP
+     * @param x relative column
+     * @param y relative row
+     * @param absoluteX absolute column
+     * @param absoluteY absolute row
+     * @param mouse1 if true, left button is down
+     * @param mouse2 if true, right button is down
+     * @param mouse3 if true, middle button is down
+     * @param mouseWheelUp if true, mouse wheel (button 4) is down
+     * @param mouseWheelDown if true, mouse wheel (button 5) is down
+     */
+    public TMouseEvent(final Type type, final int x, final int y,
+        final int absoluteX, final int absoluteY,
+        final boolean mouse1, final boolean mouse2, final boolean mouse3,
+        final boolean mouseWheelUp, final boolean mouseWheelDown) {
+
+        this.type               = type;
+        this.x                  = x;
+        this.y                  = y;
+        this.absoluteX          = absoluteX;
+        this.absoluteY          = absoluteY;
+        this.mouse1             = mouse1;
+        this.mouse2             = mouse2;
+        this.mouse3             = mouse3;
+        this.mouseWheelUp       = mouseWheelUp;
+        this.mouseWheelDown     = mouseWheelDown;
+    }
+
+    // ------------------------------------------------------------------------
+    // TMouseEvent ------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Get type.
      *
     /**
      * Get type.
      *
@@ -74,11 +166,6 @@ public final class TMouseEvent extends TInputEvent {
         return type;
     }
 
         return type;
     }
 
-    /**
-     * Mouse X - relative coordinates.
-     */
-    private int x;
-
     /**
      * Get x.
      *
     /**
      * Get x.
      *
@@ -100,11 +187,6 @@ public final class TMouseEvent extends TInputEvent {
         this.x = x;
     }
 
         this.x = x;
     }
 
-    /**
-     * Mouse Y - relative coordinates.
-     */
-    private int y;
-
     /**
      * Get y.
      *
     /**
      * Get y.
      *
@@ -126,11 +208,6 @@ public final class TMouseEvent extends TInputEvent {
         this.y = y;
     }
 
         this.y = y;
     }
 
-    /**
-     * Mouse X - absolute screen coordinates.
-     */
-    private int absoluteX;
-
     /**
      * Get absoluteX.
      *
     /**
      * Get absoluteX.
      *
@@ -149,11 +226,6 @@ public final class TMouseEvent extends TInputEvent {
         this.absoluteX = absoluteX;
     }
 
         this.absoluteX = absoluteX;
     }
 
-    /**
-     * Mouse Y - absolute screen coordinate.
-     */
-    private int absoluteY;
-
     /**
      * Get absoluteY.
      *
     /**
      * Get absoluteY.
      *
@@ -172,11 +244,6 @@ public final class TMouseEvent extends TInputEvent {
         this.absoluteY = absoluteY;
     }
 
         this.absoluteY = absoluteY;
     }
 
-    /**
-     * Mouse button 1 (left button).
-     */
-    private boolean mouse1;
-
     /**
      * Get mouse1.
      *
     /**
      * Get mouse1.
      *
@@ -186,11 +253,6 @@ public final class TMouseEvent extends TInputEvent {
         return mouse1;
     }
 
         return mouse1;
     }
 
-    /**
-     * Mouse button 2 (right button).
-     */
-    private boolean mouse2;
-
     /**
      * Get mouse2.
      *
     /**
      * Get mouse2.
      *
@@ -200,11 +262,6 @@ public final class TMouseEvent extends TInputEvent {
         return mouse2;
     }
 
         return mouse2;
     }
 
-    /**
-     * Mouse button 3 (middle button).
-     */
-    private boolean mouse3;
-
     /**
      * Get mouse3.
      *
     /**
      * Get mouse3.
      *
@@ -214,11 +271,6 @@ public final class TMouseEvent extends TInputEvent {
         return mouse3;
     }
 
         return mouse3;
     }
 
-    /**
-     * Mouse wheel UP (button 4).
-     */
-    private boolean mouseWheelUp;
-
     /**
      * Get mouseWheelUp.
      *
     /**
      * Get mouseWheelUp.
      *
@@ -228,11 +280,6 @@ public final class TMouseEvent extends TInputEvent {
         return mouseWheelUp;
     }
 
         return mouseWheelUp;
     }
 
-    /**
-     * Mouse wheel DOWN (button 5).
-     */
-    private boolean mouseWheelDown;
-
     /**
      * Get mouseWheelDown.
      *
     /**
      * Get mouseWheelDown.
      *
@@ -242,37 +289,6 @@ public final class TMouseEvent extends TInputEvent {
         return mouseWheelDown;
     }
 
         return mouseWheelDown;
     }
 
-    /**
-     * Public contructor.
-     *
-     * @param type the type of event, MOUSE_MOTION, MOUSE_DOWN, or MOUSE_UP
-     * @param x relative column
-     * @param y relative row
-     * @param absoluteX absolute column
-     * @param absoluteY absolute row
-     * @param mouse1 if true, left button is down
-     * @param mouse2 if true, right button is down
-     * @param mouse3 if true, middle button is down
-     * @param mouseWheelUp if true, mouse wheel (button 4) is down
-     * @param mouseWheelDown if true, mouse wheel (button 5) is down
-     */
-    public TMouseEvent(final Type type, final int x, final int y,
-        final int absoluteX, final int absoluteY,
-        final boolean mouse1, final boolean mouse2, final boolean mouse3,
-        final boolean mouseWheelUp, final boolean mouseWheelDown) {
-
-        this.type               = type;
-        this.x                  = x;
-        this.y                  = y;
-        this.absoluteX          = absoluteX;
-        this.absoluteY          = absoluteY;
-        this.mouse1             = mouse1;
-        this.mouse2             = mouse2;
-        this.mouse3             = mouse3;
-        this.mouseWheelUp       = mouseWheelUp;
-        this.mouseWheelDown     = mouseWheelDown;
-    }
-
     /**
      * Create a duplicate instance.
      *
     /**
      * Create a duplicate instance.
      *
index 8b2b36759c414c3c564f7cbb30b4d5f89c689d50..4e15121c3fddf49e58e79d9818d63e365fa290bd 100644 (file)
@@ -33,6 +33,10 @@ package jexer.event;
  */
 public final class TResizeEvent extends TInputEvent {
 
  */
 public final class TResizeEvent extends TInputEvent {
 
+    // ------------------------------------------------------------------------
+    // Constants --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Resize events can be generated for either a total screen resize or a
      * widget/window resize.
     /**
      * Resize events can be generated for either a total screen resize or a
      * widget/window resize.
@@ -49,11 +53,46 @@ public final class TResizeEvent extends TInputEvent {
         WIDGET
     }
 
         WIDGET
     }
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * The type of resize.
      */
     private Type type;
 
     /**
      * The type of resize.
      */
     private Type type;
 
+    /**
+     * New width.
+     */
+    private int width;
+
+    /**
+     * New height.
+     */
+    private int height;
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Public contructor.
+     *
+     * @param type the Type of resize, Screen or Widget
+     * @param width the new width
+     * @param height the new height
+     */
+    public TResizeEvent(final Type type, final int width, final int height) {
+        this.type   = type;
+        this.width  = width;
+        this.height = height;
+    }
+
+    // ------------------------------------------------------------------------
+    // TResizeEvent -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Get resize type.
      *
     /**
      * Get resize type.
      *
@@ -63,11 +102,6 @@ public final class TResizeEvent extends TInputEvent {
         return type;
     }
 
         return type;
     }
 
-    /**
-     * New width.
-     */
-    private int width;
-
     /**
      * Get the new width.
      *
     /**
      * Get the new width.
      *
@@ -77,11 +111,6 @@ public final class TResizeEvent extends TInputEvent {
         return width;
     }
 
         return width;
     }
 
-    /**
-     * New height.
-     */
-    private int height;
-
     /**
      * Get the new height.
      *
     /**
      * Get the new height.
      *
@@ -91,19 +120,6 @@ public final class TResizeEvent extends TInputEvent {
         return height;
     }
 
         return height;
     }
 
-    /**
-     * Public contructor.
-     *
-     * @param type the Type of resize, Screen or Widget
-     * @param width the new width
-     * @param height the new height
-     */
-    public TResizeEvent(final Type type, final int width, final int height) {
-        this.type   = type;
-        this.width  = width;
-        this.height = height;
-    }
-
     /**
      * Make human-readable description of this TResizeEvent.
      *
     /**
      * Make human-readable description of this TResizeEvent.
      *
index 667673b642c4f340dd91b411fe38e3904d0f5687..be4cf5dd12646bd107d7841a3dc49edd15996cd9 100644 (file)
@@ -51,47 +51,9 @@ public final class TMenu extends TWindow {
      */
     private static final ResourceBundle i18n = ResourceBundle.getBundle(TMenu.class.getName());
 
      */
     private static final ResourceBundle i18n = ResourceBundle.getBundle(TMenu.class.getName());
 
-    /**
-     * If true, this is a sub-menu.  Note package private access.
-     */
-    boolean isSubMenu = false;
-
-    /**
-     * The X position of the menu's title.
-     */
-    private int titleX;
-
-    /**
-     * Set the menu title X position.
-     *
-     * @param titleX the position
-     */
-    public void setTitleX(final int titleX) {
-        this.titleX = titleX;
-    }
-
-    /**
-     * Get the menu title X position.
-     *
-     * @return the position
-     */
-    public int getTitleX() {
-        return titleX;
-    }
-
-    /**
-     * The shortcut and title.
-     */
-    private MnemonicString mnemonic;
-
-    /**
-     * Get the mnemonic string.
-     *
-     * @return the full mnemonic string
-     */
-    public MnemonicString getMnemonic() {
-        return mnemonic;
-    }
+    // ------------------------------------------------------------------------
+    // Constants --------------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     // Reserved menu item IDs
     public static final int MID_UNUSED          = -1;
 
     // Reserved menu item IDs
     public static final int MID_UNUSED          = -1;
@@ -108,15 +70,21 @@ public final class TMenu extends TWindow {
     public static final int MID_PASTE           = 12;
     public static final int MID_CLEAR           = 13;
 
     public static final int MID_PASTE           = 12;
     public static final int MID_CLEAR           = 13;
 
+    // Search menu
+    public static final int MID_FIND            = 20;
+    public static final int MID_REPLACE         = 21;
+    public static final int MID_SEARCH_AGAIN    = 22;
+    public static final int MID_GOTO_LINE       = 23;
+
     // Window menu
     // 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 static final int MID_TILE            = 30;
+    public static final int MID_CASCADE         = 31;
+    public static final int MID_CLOSE_ALL       = 32;
+    public static final int MID_WINDOW_MOVE     = 33;
+    public static final int MID_WINDOW_ZOOM     = 34;
+    public static final int MID_WINDOW_NEXT     = 35;
+    public static final int MID_WINDOW_PREVIOUS = 36;
+    public static final int MID_WINDOW_CLOSE    = 37;
 
     // Help menu
     public static final int MID_HELP_CONTENTS           = 40;
 
     // Help menu
     public static final int MID_HELP_CONTENTS           = 40;
@@ -130,6 +98,29 @@ public final class TMenu extends TWindow {
     // Other
     public static final int MID_REPAINT         = 50;
 
     // Other
     public static final int MID_REPAINT         = 50;
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * If true, this is a sub-menu.  Note package private access.
+     */
+    boolean isSubMenu = false;
+
+    /**
+     * The X position of the menu's title.
+     */
+    private int titleX;
+
+    /**
+     * The shortcut and title.
+     */
+    private MnemonicString mnemonic;
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Public constructor.
      *
     /**
      * Public constructor.
      *
@@ -158,46 +149,9 @@ public final class TMenu extends TWindow {
         setActive(false);
     }
 
         setActive(false);
     }
 
-    /**
-     * Draw a top-level menu with title and menu items.
-     */
-    @Override
-    public void draw() {
-        CellAttributes background = getTheme().getColor("tmenu");
-
-        assert (isAbsoluteActive());
-
-        // 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());
-    }
+    // ------------------------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Handle mouse button presses.
 
     /**
      * Handle mouse button presses.
@@ -345,6 +299,82 @@ public final class TMenu extends TWindow {
         }
     }
 
         }
     }
 
+    // ------------------------------------------------------------------------
+    // TWindow ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Draw a top-level menu with title and menu items.
+     */
+    @Override
+    public void draw() {
+        CellAttributes background = getTheme().getColor("tmenu");
+
+        assert (isAbsoluteActive());
+
+        // 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());
+    }
+
+    // ------------------------------------------------------------------------
+    // TMenu ------------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Set the menu title X position.
+     *
+     * @param titleX the position
+     */
+    public void setTitleX(final int titleX) {
+        this.titleX = titleX;
+    }
+
+    /**
+     * Get the menu title X position.
+     *
+     * @return the position
+     */
+    public int getTitleX() {
+        return titleX;
+    }
+
+    /**
+     * Get the mnemonic string.
+     *
+     * @return the full mnemonic string
+     */
+    public MnemonicString getMnemonic() {
+        return mnemonic;
+    }
+
     /**
      * Convenience function to add a menu item.
      *
     /**
      * Convenience function to add a menu item.
      *
@@ -400,11 +430,27 @@ public final class TMenu extends TWindow {
     private TMenuItem addItemInternal(final int id, final String label,
         final TKeypress key) {
 
     private TMenuItem addItemInternal(final int id, final String label,
         final TKeypress key) {
 
+        return addItemInternal(id, label, key, true);
+    }
+
+    /**
+     * 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
+     * @param enabled default state for enabled
+     * @return the new menu item
+     */
+    private TMenuItem addItemInternal(final int id, final String label,
+        final TKeypress key, final boolean enabled) {
+
         int newY = getChildren().size() + 1;
         assert (newY < getHeight());
 
         TMenuItem menuItem = new TMenuItem(this, id, 1, newY, label);
         menuItem.setKey(key);
         int newY = getChildren().size() + 1;
         assert (newY < getHeight());
 
         TMenuItem menuItem = new TMenuItem(this, id, 1, newY, label);
         menuItem.setKey(key);
+        menuItem.setEnabled(enabled);
         setHeight(getHeight() + 1);
         if (menuItem.getWidth() + 2 > getWidth()) {
             setWidth(menuItem.getWidth() + 2);
         setHeight(getHeight() + 1);
         if (menuItem.getWidth() + 2 > getWidth()) {
             setWidth(menuItem.getWidth() + 2);
@@ -426,6 +472,18 @@ public final class TMenu extends TWindow {
      * @return the new menu item
      */
     public TMenuItem addDefaultItem(final int id) {
      * @return the new menu item
      */
     public TMenuItem addDefaultItem(final int id) {
+        return addDefaultItem(id, true);
+    }
+
+    /**
+     * Convenience function to add one of the default menu items.
+     *
+     * @param id menu item ID.  Must be between 0 (inclusive) and 1023
+     * (inclusive).
+     * @param enabled default state for enabled
+     * @return the new menu item
+     */
+    public TMenuItem addDefaultItem(final int id, final boolean enabled) {
         assert (id >= 0);
         assert (id < 1024);
 
         assert (id >= 0);
         assert (id < 1024);
 
@@ -465,6 +523,20 @@ public final class TMenu extends TWindow {
             // key = kbDel;
             break;
 
             // key = kbDel;
             break;
 
+        case MID_FIND:
+            label = i18n.getString("menuFind");
+            break;
+        case MID_REPLACE:
+            label = i18n.getString("menuReplace");
+            break;
+        case MID_SEARCH_AGAIN:
+            label = i18n.getString("menuSearchAgain");
+            break;
+        case MID_GOTO_LINE:
+            label = i18n.getString("menuGotoLine");
+            key = kbCtrlL;
+            break;
+
         case MID_TILE:
             label = i18n.getString("menuWindowTile");
             break;
         case MID_TILE:
             label = i18n.getString("menuWindowTile");
             break;
@@ -528,7 +600,7 @@ public final class TMenu extends TWindow {
             throw new IllegalArgumentException("Invalid menu ID: " + id);
         }
 
             throw new IllegalArgumentException("Invalid menu ID: " + id);
         }
 
-        return addItemInternal(id, label, key);
+        return addItemInternal(id, label, key, enabled);
     }
 
     /**
     }
 
     /**
index 000df917d26052a345b5d7ad9e4973114e350a71..d42157cb04a498d2d6c408a9657cf32342379bb4 100644 (file)
@@ -5,6 +5,10 @@ menuCut=Cu&t
 menuCopy=&Copy
 menuPaste=&Paste
 menuClear=C&lear
 menuCopy=&Copy
 menuPaste=&Paste
 menuClear=C&lear
+menuFind=&Find...
+menuReplace=&Replace...
+menuSearchAgain=&Search again
+menuGotoLine=&Go to line number...
 menuWindowTile=&Tile
 menuWindowCascade=C&ascade
 menuWindowCloseAll=Cl&ose All
 menuWindowTile=&Tile
 menuWindowCascade=C&ascade
 menuWindowCloseAll=Cl&ose All
index 9ffc1ea075a6448c561e21ef5008f87961f7f4ff..63a5355e348f0e553215a67c88d45c9979a856d5 100644 (file)
@@ -43,6 +43,10 @@ import static jexer.TKeypress.*;
  */
 public class TMenuItem extends TWidget {
 
  */
 public class TMenuItem extends TWidget {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Label for this menu item.
      */
     /**
      * Label for this menu item.
      */
@@ -54,29 +58,11 @@ public class TMenuItem extends TWidget {
      */
     private int id = TMenu.MID_UNUSED;
 
      */
     private int id = TMenu.MID_UNUSED;
 
-    /**
-     * Get the menu item ID.
-     *
-     * @return the id
-     */
-    public final int getId() {
-        return id;
-    }
-
     /**
      * When true, this item can be checked or unchecked.
      */
     private boolean checkable = false;
 
     /**
      * When true, this item can be checked or unchecked.
      */
     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.
      */
     /**
      * When true, this item is checked.
      */
@@ -93,40 +79,9 @@ public class TMenuItem extends TWidget {
      */
     private MnemonicString mnemonic;
 
      */
     private MnemonicString mnemonic;
 
-    /**
-     * Get the mnemonic string for this menu item.
-     *
-     * @return mnemonic string
-     */
-    public final MnemonicString getMnemonic() {
-        return mnemonic;
-    }
-
-    /**
-     * Get a global accelerator key for this menu item.
-     *
-     * @return global keyboard accelerator, or null if no key is associated
-     * with this item
-     */
-    public final TKeypress getKey() {
-        return key;
-    }
-
-    /**
-     * Set a global accelerator key for this menu item.
-     *
-     * @param key global keyboard accelerator
-     */
-    public final void setKey(final TKeypress key) {
-        this.key = key;
-
-        if (key != null) {
-            int newWidth = (label.length() + 4 + key.toString().length() + 2);
-            if (newWidth > getWidth()) {
-                setWidth(newWidth);
-            }
-        }
-    }
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Package private constructor.
 
     /**
      * Package private constructor.
@@ -190,6 +145,10 @@ public class TMenuItem extends TWidget {
 
     }
 
 
     }
 
+    // ------------------------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Returns true if the mouse is currently on the menu item.
      *
     /**
      * Returns true if the mouse is currently on the menu item.
      *
@@ -206,6 +165,39 @@ public class TMenuItem extends TWidget {
         return false;
     }
 
         return false;
     }
 
+    /**
+     * Handle mouse button releases.
+     *
+     * @param mouse mouse button release event
+     */
+    @Override
+    public void onMouseUp(final TMouseEvent mouse) {
+        if ((mouseOnMenuItem(mouse)) && (mouse.isMouse1())) {
+            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);
+    }
+
+    // ------------------------------------------------------------------------
+    // TWidget ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Draw a menu item with label.
      */
     /**
      * Draw a menu item with label.
      */
@@ -249,44 +241,73 @@ public class TMenuItem extends TWidget {
 
     }
 
 
     }
 
+    // ------------------------------------------------------------------------
+    // TMenuItem --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
     /**
-     * Dispatch event(s) due to selection or click.
+     * Get the menu item ID.
+     *
+     * @return the id
      */
      */
-    public void dispatch() {
-        assert (isEnabled());
+    public final int getId() {
+        return id;
+    }
 
 
-        getApplication().postMenuEvent(new TMenuEvent(id));
-        if (checkable) {
-            checked = !checked;
-        }
+    /**
+     * Set checkable flag.
+     *
+     * @param checkable if true, this menu item can be checked/unchecked
+     */
+    public final void setCheckable(final boolean checkable) {
+        this.checkable = checkable;
     }
 
     /**
     }
 
     /**
-     * Handle mouse button releases.
+     * Get the mnemonic string for this menu item.
      *
      *
-     * @param mouse mouse button release event
+     * @return mnemonic string
      */
      */
-    @Override
-    public void onMouseUp(final TMouseEvent mouse) {
-        if ((mouseOnMenuItem(mouse)) && (mouse.isMouse1())) {
-            dispatch();
-            return;
-        }
+    public final MnemonicString getMnemonic() {
+        return mnemonic;
     }
 
     /**
     }
 
     /**
-     * Handle keystrokes.
+     * Get a global accelerator key for this menu item.
      *
      *
-     * @param keypress keystroke event
+     * @return global keyboard accelerator, or null if no key is associated
+     * with this item
      */
      */
-    @Override
-    public void onKeypress(final TKeypressEvent keypress) {
-        if (keypress.equals(kbEnter)) {
-            dispatch();
-            return;
+    public final TKeypress getKey() {
+        return key;
+    }
+
+    /**
+     * Set a global accelerator key for this menu item.
+     *
+     * @param key global keyboard accelerator
+     */
+    public final void setKey(final TKeypress key) {
+        this.key = key;
+
+        if (key != null) {
+            int newWidth = (label.length() + 4 + key.toString().length() + 2);
+            if (newWidth > getWidth()) {
+                setWidth(newWidth);
+            }
         }
         }
+    }
 
 
-        // Pass to parent for the things we don't care about.
-        super.onKeypress(keypress);
+    /**
+     * Dispatch event(s) due to selection or click.
+     */
+    public void dispatch() {
+        assert (isEnabled());
+
+        getApplication().postMenuEvent(new TMenuEvent(id));
+        if (checkable) {
+            checked = !checked;
+        }
     }
     }
+
 }
 }
index 9a9fcfc81a0e39143d64481aeba57b3c0d9cc009..01164a537a63aed47d01e5d8356f101ce4107bee 100644 (file)
@@ -36,6 +36,10 @@ import jexer.bits.GraphicsChars;
  */
 public final class TMenuSeparator extends TMenuItem {
 
  */
 public final class TMenuSeparator extends TMenuItem {
 
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Package private constructor.
      *
     /**
      * Package private constructor.
      *
@@ -50,6 +54,10 @@ public final class TMenuSeparator extends TMenuItem {
         setWidth(parent.getWidth() - 2);
     }
 
         setWidth(parent.getWidth() - 2);
     }
 
+    // ------------------------------------------------------------------------
+    // TMenuItem --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Draw a menu separator.
      */
     /**
      * Draw a menu separator.
      */
index 7b5f80c911e6e402dbf69dd9dc92c0e1a23b5b35..88094daa155b0ba46e9f758084510a352c8c0415 100644 (file)
@@ -40,11 +40,19 @@ import static jexer.TKeypress.*;
  */
 public final class TSubMenu extends TMenuItem {
 
  */
 public final class TSubMenu extends TMenuItem {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * The menu window.  Note package private access.
      */
     TMenu menu;
 
     /**
      * The menu window.  Note package private access.
      */
     TMenu menu;
 
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Package private constructor.
      *
     /**
      * Package private constructor.
      *
@@ -67,28 +75,9 @@ public final class TSubMenu extends TMenuItem {
         this.menu.isSubMenu = true;
     }
 
         this.menu.isSubMenu = true;
     }
 
-    /**
-     * Draw the menu title.
-     */
-    @Override
-    public void draw() {
-        super.draw();
-
-        CellAttributes menuColor;
-        if (isAbsoluteActive()) {
-            menuColor = getTheme().getColor("tmenu.highlighted");
-        } else {
-            if (isEnabled()) {
-                menuColor = getTheme().getColor("tmenu");
-            } else {
-                menuColor = getTheme().getColor("tmenu.disabled");
-            }
-        }
-
-        // Add the arrow
-        getScreen().putCharXY(getWidth() - 2, 0, GraphicsChars.CP437[0x10],
-            menuColor);
-    }
+    // ------------------------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Handle keystrokes.
 
     /**
      * Handle keystrokes.
@@ -151,6 +140,33 @@ public final class TSubMenu extends TMenuItem {
         }
     }
 
         }
     }
 
+    // ------------------------------------------------------------------------
+    // TMenuItem --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Draw the menu title.
+     */
+    @Override
+    public void draw() {
+        super.draw();
+
+        CellAttributes menuColor;
+        if (isAbsoluteActive()) {
+            menuColor = getTheme().getColor("tmenu.highlighted");
+        } else {
+            if (isEnabled()) {
+                menuColor = getTheme().getColor("tmenu");
+            } else {
+                menuColor = getTheme().getColor("tmenu.disabled");
+            }
+        }
+
+        // Add the arrow
+        getScreen().putCharXY(getWidth() - 2, 0, GraphicsChars.CP437[0x10],
+            menuColor);
+    }
+
     /**
      * Override dispatch() to do nothing.
      */
     /**
      * Override dispatch() to do nothing.
      */
@@ -179,6 +195,10 @@ public final class TSubMenu extends TMenuItem {
         return this;
     }
 
         return this;
     }
 
+    // ------------------------------------------------------------------------
+    // TSubMenu ---------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Convenience function to add a custom menu item.
      *
     /**
      * Convenience function to add a custom menu item.
      *
@@ -233,5 +253,4 @@ public final class TSubMenu extends TMenuItem {
         return menu.addSubMenu(title);
     }
 
         return menu.addSubMenu(title);
     }
 
-
 }
 }
index ea30171c30d38a47a4acbf13e035739db6a48424..9ae77476de7d5d39305ce356149d58494fd6e2d3 100644 (file)
@@ -43,6 +43,14 @@ import static jexer.net.TelnetSocket.*;
 public final class TelnetInputStream extends InputStream
         implements SessionInfo {
 
 public final class TelnetInputStream extends InputStream
         implements SessionInfo {
 
+    // ------------------------------------------------------------------------
+    // Constants --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * The root TelnetSocket that has my telnet protocol state.
      */
     /**
      * The root TelnetSocket that has my telnet protocol state.
      */
@@ -76,6 +84,61 @@ public final class TelnetInputStream extends InputStream
      */
     private int readBufferStart;
 
      */
     private int readBufferStart;
 
+    /**
+     * User name.
+     */
+    private String username = "";
+
+    /**
+     * Language.
+     */
+    private String language = "en_US";
+
+    /**
+     * Text window width.
+     */
+    private int windowWidth = 80;
+
+    /**
+     * Text window height.
+     */
+    private int windowHeight = 24;
+
+    /**
+     * When true, the last read byte from the remote side was IAC.
+     */
+    private boolean iac = false;
+
+    /**
+     * When true, we are in the middle of a DO/DONT/WILL/WONT negotiation.
+     */
+    private boolean dowill = false;
+
+    /**
+     * The telnet option being negotiated.
+     */
+    private int dowillType = 0;
+
+    /**
+     * When true, we are waiting to see the end of the sub-negotiation
+     * sequence.
+     */
+    private boolean subnegEnd = false;
+
+    /**
+     * When true, the last byte read from the remote side was CR.
+     */
+    private boolean readCR = false;
+
+    /**
+     * The subnegotiation buffer.
+     */
+    private ArrayList<Byte> subnegBuffer;
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Package private constructor.
      *
     /**
      * Package private constructor.
      *
@@ -97,27 +160,9 @@ public final class TelnetInputStream extends InputStream
         subnegBuffer    = new ArrayList<Byte>();
     }
 
         subnegBuffer    = new ArrayList<Byte>();
     }
 
-    // SessionInfo interface --------------------------------------------------
-
-    /**
-     * User name.
-     */
-    private String username = "";
-
-    /**
-     * Language.
-     */
-    private String language = "en_US";
-
-    /**
-     * Text window width.
-     */
-    private int windowWidth = 80;
-
-    /**
-     * Text window height.
-     */
-    private int windowHeight = 24;
+    // ------------------------------------------------------------------------
+    // SessionInfo ------------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Username getter.
 
     /**
      * Username getter.
@@ -180,7 +225,9 @@ public final class TelnetInputStream extends InputStream
         // NOP
     }
 
         // NOP
     }
 
-    // InputStream interface --------------------------------------------------
+    // ------------------------------------------------------------------------
+    // InputStream ------------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Returns an estimate of the number of bytes that can be read (or
 
     /**
      * Returns an estimate of the number of bytes that can be read (or
@@ -371,39 +418,9 @@ public final class TelnetInputStream extends InputStream
         return n;
     }
 
         return n;
     }
 
-    // Telnet protocol --------------------------------------------------------
-
-
-    /**
-     * When true, the last read byte from the remote side was IAC.
-     */
-    private boolean iac = false;
-
-    /**
-     * When true, we are in the middle of a DO/DONT/WILL/WONT negotiation.
-     */
-    private boolean dowill = false;
-
-    /**
-     * The telnet option being negotiated.
-     */
-    private int dowillType = 0;
-
-    /**
-     * When true, we are waiting to see the end of the sub-negotiation
-     * sequence.
-     */
-    private boolean subnegEnd = false;
-
-    /**
-     * When true, the last byte read from the remote side was CR.
-     */
-    private boolean readCR = false;
-
-    /**
-     * The subnegotiation buffer.
-     */
-    private ArrayList<Byte> subnegBuffer;
+    // ------------------------------------------------------------------------
+    // TelnetInputStream ------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * For debugging, return a descriptive string for this telnet option.
 
     /**
      * For debugging, return a descriptive string for this telnet option.
@@ -1348,5 +1365,4 @@ public final class TelnetInputStream extends InputStream
         return bufN;
     }
 
         return bufN;
     }
 
-
 }
 }
index 3520a591f63e4dbc3e19988ac038eddd59eed425..338de2c24f4115198445d5c709a246a90aa29978 100644 (file)
@@ -38,6 +38,10 @@ import static jexer.net.TelnetSocket.*;
  */
 public final class TelnetOutputStream extends OutputStream {
 
  */
 public final class TelnetOutputStream extends OutputStream {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * The root TelnetSocket that has my telnet protocol state.
      */
     /**
      * The root TelnetSocket that has my telnet protocol state.
      */
@@ -48,6 +52,15 @@ public final class TelnetOutputStream extends OutputStream {
      */
     private OutputStream output;
 
      */
     private OutputStream output;
 
+    /**
+     * When true, the last byte the caller passed to write() was a CR.
+     */
+    private boolean writeCR = false;
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Package private constructor.
      *
     /**
      * Package private constructor.
      *
@@ -59,7 +72,9 @@ public final class TelnetOutputStream extends OutputStream {
         this.output = output;
     }
 
         this.output = output;
     }
 
-    // OutputStream interface -------------------------------------------------
+    // ------------------------------------------------------------------------
+    // OutputStrem ------------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Closes this output stream and releases any system resources associated
 
     /**
      * Closes this output stream and releases any system resources associated
@@ -135,6 +150,10 @@ public final class TelnetOutputStream extends OutputStream {
         writeImpl(bytes, 0, 1);
     }
 
         writeImpl(bytes, 0, 1);
     }
 
+    // ------------------------------------------------------------------------
+    // TelnetOutputStrem ------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Writes b.length bytes from the specified byte array to this output
      * stream.  Note package private access.
     /**
      * Writes b.length bytes from the specified byte array to this output
      * stream.  Note package private access.
@@ -146,13 +165,6 @@ public final class TelnetOutputStream extends OutputStream {
         output.write(b, 0, b.length);
     }
 
         output.write(b, 0, b.length);
     }
 
-    // Telnet protocol --------------------------------------------------------
-
-    /**
-     * When true, the last byte the caller passed to write() was a CR.
-     */
-    private boolean writeCR = false;
-
     /**
      * Writes len bytes from the specified byte array starting at offset off
      * to this output stream.
     /**
      * Writes len bytes from the specified byte array starting at offset off
      * to this output stream.
index 923ba2998a59b6f6f39c1a5972e6a103c2ca5a09..7523192602dce2a3cecb7f117ce45a06f5599cd2 100644 (file)
@@ -39,7 +39,14 @@ import java.net.SocketException;
  */
 public final class TelnetServerSocket extends ServerSocket {
 
  */
 public final class TelnetServerSocket extends ServerSocket {
 
-    // ServerSocket interface -------------------------------------------------
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Creates an unbound server socket.
 
     /**
      * Creates an unbound server socket.
@@ -94,6 +101,10 @@ public final class TelnetServerSocket extends ServerSocket {
         super(port, backlog, bindAddr);
     }
 
         super(port, backlog, bindAddr);
     }
 
+    // ------------------------------------------------------------------------
+    // ServerSocket -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Listens for a connection to be made to this socket and accepts it. The
      * method blocks until a connection is made.
     /**
      * Listens for a connection to be made to this socket and accepts it. The
      * method blocks until a connection is made.
index 74ad72a3799e5dad860f94d15a29d0d5cee73b16..31663884c1f48fceb907f22438959cd2f7a23c1f 100644 (file)
@@ -40,15 +40,9 @@ import java.net.Socket;
  */
 public final class TelnetSocket extends Socket {
 
  */
 public final class TelnetSocket extends Socket {
 
-    /**
-     * The telnet-aware socket InputStream.
-     */
-    private TelnetInputStream input;
-
-    /**
-     * The telnet-aware socket OutputStream.
-     */
-    private TelnetOutputStream output;
+    // ------------------------------------------------------------------------
+    // Constants --------------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     // Telnet protocol special characters.  Note package private access.
     static final int TELNET_SE         = 240;
 
     // Telnet protocol special characters.  Note package private access.
     static final int TELNET_SE         = 240;
@@ -71,6 +65,21 @@ public final class TelnetSocket extends Socket {
     static final int C_LF              = 0x0A;
     static final int C_CR              = 0x0D;
 
     static final int C_LF              = 0x0A;
     static final int C_CR              = 0x0D;
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * The telnet-aware socket InputStream.
+     */
+    private TelnetInputStream input;
+
+    /**
+     * The telnet-aware socket OutputStream.
+     */
+    private TelnetOutputStream output;
+
+
     /**
      * If true, this is a server socket (i.e. created by accept()).
      */
     /**
      * If true, this is a server socket (i.e. created by accept()).
      */
@@ -126,14 +135,9 @@ public final class TelnetSocket extends Socket {
      */
     String terminalSpeed = "";
 
      */
     String terminalSpeed = "";
 
-    /**
-     * See if telnet server/client is in ASCII mode.
-     *
-     * @return if true, this connection is in ASCII mode
-     */
-    public boolean isAscii() {
-        return (!binaryMode);
-    }
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Creates a Socket that knows the telnet protocol.  Note package private
 
     /**
      * Creates a Socket that knows the telnet protocol.  Note package private
@@ -145,7 +149,9 @@ public final class TelnetSocket extends Socket {
         super();
     }
 
         super();
     }
 
-    // Socket interface -------------------------------------------------------
+    // ------------------------------------------------------------------------
+    // Socket -----------------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Returns an input stream for this socket.
 
     /**
      * Returns an input stream for this socket.
@@ -181,4 +187,17 @@ public final class TelnetSocket extends Socket {
         return output;
     }
 
         return output;
     }
 
+    // ------------------------------------------------------------------------
+    // TelnetSocket -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * See if telnet server/client is in ASCII mode.
+     *
+     * @return if true, this connection is in ASCII mode
+     */
+    public boolean isAscii() {
+        return (!binaryMode);
+    }
+
 }
 }
index 4cc8757544cadf639c23210a1c016b4666a7464f..5110db0edbdb925b3fc93368d919dac401702f68 100644 (file)
@@ -34,11 +34,9 @@ package jexer.tterminal;
  */
 public final class DECCharacterSets {
 
  */
 public final class DECCharacterSets {
 
-    /**
-     * Private constructor prevents accidental creation of this class.
-     */
-    private DECCharacterSets() {
-    }
+    // ------------------------------------------------------------------------
+    // Constants --------------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * US - Normal "international" (ASCII).
 
     /**
      * US - Normal "international" (ASCII).
@@ -370,4 +368,14 @@ public final class DECCharacterSets {
         0x2084, 0x2085, 0x2086, 0x2087, 0x2088, 0x2089, 0x00B6, 0x0020
     };
 
         0x2084, 0x2085, 0x2086, 0x2087, 0x2088, 0x2089, 0x00B6, 0x0020
     };
 
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Private constructor prevents accidental creation of this class.
+     */
+    private DECCharacterSets() {
+    }
+
 }
 }
index 15739486de2b6bf386d4641638d69a3e1c94c744..8eff2de1b641fbb61ee4aea910e52459ae8b29d9 100644 (file)
@@ -35,16 +35,68 @@ import jexer.bits.CellAttributes;
  * This represents a single line of the display buffer.
  */
 public final class DisplayLine {
  * This represents a single line of the display buffer.
  */
 public final class DisplayLine {
+
+    // ------------------------------------------------------------------------
+    // Constants --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Maximum line length.
      */
     private static final int MAX_LINE_LENGTH = 256;
 
     /**
      * Maximum line length.
      */
     private static final int MAX_LINE_LENGTH = 256;
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * The characters/attributes of the line.
      */
     private Cell [] chars;
 
     /**
      * The characters/attributes of the line.
      */
     private Cell [] chars;
 
+    /**
+     * Double-width line flag.
+     */
+    private boolean doubleWidth = false;
+
+    /**
+     * Double height line flag.  Valid values are:
+     *
+     * <p><pre>
+     *   0 = single height
+     *   1 = top half double height
+     *   2 = bottom half double height
+     * </pre>
+     */
+    private int doubleHeight = 0;
+
+    /**
+     * DECSCNM - reverse video.  We copy the flag to the line so that
+     * reverse-mode scrollback lines still show inverted colors correctly.
+     */
+    private boolean reverseColor = false;
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Public constructor sets everything to drawing attributes.
+     *
+     * @param attr current drawing attributes
+     */
+    public DisplayLine(final CellAttributes attr) {
+        chars = new Cell[MAX_LINE_LENGTH];
+        for (int i = 0; i < chars.length; i++) {
+            chars[i] = new Cell();
+            chars[i].setTo(attr);
+        }
+    }
+
+    // ------------------------------------------------------------------------
+    // DisplayLine ------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Get the Cell at a specific column.
      *
     /**
      * Get the Cell at a specific column.
      *
@@ -64,11 +116,6 @@ public final class DisplayLine {
         return chars.length;
     }
 
         return chars.length;
     }
 
-    /**
-     * Double-width line flag.
-     */
-    private boolean doubleWidth = false;
-
     /**
      * Get double width flag.
      *
     /**
      * Get double width flag.
      *
@@ -87,17 +134,6 @@ public final class DisplayLine {
         this.doubleWidth = doubleWidth;
     }
 
         this.doubleWidth = doubleWidth;
     }
 
-    /**
-     * Double height line flag.  Valid values are:
-     *
-     * <p><pre>
-     *   0 = single height
-     *   1 = top half double height
-     *   2 = bottom half double height
-     * </pre>
-     */
-    private int doubleHeight = 0;
-
     /**
      * Get double height flag.
      *
     /**
      * Get double height flag.
      *
@@ -116,12 +152,6 @@ public final class DisplayLine {
         this.doubleHeight = doubleHeight;
     }
 
         this.doubleHeight = doubleHeight;
     }
 
-    /**
-     * DECSCNM - reverse video.  We copy the flag to the line so that
-     * reverse-mode scrollback lines still show inverted colors correctly.
-     */
-    private boolean reverseColor = false;
-
     /**
      * Get reverse video flag.
      *
     /**
      * Get reverse video flag.
      *
@@ -140,19 +170,6 @@ public final class DisplayLine {
         this.reverseColor = reverseColor;
     }
 
         this.reverseColor = reverseColor;
     }
 
-    /**
-     * Public constructor sets everything to drawing attributes.
-     *
-     * @param attr current drawing attributes
-     */
-    public DisplayLine(final CellAttributes attr) {
-        chars = new Cell[MAX_LINE_LENGTH];
-        for (int i = 0; i < chars.length; i++) {
-            chars[i] = new Cell();
-            chars[i].setTo(attr);
-        }
-    }
-
     /**
      * Insert a character at the specified position.
      *
     /**
      * Insert a character at the specified position.
      *
index caa295ad47776ce242557359407ab18b2d7dacf5..7a37a95fdd765cae7e7eb389cc5446fd603ff5d1 100644 (file)
@@ -90,6 +90,10 @@ import static jexer.TKeypress.*;
  */
 public class ECMA48 implements Runnable {
 
  */
 public class ECMA48 implements Runnable {
 
+    // ------------------------------------------------------------------------
+    // Constants --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * The emulator can emulate several kinds of terminals.
      */
     /**
      * The emulator can emulate several kinds of terminals.
      */
@@ -116,194 +120,115 @@ public class ECMA48 implements Runnable {
     }
 
     /**
     }
 
     /**
-     * Return the proper primary Device Attributes string.
-     *
-     * @return string to send to remote side that is appropriate for the
-     * this.type
+     * Parser character scan states.
      */
      */
-    private String deviceTypeResponse() {
-        switch (type) {
-        case VT100:
-            // "I am a VT100 with advanced video option" (often VT102)
-            return "\033[?1;2c";
-
-        case VT102:
-            // "I am a VT102"
-            return "\033[?6c";
-
-        case VT220:
-        case XTERM:
-            // "I am a VT220" - 7 bit version
-            if (!s8c1t) {
-                return "\033[?62;1;6c";
-            }
-            // "I am a VT220" - 8 bit version
-            return "\u009b?62;1;6c";
-        default:
-            throw new IllegalArgumentException("Invalid device type: " + type);
-        }
+    private enum ScanState {
+        GROUND,
+        ESCAPE,
+        ESCAPE_INTERMEDIATE,
+        CSI_ENTRY,
+        CSI_PARAM,
+        CSI_INTERMEDIATE,
+        CSI_IGNORE,
+        DCS_ENTRY,
+        DCS_INTERMEDIATE,
+        DCS_PARAM,
+        DCS_PASSTHROUGH,
+        DCS_IGNORE,
+        SOSPMAPC_STRING,
+        OSC_STRING,
+        VT52_DIRECT_CURSOR_ADDRESS
     }
 
     /**
     }
 
     /**
-     * Return the proper TERM environment variable for this device type.
-     *
-     * @param deviceType DeviceType.VT100, DeviceType, XTERM, etc.
-     * @return "vt100", "xterm", etc.
+     * The selected number pad mode (DECKPAM, DECKPNM).  We record this, but
+     * can't really use it in keypress() because we do not see number pad
+     * events from TKeypress.
      */
      */
-    public static String deviceTypeTerm(final DeviceType deviceType) {
-        switch (deviceType) {
-        case VT100:
-            return "vt100";
-
-        case VT102:
-            return "vt102";
-
-        case VT220:
-            return "vt220";
-
-        case XTERM:
-            return "xterm";
-
-        default:
-            throw new IllegalArgumentException("Invalid device type: "
-                + deviceType);
-        }
+    private enum KeypadMode {
+        Application,
+        Numeric
     }
 
     /**
     }
 
     /**
-     * Return the proper LANG for this device type.  Only XTERM devices know
-     * about UTF-8, the others are defined by their standard to be either
-     * 7-bit or 8-bit characters only.
-     *
-     * @param deviceType DeviceType.VT100, DeviceType, XTERM, etc.
-     * @param baseLang a base language without UTF-8 flag such as "C" or
-     * "en_US"
-     * @return "en_US", "en_US.UTF-8", etc.
+     * Arrow keys can emit three different sequences (DECCKM or VT52
+     * submode).
      */
      */
-    public static String deviceTypeLang(final DeviceType deviceType,
-        final String baseLang) {
-
-        switch (deviceType) {
-
-        case VT100:
-        case VT102:
-        case VT220:
-            return baseLang;
-
-        case XTERM:
-            return baseLang + ".UTF-8";
-
-        default:
-            throw new IllegalArgumentException("Invalid device type: "
-                + deviceType);
-        }
+    private enum ArrowKeyMode {
+        VT52,
+        ANSI,
+        VT100
     }
 
     /**
     }
 
     /**
-     * Write a string directly to the remote side.
-     *
-     * @param str string to send
+     * Available character sets for GL, GR, G0, G1, G2, G3.
      */
      */
-    public void writeRemote(final String str) {
-        if (stopReaderThread) {
-            // Reader hit EOF, bail out now.
-            close();
-            return;
-        }
-
-        // System.err.printf("writeRemote() '%s'\n", str);
+    private enum CharacterSet {
+        US,
+        UK,
+        DRAWING,
+        ROM,
+        ROM_SPECIAL,
+        VT52_GRAPHICS,
+        DEC_SUPPLEMENTAL,
+        NRC_DUTCH,
+        NRC_FINNISH,
+        NRC_FRENCH,
+        NRC_FRENCH_CA,
+        NRC_GERMAN,
+        NRC_ITALIAN,
+        NRC_NORWEGIAN,
+        NRC_SPANISH,
+        NRC_SWEDISH,
+        NRC_SWISS
+    }
 
 
-        switch (type) {
-        case VT100:
-        case VT102:
-        case VT220:
-            if (outputStream == null) {
-                return;
-            }
-            try {
-                outputStream.flush();
-                for (int i = 0; i < str.length(); i++) {
-                    outputStream.write(str.charAt(i));
-                }
-                outputStream.flush();
-            } catch (IOException e) {
-                // Assume EOF
-                close();
-            }
-            break;
-        case XTERM:
-            if (output == null) {
-                return;
-            }
-            try {
-                output.flush();
-                output.write(str);
-                output.flush();
-            } catch (IOException e) {
-                // Assume EOF
-                close();
-            }
-            break;
-        default:
-            throw new IllegalArgumentException("Invalid device type: " + type);
-        }
+    /**
+     * Single-shift states used by the C1 control characters SS2 (0x8E) and
+     * SS3 (0x8F).
+     */
+    private enum Singleshift {
+        NONE,
+        SS2,
+        SS3
     }
 
     /**
     }
 
     /**
-     * Close the input and output streams and stop the reader thread.  Note
-     * that it is safe to call this multiple times.
+     * VT220+ lockshift states.
      */
      */
-    public final void close() {
+    private enum LockshiftMode {
+        NONE,
+        G1_GR,
+        G2_GR,
+        G2_GL,
+        G3_GR,
+        G3_GL
+    }
 
 
-        // Tell the reader thread to stop looking at input.  It will close
-        // the input streams.
-        if (stopReaderThread == false) {
-            stopReaderThread = true;
-            try {
-                readerThread.join(1000);
-            } catch (InterruptedException e) {
-                e.printStackTrace();
-            }
-        }
+    /**
+     * XTERM mouse reporting protocols.
+     */
+    private enum MouseProtocol {
+        OFF,
+        X10,
+        NORMAL,
+        BUTTONEVENT,
+        ANYEVENT
+    }
 
 
-        // Now close the output stream.
-        switch (type) {
-        case VT100:
-        case VT102:
-        case VT220:
-            if (outputStream != null) {
-                try {
-                    outputStream.close();
-                } catch (IOException e) {
-                    // SQUASH
-                }
-                outputStream = null;
-            }
-            break;
-        case XTERM:
-            if (outputStream != null) {
-                try {
-                    outputStream.close();
-                } catch (IOException e) {
-                    // SQUASH
-                }
-                outputStream = null;
-            }
-            if (output != null) {
-                try {
-                    output.close();
-                } catch (IOException e) {
-                    // SQUASH
-                }
-                output = null;
-            }
-            break;
-        default:
-            throw new IllegalArgumentException("Invalid device type: " +
-                type);
-        }
+    /**
+     * XTERM mouse reporting encodings.
+     */
+    private enum MouseEncoding {
+        X10,
+        UTF8,
+        SGR
     }
 
     }
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * The enclosing listening object.
      */
     /**
      * The enclosing listening object.
      */
@@ -320,58 +245,20 @@ public class ECMA48 implements Runnable {
     private Thread readerThread = null;
 
     /**
     private Thread readerThread = null;
 
     /**
-     * See if the reader thread is still running.
-     *
-     * @return if true, we are still connected to / reading from the remote
-     * side
-     */
-    public final boolean isReading() {
-        return (!stopReaderThread);
-    }
-
-    /**
-     * The type of emulator to be.
+     * The type of emulator to be.
      */
     private DeviceType type = DeviceType.VT102;
 
      */
     private DeviceType type = DeviceType.VT102;
 
-    /**
-     * Obtain a new blank display line for an external user
-     * (e.g. TTerminalWindow).
-     *
-     * @return new blank line
-     */
-    public final DisplayLine getBlankDisplayLine() {
-        return new DisplayLine(currentState.attr);
-    }
-
     /**
      * The scrollback buffer characters + attributes.
      */
     private volatile List<DisplayLine> scrollback;
 
     /**
      * The scrollback buffer characters + attributes.
      */
     private volatile List<DisplayLine> scrollback;
 
-    /**
-     * Get the scrollback buffer.
-     *
-     * @return the scrollback buffer
-     */
-    public final List<DisplayLine> getScrollbackBuffer() {
-        return scrollback;
-    }
-
     /**
      * The raw display buffer characters + attributes.
      */
     private volatile List<DisplayLine> display;
 
     /**
      * The raw display buffer characters + attributes.
      */
     private volatile List<DisplayLine> display;
 
-    /**
-     * Get the display buffer.
-     *
-     * @return the display buffer
-     */
-    public final List<DisplayLine> getDisplayBuffer() {
-        return display;
-    }
-
     /**
      * The terminal's input.  For type == XTERM, this is an InputStreamReader
      * with UTF-8 encoding.
     /**
      * The terminal's input.  For type == XTERM, this is an InputStreamReader
      * with UTF-8 encoding.
@@ -394,122 +281,16 @@ public class ECMA48 implements Runnable {
      */
     private OutputStream outputStream;
 
      */
     private OutputStream outputStream;
 
-    /**
-     * Parser character scan states.
-     */
-    enum ScanState {
-        GROUND,
-        ESCAPE,
-        ESCAPE_INTERMEDIATE,
-        CSI_ENTRY,
-        CSI_PARAM,
-        CSI_INTERMEDIATE,
-        CSI_IGNORE,
-        DCS_ENTRY,
-        DCS_INTERMEDIATE,
-        DCS_PARAM,
-        DCS_PASSTHROUGH,
-        DCS_IGNORE,
-        SOSPMAPC_STRING,
-        OSC_STRING,
-        VT52_DIRECT_CURSOR_ADDRESS
-    }
-
     /**
      * Current scanning state.
      */
     private ScanState scanState;
 
     /**
      * Current scanning state.
      */
     private ScanState scanState;
 
-    /**
-     * The selected number pad mode (DECKPAM, DECKPNM).  We record this, but
-     * can't really use it in keypress() because we do not see number pad
-     * events from TKeypress.
-     */
-    private enum KeypadMode {
-        Application,
-        Numeric
-    }
-
-    /**
-     * Arrow keys can emit three different sequences (DECCKM or VT52
-     * submode).
-     */
-    private enum ArrowKeyMode {
-        VT52,
-        ANSI,
-        VT100
-    }
-
-    /**
-     * Available character sets for GL, GR, G0, G1, G2, G3.
-     */
-    private enum CharacterSet {
-        US,
-        UK,
-        DRAWING,
-        ROM,
-        ROM_SPECIAL,
-        VT52_GRAPHICS,
-        DEC_SUPPLEMENTAL,
-        NRC_DUTCH,
-        NRC_FINNISH,
-        NRC_FRENCH,
-        NRC_FRENCH_CA,
-        NRC_GERMAN,
-        NRC_ITALIAN,
-        NRC_NORWEGIAN,
-        NRC_SPANISH,
-        NRC_SWEDISH,
-        NRC_SWISS
-    }
-
-    /**
-     * Single-shift states used by the C1 control characters SS2 (0x8E) and
-     * SS3 (0x8F).
-     */
-    private enum Singleshift {
-        NONE,
-        SS2,
-        SS3
-    }
-
-    /**
-     * VT220+ lockshift states.
-     */
-    private enum LockshiftMode {
-        NONE,
-        G1_GR,
-        G2_GR,
-        G2_GL,
-        G3_GR,
-        G3_GL
-    }
-
-    /**
-     * XTERM mouse reporting protocols.
-     */
-    private enum MouseProtocol {
-        OFF,
-        X10,
-        NORMAL,
-        BUTTONEVENT,
-        ANYEVENT
-    }
-
     /**
      * Which mouse protocol is active.
      */
     private MouseProtocol mouseProtocol = MouseProtocol.OFF;
 
     /**
      * Which mouse protocol is active.
      */
     private MouseProtocol mouseProtocol = MouseProtocol.OFF;
 
-    /**
-     * XTERM mouse reporting encodings.
-     */
-    private enum MouseEncoding {
-        X10,
-        UTF8,
-        SGR
-    }
-
     /**
      * Which mouse encoding is active.
      */
     /**
      * Which mouse encoding is active.
      */
@@ -521,77 +302,12 @@ public class ECMA48 implements Runnable {
      */
     private int width;
 
      */
     private int width;
 
-    /**
-     * Get the display width.
-     *
-     * @return the width (usually 80 or 132)
-     */
-    public final int getWidth() {
-        return width;
-    }
-
-    /**
-     * Set the display width.
-     *
-     * @param width the new width
-     */
-    public final void setWidth(final int width) {
-        this.width = width;
-        rightMargin = width - 1;
-        if (currentState.cursorX >= width) {
-            currentState.cursorX = width - 1;
-        }
-        if (savedState.cursorX >= width) {
-            savedState.cursorX = width - 1;
-        }
-    }
-
     /**
      * Physical display height.  We start at 80x24, but the user can resize
      * us bigger/smaller.
      */
     private int height;
 
     /**
      * Physical display height.  We start at 80x24, but the user can resize
      * us bigger/smaller.
      */
     private int height;
 
-    /**
-     * Get the display height.
-     *
-     * @return the height (usually 24)
-     */
-    public final int getHeight() {
-        return height;
-    }
-
-    /**
-     * Set the display height.
-     *
-     * @param height the new height
-     */
-    public final void setHeight(final int height) {
-        int delta = height - this.height;
-        this.height = height;
-        scrollRegionBottom += delta;
-        if (scrollRegionBottom < 0) {
-            scrollRegionBottom = height;
-        }
-        if (scrollRegionTop >= scrollRegionBottom) {
-            scrollRegionTop = 0;
-        }
-        if (currentState.cursorY >= height) {
-            currentState.cursorY = height - 1;
-        }
-        if (savedState.cursorY >= height) {
-            savedState.cursorY = height - 1;
-        }
-        while (display.size() < height) {
-            DisplayLine line = new DisplayLine(currentState.attr);
-            line.setReverseColor(reverseVideo);
-            display.add(line);
-        }
-        while (display.size() > height) {
-            scrollback.add(display.remove(0));
-        }
-    }
-
     /**
      * Top margin of the scrolling region.
      */
     /**
      * Top margin of the scrolling region.
      */
@@ -641,32 +357,12 @@ public class ECMA48 implements Runnable {
      */
     private boolean cursorVisible = true;
 
      */
     private boolean cursorVisible = true;
 
-    /**
-     * Get visible cursor flag.
-     *
-     * @return if true, the cursor is visible
-     */
-    public final boolean isCursorVisible() {
-        return cursorVisible;
-    }
-
     /**
      * Screen title as set by the xterm OSC sequence.  Lots of applications
      * send a screenTitle regardless of whether it is an xterm client or not.
      */
     private String screenTitle = "";
 
     /**
      * Screen title as set by the xterm OSC sequence.  Lots of applications
      * send a screenTitle regardless of whether it is an xterm client or not.
      */
     private String screenTitle = "";
 
-    /**
-     * Get the screen title as set by the xterm OSC sequence.  Lots of
-     * applications send a screenTitle regardless of whether it is an xterm
-     * client or not.
-     *
-     * @return screen title
-     */
-    public final String getScreenTitle() {
-        return screenTitle;
-    }
-
     /**
      * Parameter characters being collected.
      */
     /**
      * Parameter characters being collected.
      */
@@ -722,15 +418,6 @@ public class ECMA48 implements Runnable {
      */
     private boolean columns132 = false;
 
      */
     private boolean columns132 = false;
 
-    /**
-     * Get 132 columns value.
-     *
-     * @return if true, the terminal is in 132 column mode
-     */
-    public final boolean isColumns132() {
-                return columns132;
-        }
-
     /**
      * true = reverse video.  Set by DECSCNM.
      */
     /**
      * true = reverse video.  Set by DECSCNM.
      */
@@ -741,6 +428,16 @@ public class ECMA48 implements Runnable {
      */
     private boolean fullDuplex = true;
 
      */
     private boolean fullDuplex = true;
 
+    /**
+     * The current terminal state.
+     */
+    private SaveableState currentState;
+
+    /**
+     * The last saved terminal state.
+     */
+    private SaveableState savedState;
+
     /**
      * DECSC/DECRC save/restore a subset of the total state.  This class
      * encapsulates those specific flags/modes.
     /**
      * DECSC/DECRC save/restore a subset of the total state.  This class
      * encapsulates those specific flags/modes.
@@ -846,23 +543,520 @@ public class ECMA48 implements Runnable {
             this.lineWrap       = that.lineWrap;
         }
 
             this.lineWrap       = that.lineWrap;
         }
 
-        /**
-         * Public constructor.
-         */
-        public SaveableState() {
-            reset();
+        /**
+         * Public constructor.
+         */
+        public SaveableState() {
+            reset();
+        }
+    }
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Public constructor.
+     *
+     * @param type one of the DeviceType constants to select VT100, VT102,
+     * VT220, or XTERM
+     * @param inputStream an InputStream connected to the remote side.  For
+     * type == XTERM, inputStream is converted to a Reader with UTF-8
+     * encoding.
+     * @param outputStream an OutputStream connected to the remote user.  For
+     * type == XTERM, outputStream is converted to a Writer with UTF-8
+     * encoding.
+     * @param displayListener a callback to the outer display, or null for
+     * default VT100 behavior
+     * @throws UnsupportedEncodingException if an exception is thrown when
+     * creating the InputStreamReader
+     */
+    public ECMA48(final DeviceType type, final InputStream inputStream,
+        final OutputStream outputStream, final DisplayListener displayListener)
+        throws UnsupportedEncodingException {
+
+        assert (inputStream != null);
+        assert (outputStream != null);
+
+        csiParams         = new ArrayList<Integer>();
+        tabStops          = new ArrayList<Integer>();
+        scrollback        = new LinkedList<DisplayLine>();
+        display           = new LinkedList<DisplayLine>();
+
+        this.type         = type;
+        if (inputStream instanceof TimeoutInputStream) {
+            this.inputStream  = (TimeoutInputStream)inputStream;
+        } else {
+            this.inputStream  = new TimeoutInputStream(inputStream, 2000);
+        }
+        if (type == DeviceType.XTERM) {
+            this.input    = new InputStreamReader(this.inputStream, "UTF-8");
+            this.output   = new OutputStreamWriter(new
+                BufferedOutputStream(outputStream), "UTF-8");
+            this.outputStream = null;
+        } else {
+            this.output       = null;
+            this.outputStream = new BufferedOutputStream(outputStream);
+        }
+        this.displayListener  = displayListener;
+
+        reset();
+        for (int i = 0; i < height; i++) {
+            display.add(new DisplayLine(currentState.attr));
+        }
+
+        // Spin up the input reader
+        readerThread = new Thread(this);
+        readerThread.start();
+    }
+
+    // ------------------------------------------------------------------------
+    // Runnable ---------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Read function runs on a separate thread.
+     */
+    public final void run() {
+        boolean utf8 = false;
+        boolean done = false;
+
+        if (type == DeviceType.XTERM) {
+            utf8 = true;
+        }
+
+        // available() will often return > 1, so we need to read in chunks to
+        // stay caught up.
+        char [] readBufferUTF8 = null;
+        byte [] readBuffer = null;
+        if (utf8) {
+            readBufferUTF8 = new char[128];
+        } else {
+            readBuffer = new byte[128];
+        }
+
+        while (!done && !stopReaderThread) {
+            try {
+                int n = inputStream.available();
+
+                // System.err.printf("available() %d\n", n); System.err.flush();
+                if (utf8) {
+                    if (readBufferUTF8.length < n) {
+                        // The buffer wasn't big enough, make it huger
+                        int newSizeHalf = Math.max(readBufferUTF8.length,
+                            n);
+
+                        readBufferUTF8 = new char[newSizeHalf * 2];
+                    }
+                } else {
+                    if (readBuffer.length < n) {
+                        // The buffer wasn't big enough, make it huger
+                        int newSizeHalf = Math.max(readBuffer.length, n);
+                        readBuffer = new byte[newSizeHalf * 2];
+                    }
+                }
+                if (n == 0) {
+                    try {
+                        Thread.sleep(2);
+                    } catch (InterruptedException e) {
+                        // SQUASH
+                    }
+                    continue;
+                }
+
+                int rc = -1;
+                try {
+                    if (utf8) {
+                        rc = input.read(readBufferUTF8, 0,
+                            readBufferUTF8.length);
+                    } else {
+                        rc = inputStream.read(readBuffer, 0,
+                            readBuffer.length);
+                    }
+                } catch (ReadTimeoutException e) {
+                    rc = 0;
+                }
+
+                // System.err.printf("read() %d\n", rc); System.err.flush();
+                if (rc == -1) {
+                    // This is EOF
+                    done = true;
+                } else {
+                    // Don't step on UI events
+                    synchronized (this) {
+                        for (int i = 0; i < rc; i++) {
+                            int ch = 0;
+                            if (utf8) {
+                                ch = readBufferUTF8[i];
+                            } else {
+                                ch = readBuffer[i];
+                            }
+
+                            consume((char)ch);
+                        }
+                    }
+                    // Permit my enclosing UI to know that I updated.
+                    if (displayListener != null) {
+                        displayListener.displayChanged();
+                    }
+                }
+                // System.err.println("end while loop"); System.err.flush();
+            } catch (IOException e) {
+                e.printStackTrace();
+                done = true;
+            }
+
+        } // while ((done == false) && (stopReaderThread == false))
+
+        // Let the rest of the world know that I am done.
+        stopReaderThread = true;
+
+        try {
+            inputStream.cancelRead();
+            inputStream.close();
+            inputStream = null;
+        } catch (IOException e) {
+            // SQUASH
+        }
+        try {
+            input.close();
+            input = null;
+        } catch (IOException e) {
+            // SQUASH
+        }
+
+        // Permit my enclosing UI to know that I updated.
+        if (displayListener != null) {
+            displayListener.displayChanged();
+        }
+
+        // System.err.println("*** run() exiting..."); System.err.flush();
+    }
+
+    // ------------------------------------------------------------------------
+    // ECMA48 -----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Return the proper primary Device Attributes string.
+     *
+     * @return string to send to remote side that is appropriate for the
+     * this.type
+     */
+    private String deviceTypeResponse() {
+        switch (type) {
+        case VT100:
+            // "I am a VT100 with advanced video option" (often VT102)
+            return "\033[?1;2c";
+
+        case VT102:
+            // "I am a VT102"
+            return "\033[?6c";
+
+        case VT220:
+        case XTERM:
+            // "I am a VT220" - 7 bit version
+            if (!s8c1t) {
+                return "\033[?62;1;6c";
+            }
+            // "I am a VT220" - 8 bit version
+            return "\u009b?62;1;6c";
+        default:
+            throw new IllegalArgumentException("Invalid device type: " + type);
+        }
+    }
+
+    /**
+     * Return the proper TERM environment variable for this device type.
+     *
+     * @param deviceType DeviceType.VT100, DeviceType, XTERM, etc.
+     * @return "vt100", "xterm", etc.
+     */
+    public static String deviceTypeTerm(final DeviceType deviceType) {
+        switch (deviceType) {
+        case VT100:
+            return "vt100";
+
+        case VT102:
+            return "vt102";
+
+        case VT220:
+            return "vt220";
+
+        case XTERM:
+            return "xterm";
+
+        default:
+            throw new IllegalArgumentException("Invalid device type: "
+                + deviceType);
+        }
+    }
+
+    /**
+     * Return the proper LANG for this device type.  Only XTERM devices know
+     * about UTF-8, the others are defined by their standard to be either
+     * 7-bit or 8-bit characters only.
+     *
+     * @param deviceType DeviceType.VT100, DeviceType, XTERM, etc.
+     * @param baseLang a base language without UTF-8 flag such as "C" or
+     * "en_US"
+     * @return "en_US", "en_US.UTF-8", etc.
+     */
+    public static String deviceTypeLang(final DeviceType deviceType,
+        final String baseLang) {
+
+        switch (deviceType) {
+
+        case VT100:
+        case VT102:
+        case VT220:
+            return baseLang;
+
+        case XTERM:
+            return baseLang + ".UTF-8";
+
+        default:
+            throw new IllegalArgumentException("Invalid device type: "
+                + deviceType);
+        }
+    }
+
+    /**
+     * Write a string directly to the remote side.
+     *
+     * @param str string to send
+     */
+    public void writeRemote(final String str) {
+        if (stopReaderThread) {
+            // Reader hit EOF, bail out now.
+            close();
+            return;
+        }
+
+        // System.err.printf("writeRemote() '%s'\n", str);
+
+        switch (type) {
+        case VT100:
+        case VT102:
+        case VT220:
+            if (outputStream == null) {
+                return;
+            }
+            try {
+                outputStream.flush();
+                for (int i = 0; i < str.length(); i++) {
+                    outputStream.write(str.charAt(i));
+                }
+                outputStream.flush();
+            } catch (IOException e) {
+                // Assume EOF
+                close();
+            }
+            break;
+        case XTERM:
+            if (output == null) {
+                return;
+            }
+            try {
+                output.flush();
+                output.write(str);
+                output.flush();
+            } catch (IOException e) {
+                // Assume EOF
+                close();
+            }
+            break;
+        default:
+            throw new IllegalArgumentException("Invalid device type: " + type);
+        }
+    }
+
+    /**
+     * Close the input and output streams and stop the reader thread.  Note
+     * that it is safe to call this multiple times.
+     */
+    public final void close() {
+
+        // Tell the reader thread to stop looking at input.  It will close
+        // the input streams.
+        if (stopReaderThread == false) {
+            stopReaderThread = true;
+            try {
+                readerThread.join(1000);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+
+        // Now close the output stream.
+        switch (type) {
+        case VT100:
+        case VT102:
+        case VT220:
+            if (outputStream != null) {
+                try {
+                    outputStream.close();
+                } catch (IOException e) {
+                    // SQUASH
+                }
+                outputStream = null;
+            }
+            break;
+        case XTERM:
+            if (outputStream != null) {
+                try {
+                    outputStream.close();
+                } catch (IOException e) {
+                    // SQUASH
+                }
+                outputStream = null;
+            }
+            if (output != null) {
+                try {
+                    output.close();
+                } catch (IOException e) {
+                    // SQUASH
+                }
+                output = null;
+            }
+            break;
+        default:
+            throw new IllegalArgumentException("Invalid device type: " +
+                type);
+        }
+    }
+
+    /**
+     * See if the reader thread is still running.
+     *
+     * @return if true, we are still connected to / reading from the remote
+     * side
+     */
+    public final boolean isReading() {
+        return (!stopReaderThread);
+    }
+
+    /**
+     * Obtain a new blank display line for an external user
+     * (e.g. TTerminalWindow).
+     *
+     * @return new blank line
+     */
+    public final DisplayLine getBlankDisplayLine() {
+        return new DisplayLine(currentState.attr);
+    }
+
+    /**
+     * Get the scrollback buffer.
+     *
+     * @return the scrollback buffer
+     */
+    public final List<DisplayLine> getScrollbackBuffer() {
+        return scrollback;
+    }
+
+    /**
+     * Get the display buffer.
+     *
+     * @return the display buffer
+     */
+    public final List<DisplayLine> getDisplayBuffer() {
+        return display;
+    }
+
+    /**
+     * Get the display width.
+     *
+     * @return the width (usually 80 or 132)
+     */
+    public final int getWidth() {
+        return width;
+    }
+
+    /**
+     * Set the display width.
+     *
+     * @param width the new width
+     */
+    public final void setWidth(final int width) {
+        this.width = width;
+        rightMargin = width - 1;
+        if (currentState.cursorX >= width) {
+            currentState.cursorX = width - 1;
+        }
+        if (savedState.cursorX >= width) {
+            savedState.cursorX = width - 1;
+        }
+    }
+
+    /**
+     * Get the display height.
+     *
+     * @return the height (usually 24)
+     */
+    public final int getHeight() {
+        return height;
+    }
+
+    /**
+     * Set the display height.
+     *
+     * @param height the new height
+     */
+    public final void setHeight(final int height) {
+        int delta = height - this.height;
+        this.height = height;
+        scrollRegionBottom += delta;
+        if (scrollRegionBottom < 0) {
+            scrollRegionBottom = height;
+        }
+        if (scrollRegionTop >= scrollRegionBottom) {
+            scrollRegionTop = 0;
+        }
+        if (currentState.cursorY >= height) {
+            currentState.cursorY = height - 1;
+        }
+        if (savedState.cursorY >= height) {
+            savedState.cursorY = height - 1;
+        }
+        while (display.size() < height) {
+            DisplayLine line = new DisplayLine(currentState.attr);
+            line.setReverseColor(reverseVideo);
+            display.add(line);
+        }
+        while (display.size() > height) {
+            scrollback.add(display.remove(0));
         }
     }
 
     /**
         }
     }
 
     /**
-     * The current terminal state.
+     * Get visible cursor flag.
+     *
+     * @return if true, the cursor is visible
      */
      */
-    private SaveableState currentState;
+    public final boolean isCursorVisible() {
+        return cursorVisible;
+    }
 
     /**
 
     /**
-     * The last saved terminal state.
+     * Get the screen title as set by the xterm OSC sequence.  Lots of
+     * applications send a screenTitle regardless of whether it is an xterm
+     * client or not.
+     *
+     * @return screen title
      */
      */
-    private SaveableState savedState;
+    public final String getScreenTitle() {
+        return screenTitle;
+    }
+
+    /**
+     * Get 132 columns value.
+     *
+     * @return if true, the terminal is in 132 column mode
+     */
+    public final boolean isColumns132() {
+        return columns132;
+    }
 
     /**
      * Clear the CSI parameters and flags.
 
     /**
      * Clear the CSI parameters and flags.
@@ -932,61 +1126,6 @@ public class ECMA48 implements Runnable {
         toGround();
     }
 
         toGround();
     }
 
-    /**
-     * Public constructor.
-     *
-     * @param type one of the DeviceType constants to select VT100, VT102,
-     * VT220, or XTERM
-     * @param inputStream an InputStream connected to the remote side.  For
-     * type == XTERM, inputStream is converted to a Reader with UTF-8
-     * encoding.
-     * @param outputStream an OutputStream connected to the remote user.  For
-     * type == XTERM, outputStream is converted to a Writer with UTF-8
-     * encoding.
-     * @param displayListener a callback to the outer display, or null for
-     * default VT100 behavior
-     * @throws UnsupportedEncodingException if an exception is thrown when
-     * creating the InputStreamReader
-     */
-    public ECMA48(final DeviceType type, final InputStream inputStream,
-        final OutputStream outputStream, final DisplayListener displayListener)
-        throws UnsupportedEncodingException {
-
-        assert (inputStream != null);
-        assert (outputStream != null);
-
-        csiParams         = new ArrayList<Integer>();
-        tabStops          = new ArrayList<Integer>();
-        scrollback        = new LinkedList<DisplayLine>();
-        display           = new LinkedList<DisplayLine>();
-
-        this.type         = type;
-        if (inputStream instanceof TimeoutInputStream) {
-            this.inputStream  = (TimeoutInputStream)inputStream;
-        } else {
-            this.inputStream  = new TimeoutInputStream(inputStream, 2000);
-        }
-        if (type == DeviceType.XTERM) {
-            this.input    = new InputStreamReader(this.inputStream, "UTF-8");
-            this.output   = new OutputStreamWriter(new
-                BufferedOutputStream(outputStream), "UTF-8");
-            this.outputStream = null;
-        } else {
-            this.output       = null;
-            this.outputStream = new BufferedOutputStream(outputStream);
-        }
-        this.displayListener  = displayListener;
-
-        reset();
-        for (int i = 0; i < height; i++) {
-            display.add(new DisplayLine(currentState.attr));
-        }
-
-        // Spin up the input reader
-        readerThread = new Thread(this);
-        readerThread.start();
-    }
-
     /**
      * Append a new line to the bottom of the display, adding lines off the
      * top to the scrollback buffer.
     /**
      * Append a new line to the bottom of the display, adding lines off the
      * top to the scrollback buffer.
@@ -6015,123 +6154,4 @@ public class ECMA48 implements Runnable {
         return currentState.cursorY;
     }
 
         return currentState.cursorY;
     }
 
-    /**
-     * Read function runs on a separate thread.
-     */
-    public final void run() {
-        boolean utf8 = false;
-        boolean done = false;
-
-        if (type == DeviceType.XTERM) {
-            utf8 = true;
-        }
-
-        // available() will often return > 1, so we need to read in chunks to
-        // stay caught up.
-        char [] readBufferUTF8 = null;
-        byte [] readBuffer = null;
-        if (utf8) {
-            readBufferUTF8 = new char[128];
-        } else {
-            readBuffer = new byte[128];
-        }
-
-        while (!done && !stopReaderThread) {
-            try {
-                int n = inputStream.available();
-
-                // System.err.printf("available() %d\n", n); System.err.flush();
-                if (utf8) {
-                    if (readBufferUTF8.length < n) {
-                        // The buffer wasn't big enough, make it huger
-                        int newSizeHalf = Math.max(readBufferUTF8.length,
-                            n);
-
-                        readBufferUTF8 = new char[newSizeHalf * 2];
-                    }
-                } else {
-                    if (readBuffer.length < n) {
-                        // The buffer wasn't big enough, make it huger
-                        int newSizeHalf = Math.max(readBuffer.length, n);
-                        readBuffer = new byte[newSizeHalf * 2];
-                    }
-                }
-                if (n == 0) {
-                    try {
-                        Thread.sleep(2);
-                    } catch (InterruptedException e) {
-                        // SQUASH
-                    }
-                    continue;
-                }
-
-                int rc = -1;
-                try {
-                    if (utf8) {
-                        rc = input.read(readBufferUTF8, 0,
-                            readBufferUTF8.length);
-                    } else {
-                        rc = inputStream.read(readBuffer, 0,
-                            readBuffer.length);
-                    }
-                } catch (ReadTimeoutException e) {
-                    rc = 0;
-                }
-
-                // System.err.printf("read() %d\n", rc); System.err.flush();
-                if (rc == -1) {
-                    // This is EOF
-                    done = true;
-                } else {
-                    // Don't step on UI events
-                    synchronized (this) {
-                        for (int i = 0; i < rc; i++) {
-                            int ch = 0;
-                            if (utf8) {
-                                ch = readBufferUTF8[i];
-                            } else {
-                                ch = readBuffer[i];
-                            }
-
-                            consume((char)ch);
-                        }
-                    }
-                    // Permit my enclosing UI to know that I updated.
-                    if (displayListener != null) {
-                        displayListener.displayChanged();
-                    }
-                }
-                // System.err.println("end while loop"); System.err.flush();
-            } catch (IOException e) {
-                e.printStackTrace();
-                done = true;
-            }
-
-        } // while ((done == false) && (stopReaderThread == false))
-
-        // Let the rest of the world know that I am done.
-        stopReaderThread = true;
-
-        try {
-            inputStream.cancelRead();
-            inputStream.close();
-            inputStream = null;
-        } catch (IOException e) {
-            // SQUASH
-        }
-        try {
-            input.close();
-            input = null;
-        } catch (IOException e) {
-            // SQUASH
-        }
-
-        // Permit my enclosing UI to know that I updated.
-        if (displayListener != null) {
-            displayListener.displayChanged();
-        }
-
-        // System.err.println("*** run() exiting..."); System.err.flush();
-    }
-
 }
 }
similarity index 80%
rename from src/jexer/TDirectoryTreeItem.java
rename to src/jexer/ttree/TDirectoryTreeItem.java
index 6d5d018004775c97ae1b476018c5c30f398ce996..c260d7fb95212c7920fce537f5deb74066553b9f 100644 (file)
@@ -26,7 +26,7 @@
  * @author Kevin Lamonte [kevin.lamonte@gmail.com]
  * @version 1
  */
  * @author Kevin Lamonte [kevin.lamonte@gmail.com]
  * @version 1
  */
-package jexer;
+package jexer.ttree;
 
 import java.io.File;
 import java.io.IOException;
 
 import java.io.File;
 import java.io.IOException;
@@ -34,116 +34,41 @@ import java.util.Collections;
 import java.util.List;
 import java.util.LinkedList;
 
 import java.util.List;
 import java.util.LinkedList;
 
+import jexer.TWidget;
+import jexer.ttree.TTreeViewWidget;
+
 /**
  * TDirectoryTreeItem is a single item in a disk directory tree view.
  */
 public class TDirectoryTreeItem extends TTreeItem {
 
 /**
  * TDirectoryTreeItem is a single item in a disk directory tree view.
  */
 public class TDirectoryTreeItem extends TTreeItem {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * File corresponding to this list item.
      */
     private File file;
 
     /**
     /**
      * File corresponding to this list item.
      */
     private File file;
 
     /**
-     * Get the File corresponding to this list item.
-     *
-     * @return the File
+     * The TTreeViewWidget containing this directory tree.
      */
      */
-    public final File getFile() {
-        return file;
-    }
+    private TTreeViewWidget treeViewWidget;
 
 
-    /**
-     * Called when this item is expanded or collapsed.  this.expanded will be
-     * true if this item was just expanded from a mouse click or keypress.
-     */
-    @Override
-    public final void onExpand() {
-        // System.err.printf("onExpand() %s\n", file);
-
-        if (file == null) {
-            return;
-        }
-        getChildren().clear();
-
-        // Make sure we can read it before trying to.
-        if (file.canRead()) {
-            setSelectable(true);
-        } else {
-            setSelectable(false);
-        }
-        assert (file.isDirectory());
-        setExpandable(true);
-
-        if (!isExpanded() || !isExpandable()) {
-            getTreeView().reflowData();
-            return;
-        }
-
-        for (File f: file.listFiles()) {
-            // System.err.printf("   -> file %s %s\n", file, file.getName());
-
-            if (f.getName().startsWith(".")) {
-                // Hide dot-files
-                continue;
-            }
-            if (!f.isDirectory()) {
-                continue;
-            }
-
-            try {
-                TDirectoryTreeItem item = new TDirectoryTreeItem(getTreeView(),
-                    f.getCanonicalPath(), false, false);
-
-                item.level = this.level + 1;
-                getChildren().add(item);
-            } catch (IOException e) {
-                continue;
-            }
-        }
-        Collections.sort(getChildren());
-
-        getTreeView().reflowData();
-    }
-
-    /**
-     * Add a child item.  This method should never be used, it will throw an
-     * IllegalArgumentException every time.
-     *
-     * @param text text for this item
-     * @param expanded if true, have it expanded immediately
-     * @return the new item
-     * @throws IllegalArgumentException if this function is called
-     */
-    @Override
-    public final TTreeItem addChild(final String text,
-        final boolean expanded) throws IllegalArgumentException {
-
-        throw new IllegalArgumentException("Do not call addChild(), use onExpand() instead");
-    }
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Public constructor.
      *
 
     /**
      * Public constructor.
      *
-     * @param view root TTreeView
-     * @param text text for this item
-     * @throws IOException if a java.io operation throws
-     */
-    public TDirectoryTreeItem(final TTreeView view,
-        final String text) throws IOException {
-
-        this(view, text, false, true);
-    }
-
-    /**
-     * Public constructor.
-     *
-     * @param view root TTreeView
+     * @param view root TTreeViewWidget
      * @param text text for this item
      * @param expanded if true, have it expanded immediately
      * @throws IOException if a java.io operation throws
      */
      * @param text text for this item
      * @param expanded if true, have it expanded immediately
      * @throws IOException if a java.io operation throws
      */
-    public TDirectoryTreeItem(final TTreeView view, final String text,
+    public TDirectoryTreeItem(final TTreeViewWidget view, final String text,
         final boolean expanded) throws IOException {
 
         this(view, text, expanded, true);
         final boolean expanded) throws IOException {
 
         this(view, text, expanded, true);
@@ -152,17 +77,19 @@ public class TDirectoryTreeItem extends TTreeItem {
     /**
      * Public constructor.
      *
     /**
      * Public constructor.
      *
-     * @param view root TTreeView
+     * @param view root TTreeViewWidget
      * @param text text for this item
      * @param expanded if true, have it expanded immediately
      * @param openParents if true, expand all paths up the root path and
      * return the root path entry
      * @throws IOException if a java.io operation throws
      */
      * @param text text for this item
      * @param expanded if true, have it expanded immediately
      * @param openParents if true, expand all paths up the root path and
      * return the root path entry
      * @throws IOException if a java.io operation throws
      */
-    public TDirectoryTreeItem(final TTreeView view, final String text,
+    public TDirectoryTreeItem(final TTreeViewWidget view, final String text,
         final boolean expanded, final boolean openParents) throws IOException {
 
         final boolean expanded, final boolean openParents) throws IOException {
 
-        super(view, text, false);
+        super(view.getTreeView(), text, false);
+
+        this.treeViewWidget = view;
 
         List<String> parentFiles = new LinkedList<String>();
         boolean oldExpanded = expanded;
 
         List<String> parentFiles = new LinkedList<String>();
         boolean oldExpanded = expanded;
@@ -209,9 +136,74 @@ public class TDirectoryTreeItem extends TTreeItem {
                 }
             }
             unselect();
                 }
             }
             unselect();
-            getTreeView().setSelected(childFile);
+            getTreeView().setSelected(childFile, true);
             setExpanded(oldExpanded);
         }
             setExpanded(oldExpanded);
         }
-        getTreeView().reflowData();
+
+        view.reflowData();
+    }
+
+    // ------------------------------------------------------------------------
+    // TTreeItem --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Get the File corresponding to this list item.
+     *
+     * @return the File
+     */
+    public final File getFile() {
+        return file;
+    }
+
+    /**
+     * Called when this item is expanded or collapsed.  this.expanded will be
+     * true if this item was just expanded from a mouse click or keypress.
+     */
+    @Override
+    public final void onExpand() {
+        // System.err.printf("onExpand() %s\n", file);
+
+        if (file == null) {
+            return;
+        }
+        getChildren().clear();
+
+        // Make sure we can read it before trying to.
+        if (file.canRead()) {
+            setSelectable(true);
+        } else {
+            setSelectable(false);
+        }
+        assert (file.isDirectory());
+        setExpandable(true);
+
+        if (!isExpanded() || !isExpandable()) {
+            return;
+        }
+
+        for (File f: file.listFiles()) {
+            // System.err.printf("   -> file %s %s\n", file, file.getName());
+
+            if (f.getName().startsWith(".")) {
+                // Hide dot-files
+                continue;
+            }
+            if (!f.isDirectory()) {
+                continue;
+            }
+
+            try {
+                TDirectoryTreeItem item = new TDirectoryTreeItem(treeViewWidget,
+                    f.getCanonicalPath(), false, false);
+
+                item.level = this.level + 1;
+                getChildren().add(item);
+            } catch (IOException e) {
+                continue;
+            }
+        }
+        Collections.sort(getChildren());
     }
     }
+
 }
 }
similarity index 76%
rename from src/jexer/TTreeItem.java
rename to src/jexer/ttree/TTreeItem.java
index 7de5e123587fe16aa06a62ab5737f27afa5d41ff..901ce8590a78095bdf00a5831f86331b5b84d9e9 100644 (file)
  * @author Kevin Lamonte [kevin.lamonte@gmail.com]
  * @version 1
  */
  * @author Kevin Lamonte [kevin.lamonte@gmail.com]
  * @version 1
  */
-package jexer;
+package jexer.ttree;
 
 import java.util.ArrayList;
 import java.util.List;
 
 
 import java.util.ArrayList;
 import java.util.List;
 
+import jexer.TWidget;
 import jexer.bits.CellAttributes;
 import jexer.bits.GraphicsChars;
 import jexer.event.TKeypressEvent;
 import jexer.bits.CellAttributes;
 import jexer.bits.GraphicsChars;
 import jexer.event.TKeypressEvent;
@@ -42,164 +43,55 @@ import static jexer.TKeypress.*;
  */
 public class TTreeItem extends TWidget {
 
  */
 public class TTreeItem extends TWidget {
 
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Hang onto reference to my parent TTreeView so I can call its reflow()
      * when I add a child node.
      */
     private TTreeView view;
 
     /**
      * Hang onto reference to my parent TTreeView so I can call its reflow()
      * when I add a child node.
      */
     private TTreeView view;
 
-    /**
-     * Get the parent TTreeView.
-     *
-     * @return the parent TTreeView
-     */
-    public final TTreeView getTreeView() {
-        return view;
-    }
-
     /**
      * Displayable text for this item.
      */
     private String text;
 
     /**
      * Displayable text for this item.
      */
     private String text;
 
-    /**
-     * Get the displayable text for this item.
-     *
-     * @return the displayable text for this item
-     */
-    public final String getText() {
-        return text;
-    }
-
-    /**
-     * Set the displayable text for this item.
-     *
-     * @param text the displayable text for this item
-     */
-    public final void setText(final String text) {
-        this.text = text;
-    }
-
     /**
      * If true, this item is expanded in the tree view.
      */
     private boolean expanded = true;
 
     /**
      * If true, this item is expanded in the tree view.
      */
     private boolean expanded = true;
 
-    /**
-     * Get expanded value.
-     *
-     * @return if true, this item is expanded
-     */
-    public final boolean isExpanded() {
-        return expanded;
-    }
-
-    /**
-     * Set expanded value.
-     *
-     * @param expanded new value
-     */
-    public final void setExpanded(final boolean expanded) {
-        this.expanded = expanded;
-    }
-
     /**
      * If true, this item can be expanded in the tree view.
      */
     private boolean expandable = false;
 
     /**
      * If true, this item can be expanded in the tree view.
      */
     private boolean expandable = false;
 
-    /**
-     * Get expandable value.
-     *
-     * @return if true, this item is expandable
-     */
-    public final boolean isExpandable() {
-        return expandable;
-    }
-
-    /**
-     * Set expandable value.
-     *
-     * @param expandable new value
-     */
-    public final void setExpandable(final boolean expandable) {
-        this.expandable = expandable;
-    }
-
     /**
      * The vertical bars and such along the left side.
      */
     private String prefix = "";
 
     /**
     /**
      * The vertical bars and such along the left side.
      */
     private String prefix = "";
 
     /**
-     * Get the vertical bars and such along the left side.
-     *
-     * @return the vertical bars and such along the left side
+     * Tree level.
      */
      */
-    public final String getPrefix() {
-        return prefix;
-    }
-
-    /**
-     * Whether or not this item is last in its parent's list of children.
-     */
-    private boolean last = false;
-
-    /**
-     * Tree level.  Note package private access.
-     */
-    int level = 0;
-
-    /**
-     * If true, this item will not be drawn.
-     */
-    private boolean invisible = false;
-
-    /**
-     * Set invisible value.
-     *
-     * @param invisible new value
-     */
-    public final void setInvisible(final boolean invisible) {
-        this.invisible = invisible;
-    }
+    protected int level = 0;
 
     /**
      * True means selected.
      */
     private boolean selected = false;
 
 
     /**
      * True means selected.
      */
     private boolean selected = false;
 
-    /**
-     * Get selected value.
-     *
-     * @return if true, this item is selected
-     */
-    public final boolean isSelected() {
-        return selected;
-    }
-
-    /**
-     * Set selected value.
-     *
-     * @param selected new value
-     */
-    public final void setSelected(final boolean selected) {
-        this.selected = selected;
-    }
-
     /**
      * True means select-able.
      */
     private boolean selectable = true;
 
     /**
     /**
      * True means select-able.
      */
     private boolean selectable = true;
 
     /**
-     * Set selectable value.
-     *
-     * @param selectable new value
+     * Whether or not this item is last in its parent's list of children.
      */
      */
-    public final void setSelectable(final boolean selectable) {
-        this.selectable = selectable;
-    }
+    private boolean last = false;
 
     /**
      * Pointer to the previous keyboard-navigable item (kbUp).  Note package
 
     /**
      * Pointer to the previous keyboard-navigable item (kbUp).  Note package
@@ -213,6 +105,10 @@ public class TTreeItem extends TWidget {
      */
     TTreeItem keyboardNext = null;
 
      */
     TTreeItem keyboardNext = null;
 
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Public constructor.
      *
     /**
      * Public constructor.
      *
@@ -224,107 +120,21 @@ public class TTreeItem extends TWidget {
         final boolean expanded) {
 
         super(view, 0, 0, view.getWidth() - 3, 1);
         final boolean expanded) {
 
         super(view, 0, 0, view.getWidth() - 3, 1);
+
         this.text = text;
         this.expanded = expanded;
         this.view = view;
 
         if (view.getTreeRoot() == null) {
         this.text = text;
         this.expanded = expanded;
         this.view = view;
 
         if (view.getTreeRoot() == null) {
-            view.setTreeRoot(this, true);
-        }
-
-        view.reflowData();
-    }
-
-    /**
-     * Add a child item.
-     *
-     * @param text text for this item
-     * @return the new child item
-     */
-    public TTreeItem addChild(final String text) {
-        return addChild(text, true);
-    }
-
-    /**
-     * Add a child item.
-     *
-     * @param text text for this item
-     * @param expanded if true, have it expanded immediately
-     * @return the new child item
-     */
-    public TTreeItem addChild(final String text, final boolean expanded) {
-        TTreeItem item = new TTreeItem(view, text, expanded);
-        item.level = this.level + 1;
-        getChildren().add(item);
-        view.reflowData();
-        return item;
-    }
-
-    /**
-     * Recursively expand the tree into a linear array of items.
-     *
-     * @param prefix vertical bar of parent levels and such that is set on
-     * each child
-     * @param last if true, this is the "last" leaf node of a tree
-     * @return additional items to add to the array
-     */
-    public List<TTreeItem> expandTree(final String prefix, final boolean last) {
-        List<TTreeItem> array = new ArrayList<TTreeItem>();
-        this.last = last;
-        this.prefix = prefix;
-        array.add(this);
-
-        if ((getChildren().size() == 0) || !expanded) {
-            return array;
-        }
-
-        String newPrefix = prefix;
-        if (level > 0) {
-            if (last) {
-                newPrefix += "  ";
-            } else {
-                newPrefix += GraphicsChars.CP437[0xB3];
-                newPrefix += ' ';
-            }
-        }
-        for (int i = 0; i < getChildren().size(); i++) {
-            TTreeItem item = (TTreeItem) getChildren().get(i);
-            if (i == getChildren().size() - 1) {
-                array.addAll(item.expandTree(newPrefix, true));
-            } else {
-                array.addAll(item.expandTree(newPrefix, false));
-            }
-        }
-        return array;
-    }
-
-    /**
-     * Get the x spot for the + or - to expand/collapse.
-     *
-     * @return column of the expand/collapse button
-     */
-    private int getExpanderX() {
-        if ((level == 0) || (!expandable)) {
-            return 0;
+            view.setTreeRoot(this);
+        } else {
+            view.alignTree();
         }
         }
-        return prefix.length() + 3;
     }
 
     }
 
-    /**
-     * Recursively unselect my or my children.
-     */
-    public void unselect() {
-        if (selected == true) {
-            selected = false;
-            view.setSelected(null);
-        }
-        for (TWidget widget: getChildren()) {
-            if (widget instanceof TTreeItem) {
-                TTreeItem item = (TTreeItem) widget;
-                item.unselect();
-            }
-        }
-    }
+    // ------------------------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Handle mouse release events.
 
     /**
      * Handle mouse release events.
@@ -333,9 +143,13 @@ public class TTreeItem extends TWidget {
      */
     @Override
     public void onMouseUp(final TMouseEvent mouse) {
      */
     @Override
     public void onMouseUp(final TMouseEvent mouse) {
-        if ((mouse.getX() == (getExpanderX() - view.getHorizontalValue()))
+        if ((mouse.getX() == (getExpanderX() - view.getLeftColumn()))
             && (mouse.getY() == 0)
         ) {
             && (mouse.getY() == 0)
         ) {
+            if (level == 0) {
+                // Root node can't switch.
+                return;
+            }
             if (selectable) {
                 // Flip expanded flag
                 expanded = !expanded;
             if (selectable) {
                 // Flip expanded flag
                 expanded = !expanded;
@@ -343,16 +157,18 @@ public class TTreeItem extends TWidget {
                     // Unselect children that became invisible
                     unselect();
                 }
                     // Unselect children that became invisible
                     unselect();
                 }
+                view.setSelected(this, false);
             }
             // Let subclasses do something with this
             onExpand();
             }
             // Let subclasses do something with this
             onExpand();
+
+            // Update the screen after any thing has expanded/contracted
+            view.alignTree();
         } else if (mouse.getY() == 0) {
         } else if (mouse.getY() == 0) {
-            view.setSelected(this);
+            // Do the action associated with this item.
+            view.setSelected(this, false);
             view.dispatch();
         }
             view.dispatch();
         }
-
-        // Update the screen after any thing has expanded/contracted
-        view.reflowData();
     }
 
     /**
     }
 
     /**
@@ -377,6 +193,10 @@ public class TTreeItem extends TWidget {
             || keypress.equals(kbRight)
             || keypress.equals(kbSpace)
         ) {
             || keypress.equals(kbRight)
             || keypress.equals(kbSpace)
         ) {
+            if (level == 0) {
+                // Root node can't switch.
+                return;
+            }
             if (selectable) {
                 // Flip expanded flag
                 expanded = !expanded;
             if (selectable) {
                 // Flip expanded flag
                 expanded = !expanded;
@@ -384,26 +204,33 @@ public class TTreeItem extends TWidget {
                     // Unselect children that became invisible
                     unselect();
                 }
                     // Unselect children that became invisible
                     unselect();
                 }
-                view.setSelected(this);
+                view.setSelected(this, false);
             }
             // Let subclasses do something with this
             onExpand();
             }
             // Let subclasses do something with this
             onExpand();
+        } else if (keypress.equals(kbEnter)) {
+            // Do the action associated with this item.
+            view.dispatch();
         } else {
             // Pass other keys (tab etc.) on to TWidget's handler.
             super.onKeypress(keypress);
         }
     }
 
         } else {
             // Pass other keys (tab etc.) on to TWidget's handler.
             super.onKeypress(keypress);
         }
     }
 
+    // ------------------------------------------------------------------------
+    // TWidget ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
     /**
      * Draw this item to a window.
      */
     @Override
     public void draw() {
     /**
      * Draw this item to a window.
      */
     @Override
     public void draw() {
-        if (invisible) {
+        if ((getY() < 0) || (getY() > getParent().getHeight() - 1)) {
             return;
         }
 
             return;
         }
 
-        int offset = -view.getHorizontalValue();
+        int offset = -view.getLeftColumn();
 
         CellAttributes color = getTheme().getColor("ttreeview");
         CellAttributes textColor = getTheme().getColor("ttreeview");
 
         CellAttributes color = getTheme().getColor("ttreeview");
         CellAttributes textColor = getTheme().getColor("ttreeview");
@@ -413,6 +240,7 @@ public class TTreeItem extends TWidget {
         if (!getParent().isAbsoluteActive()) {
             color = getTheme().getColor("ttreeview.inactive");
             textColor = getTheme().getColor("ttreeview.inactive");
         if (!getParent().isAbsoluteActive()) {
             color = getTheme().getColor("ttreeview.inactive");
             textColor = getTheme().getColor("ttreeview.inactive");
+            selectedColor = getTheme().getColor("ttreeview.selected.inactive");
         }
 
         if (!selectable) {
         }
 
         if (!selectable) {
@@ -432,6 +260,8 @@ public class TTreeItem extends TWidget {
             line += GraphicsChars.CP437[0xC4];
             if (expandable) {
                 line += "[ ] ";
             line += GraphicsChars.CP437[0xC4];
             if (expandable) {
                 line += "[ ] ";
+            } else {
+                line += " ";
             }
         }
         getScreen().putStringXY(offset, 0, line, color);
             }
         }
         getScreen().putStringXY(offset, 0, line, color);
@@ -452,4 +282,204 @@ public class TTreeItem extends TWidget {
         }
     }
 
         }
     }
 
+    // ------------------------------------------------------------------------
+    // TTreeItem --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Get the parent TTreeView.
+     *
+     * @return the parent TTreeView
+     */
+    public final TTreeView getTreeView() {
+        return view;
+    }
+
+    /**
+     * Get the displayable text for this item.
+     *
+     * @return the displayable text for this item
+     */
+    public final String getText() {
+        return text;
+    }
+
+    /**
+     * Set the displayable text for this item.
+     *
+     * @param text the displayable text for this item
+     */
+    public final void setText(final String text) {
+        this.text = text;
+    }
+
+    /**
+     * Get expanded value.
+     *
+     * @return if true, this item is expanded
+     */
+    public final boolean isExpanded() {
+        return expanded;
+    }
+
+    /**
+     * Set expanded value.
+     *
+     * @param expanded new value
+     */
+    public final void setExpanded(final boolean expanded) {
+        if (level == 0) {
+            // Root node can't be unexpanded, ever.
+            this.expanded = true;
+            return;
+        }
+        if (level > 0) {
+            this.expanded = expanded;
+        }
+    }
+
+    /**
+     * Get expandable value.
+     *
+     * @return if true, this item is expandable
+     */
+    public final boolean isExpandable() {
+        return expandable;
+    }
+
+    /**
+     * Set expandable value.
+     *
+     * @param expandable new value
+     */
+    public final void setExpandable(final boolean expandable) {
+        if (level == 0) {
+            // Root node can't be unexpanded, ever.
+            this.expandable = true;
+            return;
+        }
+        if (level > 0) {
+            this.expandable = expandable;
+        }
+    }
+
+    /**
+     * Get the vertical bars and such along the left side.
+     *
+     * @return the vertical bars and such along the left side
+     */
+    public final String getPrefix() {
+        return prefix;
+    }
+
+    /**
+     * Get selected value.
+     *
+     * @return if true, this item is selected
+     */
+    public final boolean isSelected() {
+        return selected;
+    }
+
+    /**
+     * Set selected value.
+     *
+     * @param selected new value
+     */
+    public final void setSelected(final boolean selected) {
+        this.selected = selected;
+    }
+
+    /**
+     * Set selectable value.
+     *
+     * @param selectable new value
+     */
+    public final void setSelectable(final boolean selectable) {
+        this.selectable = selectable;
+    }
+
+    /**
+     * Get the length of the widest item to display.
+     *
+     * @return the maximum number of columns for this item or its children
+     */
+    public int getMaximumColumn() {
+        int max = prefix.length() + 4 + text.length();
+        for (TWidget widget: getChildren()) {
+            TTreeItem item = (TTreeItem) widget;
+            int n = item.prefix.length() + 4 + item.text.length();
+            if (n > max) {
+                max = n;
+            }
+        }
+        return max;
+    }
+
+    /**
+     * Recursively expand the tree into a linear array of items.
+     *
+     * @param prefix vertical bar of parent levels and such that is set on
+     * each child
+     * @param last if true, this is the "last" leaf node of a tree
+     * @return additional items to add to the array
+     */
+    public List<TTreeItem> expandTree(final String prefix, final boolean last) {
+        List<TTreeItem> array = new ArrayList<TTreeItem>();
+        this.last = last;
+        this.prefix = prefix;
+        array.add(this);
+
+        if ((getChildren().size() == 0) || !expanded) {
+            return array;
+        }
+
+        String newPrefix = prefix;
+        if (level > 0) {
+            if (last) {
+                newPrefix += "  ";
+            } else {
+                newPrefix += GraphicsChars.CP437[0xB3];
+                newPrefix += ' ';
+            }
+        }
+        for (int i = 0; i < getChildren().size(); i++) {
+            TTreeItem item = (TTreeItem) getChildren().get(i);
+            if (i == getChildren().size() - 1) {
+                array.addAll(item.expandTree(newPrefix, true));
+            } else {
+                array.addAll(item.expandTree(newPrefix, false));
+            }
+        }
+        return array;
+    }
+
+    /**
+     * Get the x spot for the + or - to expand/collapse.
+     *
+     * @return column of the expand/collapse button
+     */
+    private int getExpanderX() {
+        if ((level == 0) || (!expandable)) {
+            return 0;
+        }
+        return prefix.length() + 3;
+    }
+
+    /**
+     * Recursively unselect me and my children.
+     */
+    public void unselect() {
+        if (selected == true) {
+            selected = false;
+            view.setSelected(null, false);
+        }
+        for (TWidget widget: getChildren()) {
+            if (widget instanceof TTreeItem) {
+                TTreeItem item = (TTreeItem) widget;
+                item.unselect();
+            }
+        }
+    }
+
 }
 }
diff --git a/src/jexer/ttree/TTreeView.java b/src/jexer/ttree/TTreeView.java
new file mode 100644 (file)
index 0000000..acc8924
--- /dev/null
@@ -0,0 +1,329 @@
+/*
+ * Jexer - Java Text User Interface
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (C) 2017 Kevin Lamonte
+ *
+ * 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:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * 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
+ */
+package jexer.ttree;
+
+import jexer.TAction;
+import jexer.TKeypress;
+import jexer.TWidget;
+import jexer.event.TKeypressEvent;
+import static jexer.TKeypress.*;
+
+/**
+ * TTreeView implements a simple tree view.
+ */
+public class TTreeView extends TWidget {
+
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Root of the tree.
+     */
+    private TTreeItem treeRoot;
+
+    /**
+     * Only one of my children can be selected.
+     */
+    private TTreeItem selectedItem = null;
+
+    /**
+     * The action to perform when the user selects an item.
+     */
+    private TAction action = null;
+
+    /**
+     * The top line currently visible.
+     */
+    private int topLine = 0;
+
+    /**
+     * The left column currently visible.
+     */
+    private int leftColumn = 0;
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Public constructor.
+     *
+     * @param parent parent widget
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param width width of tree view
+     * @param height height of tree view
+     */
+    public TTreeView(final TWidget parent, final int x, final int y,
+        final int width, final int height) {
+
+        this(parent, x, y, width, height, null);
+    }
+
+    /**
+     * Public constructor.
+     *
+     * @param parent parent widget
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param width width of tree view
+     * @param height height of tree view
+     * @param action action to perform when an item is selected
+     */
+    public TTreeView(final TWidget parent, final int x, final int y,
+        final int width, final int height, final TAction action) {
+
+        super(parent, x, y, width, height);
+        this.action = action;
+    }
+
+    // ------------------------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Handle keystrokes.
+     *
+     * @param keypress keystroke event
+     */
+    @Override
+    public void onKeypress(final TKeypressEvent keypress) {
+        if (keypress.equals(kbUp)) {
+            // Select the previous item
+            if (selectedItem != null) {
+                if (selectedItem.keyboardPrevious != null) {
+                    setSelected(selectedItem.keyboardPrevious, true);
+                }
+            }
+        } else if (keypress.equals(kbDown)) {
+            // Select the next item
+            if (selectedItem != null) {
+                if (selectedItem.keyboardNext != null) {
+                    setSelected(selectedItem.keyboardNext, true);
+                }
+            }
+        } else if (keypress.equals(kbPgDn)) {
+            for (int i = 0; i < getHeight() - 1; i++) {
+                onKeypress(new TKeypressEvent(TKeypress.kbDown));
+            }
+        } else if (keypress.equals(kbPgUp)) {
+            for (int i = 0; i < getHeight() - 1; i++) {
+                onKeypress(new TKeypressEvent(TKeypress.kbUp));
+            }
+        } else if (keypress.equals(kbHome)) {
+            setSelected((TTreeItem) getChildren().get(0), false);
+            setTopLine(0);
+        } else if (keypress.equals(kbEnd)) {
+            setSelected((TTreeItem) getChildren().get(getChildren().size() - 1),
+                true);
+        } else {
+            if (selectedItem != null) {
+                selectedItem.onKeypress(keypress);
+            } else {
+                // Pass other keys (tab etc.) on to TWidget's handler.
+                super.onKeypress(keypress);
+            }
+        }
+    }
+
+    // ------------------------------------------------------------------------
+    // TWidget ----------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+
+    // ------------------------------------------------------------------------
+    // TTreeView --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Get the root of the tree.
+     *
+     * @return the root of the tree
+     */
+    public final TTreeItem getTreeRoot() {
+        return treeRoot;
+    }
+
+    /**
+     * Set the root of the tree.
+     *
+     * @param treeRoot the new root of the tree
+     */
+    public final void setTreeRoot(final TTreeItem treeRoot) {
+        this.treeRoot = treeRoot;
+        alignTree();
+    }
+
+    /**
+     * Get the tree view item that was selected.
+     *
+     * @return the selected item, or null if no item is selected
+     */
+    public final TTreeItem getSelected() {
+        return selectedItem;
+    }
+
+    /**
+     * Set the new selected tree view item.
+     *
+     * @param item new item that became selected
+     * @param centerWindow if true, move the window to put the selected into
+     * view
+     */
+    public void setSelected(final TTreeItem item, final boolean centerWindow) {
+        if (item != null) {
+            item.setSelected(true);
+        }
+        if ((selectedItem != null) && (selectedItem != item)) {
+            selectedItem.setSelected(false);
+        }
+        selectedItem = item;
+
+        if (centerWindow) {
+            int y = 0;
+            for (TWidget widget: getChildren()) {
+                if (widget == selectedItem) {
+                    break;
+                }
+                y++;
+            }
+            topLine = y - (getHeight() - 1)/2;
+            if (topLine > getChildren().size() - getHeight()) {
+                topLine = getChildren().size() - getHeight();
+            }
+            if (topLine < 0) {
+                topLine = 0;
+            }
+        }
+
+        if (selectedItem != null) {
+            activate(selectedItem);
+        }
+    }
+
+    /**
+     * Perform user selection action.
+     */
+    public void dispatch() {
+        if (action != null) {
+            action.DO();
+        }
+    }
+
+    /**
+     * Get the left column value.  0 is the leftmost column.
+     *
+     * @return the left column
+     */
+    public int getLeftColumn() {
+        return leftColumn;
+    }
+
+    /**
+     * Set the left column value.  0 is the leftmost column.
+     *
+     * @param leftColumn the new left column
+     */
+    public void setLeftColumn(final int leftColumn) {
+        this.leftColumn = leftColumn;
+    }
+
+    /**
+     * Get the top line (row) value.  0 is the topmost line.
+     *
+     * @return the top line
+     */
+    public int getTopLine() {
+        return topLine;
+    }
+
+    /**
+     * Set the top line value.  0 is the topmost line.
+     *
+     * @param topLine the new top line
+     */
+    public void setTopLine(final int topLine) {
+        this.topLine = topLine;
+    }
+
+    /**
+     * Get the total line (rows) count, based on the items that are visible
+     * and expanded.
+     *
+     * @return the line count
+     */
+    public int getTotalLineCount() {
+        if (treeRoot == null) {
+            return 0;
+        }
+        return getChildren().size();
+    }
+
+    /**
+     * Get the length of the widest item to display.
+     *
+     * @return the maximum number of columns for this item or its children
+     */
+    public int getMaximumColumn() {
+        if (treeRoot == null) {
+            return 0;
+        }
+        return treeRoot.getMaximumColumn();
+    }
+
+    /**
+     * Update the Y positions of all the children items to match the current
+     * topLine value.  Note package private access.
+     */
+    void alignTree() {
+        if (treeRoot == null) {
+            return;
+        }
+
+        // As we walk the list we also adjust next/previous pointers,
+        // resulting in a doubly-linked list but only of the expanded items.
+        TTreeItem p = null;
+
+        for (int i = 0; i < getChildren().size(); i++) {
+            TTreeItem item = (TTreeItem) getChildren().get(i);
+
+            if (p != null) {
+                item.keyboardPrevious = p;
+                p.keyboardNext = item;
+            }
+            p = item;
+
+            item.setY(i - topLine);
+            item.setWidth(getWidth());
+        }
+
+    }
+
+}
similarity index 58%
rename from src/jexer/TTreeView.java
rename to src/jexer/ttree/TTreeViewWidget.java
index b9b05bf0fd492dc0d5b83933a0736d52d7720c55..adb9a5d8e354b1974d70843e608944e0dd4b5c8b 100644 (file)
  * @author Kevin Lamonte [kevin.lamonte@gmail.com]
  * @version 1
  */
  * @author Kevin Lamonte [kevin.lamonte@gmail.com]
  * @version 1
  */
-package jexer;
-
+package jexer.ttree;
+
+import jexer.TAction;
+import jexer.THScroller;
+import jexer.TKeypress;
+import jexer.TScrollableWidget;
+import jexer.TVScroller;
+import jexer.TWidget;
 import jexer.event.TKeypressEvent;
 import jexer.event.TMouseEvent;
 import static jexer.TKeypress.*;
 
 /**
 import jexer.event.TKeypressEvent;
 import jexer.event.TMouseEvent;
 import static jexer.TKeypress.*;
 
 /**
- * TTreeView implements a simple tree view.
+ * TTreeViewWidget wraps a tree view with horizontal and vertical scrollbars.
  */
  */
-public class TTreeView extends TScrollableWidget {
-
-    /**
-     * Root of the tree.
-     */
-    private TTreeItem treeRoot;
-
-    /**
-     * Get the root of the tree.
-     *
-     * @return the root of the tree
-     */
-    public final TTreeItem getTreeRoot() {
-        return treeRoot;
-    }
-
-    /**
-     * Set the root of the tree.
-     *
-     * @param treeRoot the new root of the tree
-     */
-    public final void setTreeRoot(final TTreeItem treeRoot) {
-        this.treeRoot = treeRoot;
-    }
+public class TTreeViewWidget extends TScrollableWidget {
 
 
-    /**
-     * Maximum width of a single line.
-     */
-    private int maxLineWidth;
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
 
     /**
-     * Only one of my children can be selected.
+     * The TTreeView
      */
      */
-    private TTreeItem selectedItem = null;
+    private TTreeView treeView;
 
     /**
      * If true, move the window to put the selected item in view.  This
 
     /**
      * If true, move the window to put the selected item in view.  This
@@ -77,22 +59,13 @@ public class TTreeView extends TScrollableWidget {
     private boolean centerWindow = false;
 
     /**
     private boolean centerWindow = false;
 
     /**
-     * The action to perform when the user selects an item.
-     */
-    private TAction action = null;
-
-    /**
-     * Set treeRoot.
-     *
-     * @param treeRoot ultimate root of tree
-     * @param centerWindow if true, move the window to put the root in view
+     * Maximum width of a single line.
      */
      */
-    public void setTreeRoot(final TTreeItem treeRoot,
-        final boolean centerWindow) {
+    private int maxLineWidth;
 
 
-        this.treeRoot = treeRoot;
-        this.centerWindow = centerWindow;
-    }
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Public constructor.
 
     /**
      * Public constructor.
@@ -103,7 +76,7 @@ public class TTreeView extends TScrollableWidget {
      * @param width width of tree view
      * @param height height of tree view
      */
      * @param width width of tree view
      * @param height height of tree view
      */
-    public TTreeView(final TWidget parent, final int x, final int y,
+    public TTreeViewWidget(final TWidget parent, final int x, final int y,
         final int width, final int height) {
 
         this(parent, x, y, width, height, null);
         final int width, final int height) {
 
         this(parent, x, y, width, height, null);
@@ -119,174 +92,22 @@ public class TTreeView extends TScrollableWidget {
      * @param height height of tree view
      * @param action action to perform when an item is selected
      */
      * @param height height of tree view
      * @param action action to perform when an item is selected
      */
-    public TTreeView(final TWidget parent, final int x, final int y,
+    public TTreeViewWidget(final TWidget parent, final int x, final int y,
         final int width, final int height, final TAction action) {
 
         super(parent, x, y, width, height);
         final int width, final int height, final TAction action) {
 
         super(parent, x, y, width, height);
-        this.action = action;
+
+        treeView = new TTreeView(this, 0, 0, getWidth() - 1, getHeight() - 1,
+            action);
 
         vScroller = new TVScroller(this, getWidth() - 1, 0, getHeight() - 1);
         hScroller = new THScroller(this, 0, getHeight() - 1, getWidth() - 1);
 
         vScroller = new TVScroller(this, getWidth() - 1, 0, getHeight() - 1);
         hScroller = new THScroller(this, 0, getHeight() - 1, getWidth() - 1);
-    }
-
-    /**
-     * Get the tree view item that was selected.
-     *
-     * @return the selected item, or null if no item is selected
-     */
-    public final TTreeItem getSelected() {
-        return selectedItem;
-    }
-
-    /**
-     * Set the new selected tree view item.
-     *
-     * @param item new item that became selected
-     */
-    public void setSelected(final TTreeItem item) {
-        if (item != null) {
-            item.setSelected(true);
-        }
-        if ((selectedItem != null) && (selectedItem != item)) {
-            selectedItem.setSelected(false);
-        }
-        selectedItem = item;
-    }
-
-    /**
-     * Perform user selection action.
-     */
-    public void dispatch() {
-        if (action != null) {
-            action.DO();
-        }
-    }
 
 
-    /**
-     * Resize text and scrollbars for a new width/height.
-     */
-    @Override
-    public void reflowData() {
-        int selectedRow = 0;
-        boolean foundSelectedRow = false;
-
-        if (treeRoot == null) {
-            return;
-        }
-
-        // Make each child invisible/inactive to start, expandTree() will
-        // reactivate the visible ones.
-        for (TWidget widget: getChildren()) {
-            if (widget instanceof TTreeItem) {
-                TTreeItem item = (TTreeItem) widget;
-                item.setInvisible(true);
-                item.setEnabled(false);
-                item.keyboardPrevious = null;
-                item.keyboardNext = null;
-            }
-        }
-
-        // Expand the tree into a linear list
-        getChildren().clear();
-        getChildren().addAll(treeRoot.expandTree("", true));
-
-        // Locate the selected row and maximum line width
-        for (TWidget widget: getChildren()) {
-            TTreeItem item = (TTreeItem) widget;
-
-            if (item == selectedItem) {
-                foundSelectedRow = true;
-            }
-            if (!foundSelectedRow) {
-                selectedRow++;
-            }
-
-            int lineWidth = item.getText().length()
-                + item.getPrefix().length() + 4;
-            if (lineWidth > maxLineWidth) {
-                maxLineWidth = lineWidth;
-            }
-        }
-
-        if ((centerWindow) && (foundSelectedRow)) {
-            if ((selectedRow < getVerticalValue())
-                || (selectedRow > getVerticalValue() + getHeight() - 2)
-            ) {
-                setVerticalValue(selectedRow);
-                centerWindow = false;
-            }
-        }
-        updatePositions();
-
-        // Rescale the scroll bars
-        setBottomValue(getChildren().size() - getHeight() + 1);
-        if (getBottomValue() < 0) {
-            setBottomValue(0);
-        }
-        if (getVerticalValue() > getBottomValue()) {
-            setVerticalValue(getBottomValue());
-        }
-        setRightValue(maxLineWidth - getWidth() + 3);
-        if (getRightValue() < 0) {
-            setRightValue(0);
-        }
-        if (getHorizontalValue() > getRightValue()) {
-            setHorizontalValue(getRightValue());
-        }
-        getChildren().add(hScroller);
-        getChildren().add(vScroller);
     }
 
     }
 
-    /**
-     * Update the Y positions of all the children items.
-     */
-    private void updatePositions() {
-        if (treeRoot == null) {
-            return;
-        }
-
-        int begin = getVerticalValue();
-        int topY = 0;
-
-        // As we walk the list we also adjust next/previous pointers,
-        // resulting in a doubly-linked list but only of the expanded items.
-        TTreeItem p = null;
-
-        for (int i = 0; i < getChildren().size(); i++) {
-            if (!(getChildren().get(i) instanceof TTreeItem)) {
-                // Skip the scrollbars
-                continue;
-            }
-            TTreeItem item = (TTreeItem) getChildren().get(i);
-
-            if (p != null) {
-                item.keyboardPrevious = p;
-                p.keyboardNext = item;
-            }
-            p = item;
-
-            if (i < begin) {
-                // Render invisible
-                item.setEnabled(false);
-                item.setInvisible(true);
-                continue;
-            }
-
-            if (topY >= getHeight() - 1) {
-                // Render invisible
-                item.setEnabled(false);
-                item.setInvisible(true);
-                continue;
-            }
-
-            item.setY(topY);
-            item.setEnabled(true);
-            item.setInvisible(false);
-            item.setWidth(getWidth() - 1);
-            topY++;
-        }
-
-    }
+    // ------------------------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
+    // ------------------------------------------------------------------------
 
     /**
      * Handle mouse press events.
 
     /**
      * Handle mouse press events.
@@ -300,11 +121,13 @@ public class TTreeView extends TScrollableWidget {
         } else if (mouse.isMouseWheelDown()) {
             verticalIncrement();
         } else {
         } else if (mouse.isMouseWheelDown()) {
             verticalIncrement();
         } else {
-            // Pass to children
+            // Pass to the TreeView or scrollbars
             super.onMouseDown(mouse);
         }
 
             super.onMouseDown(mouse);
         }
 
-        // Update the screen after the scrollbars have moved
+        // Update the view to reflect the new scrollbar positions
+        treeView.setTopLine(getVerticalValue());
+        treeView.setLeftColumn(getHorizontalValue());
         reflowData();
     }
 
         reflowData();
     }
 
@@ -315,10 +138,28 @@ public class TTreeView extends TScrollableWidget {
      */
     @Override
     public void onMouseUp(final TMouseEvent mouse) {
      */
     @Override
     public void onMouseUp(final TMouseEvent mouse) {
-        // Pass to children
-        super.onMouseDown(mouse);
+        // Pass to the TreeView or scrollbars
+        super.onMouseUp(mouse);
+
+        // Update the view to reflect the new scrollbar positions
+        treeView.setTopLine(getVerticalValue());
+        treeView.setLeftColumn(getHorizontalValue());
+        reflowData();
+    }
+
+    /**
+     * Handle mouse motion events.
+     *
+     * @param mouse mouse motion event
+     */
+    @Override
+    public void onMouseMotion(final TMouseEvent mouse) {
+        // Pass to the TreeView or scrollbars
+        super.onMouseMotion(mouse);
 
 
-        // Update the screen after any thing has expanded/contracted
+        // Update the view to reflect the new scrollbar positions
+        treeView.setTopLine(getVerticalValue());
+        treeView.setLeftColumn(getHorizontalValue());
         reflowData();
     }
 
         reflowData();
     }
 
@@ -359,36 +200,29 @@ public class TTreeView extends TScrollableWidget {
             || keypress.equals(kbAltPgDn)
         ) {
             bigVerticalIncrement();
             || keypress.equals(kbAltPgDn)
         ) {
             bigVerticalIncrement();
-        } else if (keypress.equals(kbHome)) {
-            toTop();
-        } else if (keypress.equals(kbEnd)) {
-            toBottom();
-        } else if (keypress.equals(kbEnter)) {
-            if (selectedItem != null) {
-                dispatch();
+        } else if (keypress.equals(kbPgDn)) {
+            for (int i = 0; i < getHeight() - 2; i++) {
+                treeView.onKeypress(new TKeypressEvent(TKeypress.kbDown));
             }
             }
-        } else if (keypress.equals(kbUp)) {
-            // Select the previous item
-            if (selectedItem != null) {
-                TTreeItem oldItem = selectedItem;
-                if (selectedItem.keyboardPrevious != null) {
-                    setSelected(selectedItem.keyboardPrevious);
-                    if (oldItem.getY() == 0) {
-                        verticalDecrement();
-                    }
-                }
-            }
-        } else if (keypress.equals(kbDown)) {
-            // Select the next item
-            if (selectedItem != null) {
-                TTreeItem oldItem = selectedItem;
-                if (selectedItem.keyboardNext != null) {
-                    setSelected(selectedItem.keyboardNext);
-                    if (oldItem.getY() == getHeight() - 2) {
-                        verticalIncrement();
-                    }
-                }
+            reflowData();
+            return;
+        } else if (keypress.equals(kbPgUp)) {
+            for (int i = 0; i < getHeight() - 2; i++) {
+                treeView.onKeypress(new TKeypressEvent(TKeypress.kbUp));
             }
             }
+            reflowData();
+            return;
+        } else if (keypress.equals(kbHome)) {
+            treeView.setSelected((TTreeItem) treeView.getChildren().get(0),
+                false);
+            treeView.setTopLine(0);
+            reflowData();
+            return;
+        } else if (keypress.equals(kbEnd)) {
+            treeView.setSelected((TTreeItem)  treeView.getChildren().get(
+                treeView.getChildren().size() - 1), true);
+            reflowData();
+            return;
         } else if (keypress.equals(kbTab)) {
             getParent().switchWidget(true);
             return;
         } else if (keypress.equals(kbTab)) {
             getParent().switchWidget(true);
             return;
@@ -396,17 +230,157 @@ public class TTreeView extends TScrollableWidget {
                 || keypress.equals(kbBackTab)) {
             getParent().switchWidget(false);
             return;
                 || keypress.equals(kbBackTab)) {
             getParent().switchWidget(false);
             return;
-        } else if (selectedItem != null) {
-            // Give the TTreeItem a chance to handle arrow keys
-            selectedItem.onKeypress(keypress);
         } else {
         } else {
-            // Pass other keys (tab etc.) on to TWidget's handler.
-            super.onKeypress(keypress);
+            treeView.onKeypress(keypress);
+
+            // Update the scrollbars to reflect the new data position
+            reflowData();
             return;
         }
 
             return;
         }
 
-        // Update the screen after any thing has expanded/contracted
+        // Update the view to reflect the new scrollbar position
+        treeView.setTopLine(getVerticalValue());
+        treeView.setLeftColumn(getHorizontalValue());
         reflowData();
     }
 
         reflowData();
     }
 
+    // ------------------------------------------------------------------------
+    // TScrollableWidget ------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Resize text and scrollbars for a new width/height.
+     */
+    @Override
+    public void reflowData() {
+        int selectedRow = 0;
+        boolean foundSelectedRow = false;
+
+        // Reset the keyboard list, expandTree() will recreate it.
+        for (TWidget widget: treeView.getChildren()) {
+            TTreeItem item = (TTreeItem) widget;
+            item.keyboardPrevious = null;
+            item.keyboardNext = null;
+        }
+
+        // Expand the tree into a linear list
+        treeView.getChildren().clear();
+        treeView.getChildren().addAll(treeView.getTreeRoot().expandTree("",
+                true));
+
+        // Locate the selected row and maximum line width
+        for (TWidget widget: treeView.getChildren()) {
+            TTreeItem item = (TTreeItem) widget;
+
+            if (item == treeView.getSelected()) {
+                foundSelectedRow = true;
+            }
+            if (!foundSelectedRow) {
+                selectedRow++;
+            }
+
+            int lineWidth = item.getText().length()
+                + item.getPrefix().length() + 4;
+            if (lineWidth > maxLineWidth) {
+                maxLineWidth = lineWidth;
+            }
+        }
+
+        if ((centerWindow) && (foundSelectedRow)) {
+            if ((selectedRow < getVerticalValue())
+                || (selectedRow > getVerticalValue() + getHeight() - 2)
+            ) {
+                treeView.setTopLine(selectedRow);
+                centerWindow = false;
+            }
+        }
+        treeView.alignTree();
+
+        // Rescale the scroll bars
+        setVerticalValue(treeView.getTopLine());
+        setBottomValue(treeView.getTotalLineCount() - (getHeight() - 1));
+        if (getBottomValue() < getTopValue()) {
+            setBottomValue(getTopValue());
+        }
+        if (getVerticalValue() > getBottomValue()) {
+            setVerticalValue(getBottomValue());
+        }
+        setRightValue(maxLineWidth - 2);
+        if (getHorizontalValue() > getRightValue()) {
+            setHorizontalValue(getRightValue());
+        }
+
+    }
+
+    // ------------------------------------------------------------------------
+    // TTreeView --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Get the underlying TTreeView.
+     *
+     * @return the TTreeView
+     */
+    public TTreeView getTreeView() {
+        return treeView;
+    }
+
+    /**
+     * Get the root of the tree.
+     *
+     * @return the root of the tree
+     */
+    public final TTreeItem getTreeRoot() {
+        return treeView.getTreeRoot();
+    }
+
+    /**
+     * Set the root of the tree.
+     *
+     * @param treeRoot the new root of the tree
+     */
+    public final void setTreeRoot(final TTreeItem treeRoot) {
+        treeView.setTreeRoot(treeRoot);
+    }
+
+    /**
+     * Set treeRoot.
+     *
+     * @param treeRoot ultimate root of tree
+     * @param centerWindow if true, move the window to put the root in view
+     */
+    public void setTreeRoot(final TTreeItem treeRoot,
+        final boolean centerWindow) {
+
+        treeView.setTreeRoot(treeRoot);
+        this.centerWindow = centerWindow;
+    }
+
+    /**
+     * Get the tree view item that was selected.
+     *
+     * @return the selected item, or null if no item is selected
+     */
+    public final TTreeItem getSelected() {
+        return treeView.getSelected();
+    }
+
+    /**
+     * Set the new selected tree view item.
+     *
+     * @param item new item that became selected
+     * @param centerWindow if true, move the window to put the selected into
+     * view
+     */
+    public void setSelected(final TTreeItem item, final boolean centerWindow) {
+        treeView.setSelected(item, centerWindow);
+    }
+
+    /**
+     * Perform user selection action.
+     */
+    public void dispatch() {
+        treeView.dispatch();
+    }
+
 }
 }
diff --git a/src/jexer/ttree/TTreeViewWindow.java b/src/jexer/ttree/TTreeViewWindow.java
new file mode 100644 (file)
index 0000000..2adf616
--- /dev/null
@@ -0,0 +1,400 @@
+/*
+ * Jexer - Java Text User Interface
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (C) 2017 Kevin Lamonte
+ *
+ * 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:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * 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
+ */
+package jexer.ttree;
+
+import jexer.TAction;
+import jexer.TApplication;
+import jexer.THScroller;
+import jexer.TScrollableWindow;
+import jexer.TVScroller;
+import jexer.TWidget;
+import jexer.event.TKeypressEvent;
+import jexer.event.TMouseEvent;
+import jexer.event.TResizeEvent;
+import static jexer.TKeypress.*;
+
+/**
+ * TTreeViewWindow wraps a tree view with horizontal and vertical scrollbars
+ * in a standalone window.
+ */
+public class TTreeViewWindow extends TScrollableWindow {
+
+    // ------------------------------------------------------------------------
+    // Variables --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * The TTreeView
+     */
+    private TTreeView treeView;
+
+    /**
+     * If true, move the window to put the selected item in view.  This
+     * normally only happens once after setting treeRoot.
+     */
+    private boolean centerWindow = false;
+
+    /**
+     * Maximum width of a single line.
+     */
+    private int maxLineWidth;
+
+    // ------------------------------------------------------------------------
+    // Constructors -----------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Public constructor.
+     *
+     * @param parent the main application
+     * @param title the window title
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param width width of tree view
+     * @param flags bitmask of RESIZABLE, CENTERED, or MODAL
+     * @param height height of tree view
+     */
+    public TTreeViewWindow(final TApplication parent, final String title,
+        final int x, final int y, final int width, final int height,
+        final int flags) {
+
+        this(parent, title, x, y, width, height, flags, null);
+    }
+
+    /**
+     * Public constructor.
+     *
+     * @param parent the main application
+     * @param title the window title
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param width width of tree view
+     * @param height height of tree view
+     * @param flags bitmask of RESIZABLE, CENTERED, or MODAL
+     * @param action action to perform when an item is selected
+     */
+    public TTreeViewWindow(final TApplication parent, final String title,
+        final int x, final int y, final int width, final int height,
+        final int flags, final TAction action) {
+
+        super(parent, title, x, y, width, height, flags);
+
+        treeView = new TTreeView(this, 0, 0, getWidth() - 2, getHeight() - 2,
+            action);
+
+        hScroller = new THScroller(this, 17, getHeight() - 2, getWidth() - 20);
+        vScroller = new TVScroller(this, getWidth() - 2, 0, getHeight() - 2);
+
+        /*
+        System.err.println("TTreeViewWindow()");
+        for (TWidget w: getChildren()) {
+            System.err.println("    " + w + " " + w.isActive());
+        }
+        */
+    }
+
+    // ------------------------------------------------------------------------
+    // Event handlers ---------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Handle mouse press events.
+     *
+     * @param mouse mouse button press event
+     */
+    @Override
+    public void onMouseDown(final TMouseEvent mouse) {
+        if (mouse.isMouseWheelUp()) {
+            verticalDecrement();
+        } else if (mouse.isMouseWheelDown()) {
+            verticalIncrement();
+        } else {
+            // Pass to the TreeView or scrollbars
+            super.onMouseDown(mouse);
+        }
+
+        // Update the view to reflect the new scrollbar positions
+        treeView.setTopLine(getVerticalValue());
+        treeView.setLeftColumn(getHorizontalValue());
+        reflowData();
+    }
+
+    /**
+     * Handle mouse release events.
+     *
+     * @param mouse mouse button release event
+     */
+    @Override
+    public void onMouseUp(final TMouseEvent mouse) {
+        // Pass to the TreeView or scrollbars
+        super.onMouseUp(mouse);
+
+        // Update the view to reflect the new scrollbar positions
+        treeView.setTopLine(getVerticalValue());
+        treeView.setLeftColumn(getHorizontalValue());
+        reflowData();
+    }
+
+    /**
+     * Handle mouse motion events.
+     *
+     * @param mouse mouse motion event
+     */
+    @Override
+    public void onMouseMotion(final TMouseEvent mouse) {
+        // Pass to the TreeView or scrollbars
+        super.onMouseMotion(mouse);
+
+        // Update the view to reflect the new scrollbar positions
+        treeView.setTopLine(getVerticalValue());
+        treeView.setLeftColumn(getHorizontalValue());
+        reflowData();
+    }
+
+    /**
+     * Handle keystrokes.
+     *
+     * @param keypress keystroke event
+     */
+    @Override
+    public void onKeypress(final TKeypressEvent keypress) {
+        if (inKeyboardResize) {
+            // Let TWindow do its job.
+            super.onKeypress(keypress);
+            return;
+        }
+
+        if (keypress.equals(kbShiftLeft)
+            || keypress.equals(kbCtrlLeft)
+            || keypress.equals(kbAltLeft)
+        ) {
+            horizontalDecrement();
+        } else if (keypress.equals(kbShiftRight)
+            || keypress.equals(kbCtrlRight)
+            || keypress.equals(kbAltRight)
+        ) {
+            horizontalIncrement();
+        } else if (keypress.equals(kbShiftUp)
+            || keypress.equals(kbCtrlUp)
+            || keypress.equals(kbAltUp)
+        ) {
+            verticalDecrement();
+        } else if (keypress.equals(kbShiftDown)
+            || keypress.equals(kbCtrlDown)
+            || keypress.equals(kbAltDown)
+        ) {
+            verticalIncrement();
+        } else if (keypress.equals(kbShiftPgUp)
+            || keypress.equals(kbCtrlPgUp)
+            || keypress.equals(kbAltPgUp)
+        ) {
+            bigVerticalDecrement();
+        } else if (keypress.equals(kbShiftPgDn)
+            || keypress.equals(kbCtrlPgDn)
+            || keypress.equals(kbAltPgDn)
+        ) {
+            bigVerticalIncrement();
+        } else {
+            treeView.onKeypress(keypress);
+
+            // Update the scrollbars to reflect the new data position
+            reflowData();
+            return;
+        }
+
+        // Update the view to reflect the new scrollbar position
+        treeView.setTopLine(getVerticalValue());
+        treeView.setLeftColumn(getHorizontalValue());
+        reflowData();
+    }
+
+    // ------------------------------------------------------------------------
+    // TScrollableWindow ------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Handle window/screen resize events.
+     *
+     * @param resize resize event
+     */
+    @Override
+    public void onResize(final TResizeEvent resize) {
+        if (resize.getType() == TResizeEvent.Type.WIDGET) {
+            // Resize the treeView field.
+            TResizeEvent treeSize = new TResizeEvent(TResizeEvent.Type.WIDGET,
+                resize.getWidth() - 2, resize.getHeight() - 2);
+            treeView.onResize(treeSize);
+
+            // Have TScrollableWindow handle the scrollbars.
+            super.onResize(resize);
+
+            // Now re-center the treeView field.
+            if (treeView.getSelected() != null) {
+                treeView.setSelected(treeView.getSelected(), true);
+            }
+            reflowData();
+            return;
+        }
+    }
+
+    /**
+     * Resize text and scrollbars for a new width/height.
+     */
+    @Override
+    public void reflowData() {
+        int selectedRow = 0;
+        boolean foundSelectedRow = false;
+
+        // Reset the keyboard list, expandTree() will recreate it.
+        for (TWidget widget: treeView.getChildren()) {
+            TTreeItem item = (TTreeItem) widget;
+            item.keyboardPrevious = null;
+            item.keyboardNext = null;
+        }
+
+        // Expand the tree into a linear list
+        treeView.getChildren().clear();
+        treeView.getChildren().addAll(treeView.getTreeRoot().expandTree("",
+                true));
+
+        // Locate the selected row and maximum line width
+        for (TWidget widget: treeView.getChildren()) {
+            TTreeItem item = (TTreeItem) widget;
+
+            if (item == treeView.getSelected()) {
+                foundSelectedRow = true;
+            }
+            if (!foundSelectedRow) {
+                selectedRow++;
+            }
+
+            int lineWidth = item.getText().length()
+                + item.getPrefix().length() + 4;
+            if (lineWidth > maxLineWidth) {
+                maxLineWidth = lineWidth;
+            }
+        }
+
+        if ((centerWindow) && (foundSelectedRow)) {
+            if ((selectedRow < getVerticalValue())
+                || (selectedRow > getVerticalValue() + getHeight() - 3)
+            ) {
+                treeView.setTopLine(selectedRow);
+                centerWindow = false;
+            }
+        }
+        treeView.alignTree();
+
+        // Rescale the scroll bars
+        setVerticalValue(treeView.getTopLine());
+        setBottomValue(treeView.getTotalLineCount() - (getHeight() - 2));
+        if (getBottomValue() < getTopValue()) {
+            setBottomValue(getTopValue());
+        }
+        if (getVerticalValue() > getBottomValue()) {
+            setVerticalValue(getBottomValue());
+        }
+        setRightValue(maxLineWidth - 4);
+        if (getHorizontalValue() > getRightValue()) {
+            setHorizontalValue(getRightValue());
+        }
+    }
+
+    // ------------------------------------------------------------------------
+    // TTreeView --------------------------------------------------------------
+    // ------------------------------------------------------------------------
+
+    /**
+     * Get the underlying TTreeView.
+     *
+     * @return the TTreeView
+     */
+    public TTreeView getTreeView() {
+        return treeView;
+    }
+
+    /**
+     * Get the root of the tree.
+     *
+     * @return the root of the tree
+     */
+    public final TTreeItem getTreeRoot() {
+        return treeView.getTreeRoot();
+    }
+
+    /**
+     * Set the root of the tree.
+     *
+     * @param treeRoot the new root of the tree
+     */
+    public final void setTreeRoot(final TTreeItem treeRoot) {
+        treeView.setTreeRoot(treeRoot);
+    }
+
+    /**
+     * Set treeRoot.
+     *
+     * @param treeRoot ultimate root of tree
+     * @param centerWindow if true, move the window to put the root in view
+     */
+    public void setTreeRoot(final TTreeItem treeRoot,
+        final boolean centerWindow) {
+
+        treeView.setTreeRoot(treeRoot);
+        this.centerWindow = centerWindow;
+    }
+
+    /**
+     * Get the tree view item that was selected.
+     *
+     * @return the selected item, or null if no item is selected
+     */
+    public final TTreeItem getSelected() {
+        return treeView.getSelected();
+    }
+
+    /**
+     * Set the new selected tree view item.
+     *
+     * @param item new item that became selected
+     * @param centerWindow if true, move the window to put the selected into
+     * view
+     */
+    public void setSelected(final TTreeItem item, final boolean centerWindow) {
+        treeView.setSelected(item, centerWindow);
+    }
+
+    /**
+     * Perform user selection action.
+     */
+    public void dispatch() {
+        treeView.dispatch();
+    }
+
+}
diff --git a/src/jexer/ttree/package-info.java b/src/jexer/ttree/package-info.java
new file mode 100644 (file)
index 0000000..72dc8da
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Jexer - Java Text User Interface
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (C) 2017 Kevin Lamonte
+ *
+ * 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:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * 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
+ */
+
+/**
+ * TTreeView and supporting classes.
+ */
+package jexer.ttree;