clean up threads and timers
authorKevin Lamonte <kevin.lamonte@gmail.com>
Fri, 20 Mar 2015 02:51:44 +0000 (22:51 -0400)
committerKevin Lamonte <kevin.lamonte@gmail.com>
Fri, 20 Mar 2015 02:51:44 +0000 (22:51 -0400)
15 files changed:
README.md
src/jexer/TApplication.java
src/jexer/TButton.java
src/jexer/TTerminalWindow.java
src/jexer/TTimer.java
src/jexer/TWidget.java
src/jexer/TWindow.java
src/jexer/backend/AWTBackend.java
src/jexer/backend/Backend.java
src/jexer/backend/ECMA48Backend.java
src/jexer/demos/Demo1.java
src/jexer/io/AWTTerminal.java
src/jexer/io/ECMA48Terminal.java
src/jexer/io/Screen.java
src/jexer/menu/TMenu.java

index c2946c69e8b8a6fe1f0756ff49bff73c35782259..8d8ea20d625c2a168b1695498dfcbfc813ba5bcd 100644 (file)
--- a/README.md
+++ b/README.md
@@ -75,6 +75,15 @@ See the file demos/Demo1.java for detailed examples.
 
 
 
+Known Issues / Arbitrary Decisions
+----------------------------------
+
+Some arbitrary design decisions had to be made when either the
+obviously expected behavior did not happen or when a specification was
+ambiguous.  This section describes such issues.
+
+
+
 Roadmap
 -------
 
@@ -82,7 +91,6 @@ Many tasks remain before calling this version 1.0:
 
 0.0.2:
 
-- Fix clipping errors
 - AWT:
   - Blinking cursor
 - ECMA48Backend running on socket
@@ -131,3 +139,8 @@ Wishlist features (2.0):
   - TText
   - TTerminal
   - TComboBox
+
+
+Screenshots
+-----------
+
index 9d845be18affeecbd94a91ad2939a9ad8da4fa2c..e710895b555c6d645f21cc06870a4e25afff0a7a 100644 (file)
@@ -120,12 +120,33 @@ public class TApplication {
                                 break;
                             }
                         }
-                        synchronized (application) {
-                            application.wait();
+
+                        synchronized (this) {
+                            /*
+                            System.err.printf("%s %s sleep\n", this, primary ?
+                                "primary" : "secondary");
+                             */
+
+                            this.wait();
+
+                            /*
+                            System.err.printf("%s %s AWAKE\n", this, primary ?
+                                "primary" : "secondary");
+                             */
+
                             if ((!primary)
                                 && (application.secondaryEventReceiver == null)
                             ) {
-                                // Secondary thread, time to exit
+                                // Secondary thread, emergency exit.  If we
+                                // got here then something went wrong with
+                                // the handoff between yield() and
+                                // closeWindow().
+
+                                System.err.printf("secondary exiting at wrong time, why?\n");
+                                synchronized (application.primaryEventHandler) {
+                                    application.primaryEventHandler.notify();
+                                }
+                                application.secondaryEventHandler = null;
                                 return;
                             }
                             break;
@@ -153,6 +174,7 @@ public class TApplication {
                     } else {
                         secondaryHandleEvent(event);
                     }
+                    application.repaint = true;
                     if ((!primary)
                         && (application.secondaryEventReceiver == null)
                     ) {
@@ -160,7 +182,15 @@ public class TApplication {
 
                         // DO NOT UNLOCK.  Primary thread just came back from
                         // primaryHandleEvent() and will unlock in the else
-                        // block below.
+                        // block below.  Just wake it up.
+                        synchronized (application.primaryEventHandler) {
+                            application.primaryEventHandler.notify();
+                        }
+                        // Now eliminate my reference so that
+                        // wakeEventHandler() resumes working on the primary.
+                        application.secondaryEventHandler = null;
+
+                        // All done!
                         return;
                     } else {
                         // Unlock.  Either I am primary thread, or I am
@@ -168,7 +198,14 @@ public class TApplication {
                         oldLock = unlockHandleEvent();
                         assert (oldLock == true);
                     }
+                } // for (;;)
+
+                // I have done some work of some kind.  Tell the main run()
+                // loop to wake up now.
+                synchronized (application) {
+                    application.notify();
                 }
+
             } // while (true) (main runnable loop)
         }
     }
@@ -176,17 +213,17 @@ public class TApplication {
     /**
      * The primary event handler thread.
      */
-    private WidgetEventHandler primaryEventHandler;
+    private volatile WidgetEventHandler primaryEventHandler;
 
     /**
      * The secondary event handler thread.
      */
-    private WidgetEventHandler secondaryEventHandler;
+    private volatile WidgetEventHandler secondaryEventHandler;
 
     /**
      * The widget receiving events from the secondary event handler thread.
      */
-    private TWidget secondaryEventReceiver;
+    private volatile TWidget secondaryEventReceiver;
 
     /**
      * Spinlock for the primary and secondary event handlers.
@@ -194,6 +231,22 @@ public class TApplication {
      */
     private volatile boolean insideHandleEvent = false;
 
+    /**
+     * Wake the sleeping active event handler.
+     */
+    private void wakeEventHandler() {
+        if (secondaryEventHandler != null) {
+            synchronized (secondaryEventHandler) {
+                secondaryEventHandler.notify();
+            }
+        } else {
+            assert (primaryEventHandler != null);
+            synchronized (primaryEventHandler) {
+                primaryEventHandler.notify();
+            }
+        }
+    }
+
     /**
      * Set the insideHandleEvent flag to true.  lockoutEventHandlers() will
      * spin indefinitely until unlockHandleEvent() is called.
@@ -360,22 +413,16 @@ public class TApplication {
     /**
      * When true, exit the application.
      */
-    private boolean quit = false;
+    private volatile boolean quit = false;
 
     /**
      * When true, repaint the entire screen.
      */
-    private boolean repaint = true;
-
-    /**
-     * Request full repaint on next screen refresh.
-     */
-    public final void setRepaint() {
-        repaint = true;
-    }
+    private volatile boolean repaint = true;
 
     /**
-     * When true, just flush updates from the screen.
+     * When true, just flush updates from the screen.  This is only used to
+     * minimize physical writes for the mouse cursor.
      */
     private boolean flush = false;
 
@@ -441,9 +488,9 @@ public class TApplication {
 
 
         if (useAWT) {
-            backend     = new AWTBackend();
+            backend     = new AWTBackend(this);
         } else {
-            backend     = new ECMA48Backend(input, output);
+            backend     = new ECMA48Backend(this, input, output);
         }
         theme           = new ColorTheme();
         desktopBottom   = getScreen().getHeight() - 1;
@@ -489,10 +536,6 @@ public class TApplication {
             return;
         }
 
-        if (!repaint) {
-            return;
-        }
-
         if (debugThreads) {
             System.err.printf("drawAll() REDRAW\n");
         }
@@ -587,24 +630,48 @@ public class TApplication {
         while (!quit) {
             // Timeout is in milliseconds, so default timeout after 1 second
             // of inactivity.
-            int timeout = getSleepTime(1000);
-
-            // See if there are any definitely events waiting to be processed
-            // or a screen redraw to do.  If so, do not wait if there is no
-            // I/O coming in.
-            synchronized (drainEventQueue) {
-                if (drainEventQueue.size() > 0) {
-                    timeout = 0;
+            long timeout = 0;
+
+            // If I've got no updates to render, wait for something from the
+            // backend or a timer.
+            if (!repaint && !flush) {
+                // Never sleep longer than 100 millis, to get windows with
+                // background tasks an opportunity to update the display.
+                timeout = getSleepTime(100);
+
+                // See if there are any definitely events waiting to be
+                // processed.  If so, do not wait -- either there is I/O
+                // coming in or the primary/secondary threads are still
+                // working.
+                synchronized (drainEventQueue) {
+                    if (drainEventQueue.size() > 0) {
+                        timeout = 0;
+                    }
+                }
+                synchronized (fillEventQueue) {
+                    if (fillEventQueue.size() > 0) {
+                        timeout = 0;
+                    }
                 }
             }
-            synchronized (fillEventQueue) {
-                if (fillEventQueue.size() > 0) {
-                    timeout = 0;
+
+            if (timeout > 0) {
+                // As of now, I've got nothing to do: no I/O, nothing from
+                // the consumer threads, no timers that need to run ASAP.  So
+                // wait until either the backend or the consumer threads have
+                // something to do.
+                try {
+                    synchronized (this) {
+                        this.wait(timeout);
+                    }
+                } catch (InterruptedException e) {
+                    // I'm awake and don't care why, let's see what's going
+                    // on out there.
                 }
             }
 
             // Pull any pending I/O events
-            backend.getEvents(fillEventQueue, timeout);
+            backend.getEvents(fillEventQueue);
 
             // Dispatch each event to the appropriate handler, one at a time.
             for (;;) {
@@ -618,6 +685,13 @@ public class TApplication {
                 metaHandleEvent(event);
             }
 
+            // Wake a consumer thread if we have any pending events.
+            synchronized (drainEventQueue) {
+                if (drainEventQueue.size() > 0) {
+                    wakeEventHandler();
+                }
+            }
+
             // Prevent stepping on the primary or secondary event handler.
             stopEventHandlers();
 
@@ -631,14 +705,28 @@ public class TApplication {
 
             // Let the event handlers run again.
             startEventHandlers();
-        }
 
-        // Shutdown the consumer threads
-        synchronized (this) {
-            this.notifyAll();
+        } // while (!quit)
+
+        // Shutdown the event consumer threads
+        if (secondaryEventHandler != null) {
+            synchronized (secondaryEventHandler) {
+                secondaryEventHandler.notify();
+            }
+        }
+        if (primaryEventHandler != null) {
+            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();
+
     }
 
     /**
@@ -697,13 +785,6 @@ public class TApplication {
         synchronized (drainEventQueue) {
             drainEventQueue.add(event);
         }
-
-        // Wake all threads: primary thread will either be consuming events
-        // again or waiting in yield(), and secondary thread will either not
-        // exist or consuming events.
-        synchronized (this) {
-            this.notifyAll();
-        }
     }
 
     /**
@@ -861,15 +942,19 @@ public class TApplication {
         boolean oldLock = unlockHandleEvent();
         assert (oldLock == true);
 
+        // System.err.printf("YIELD\n");
+
         while (secondaryEventReceiver != null) {
-            synchronized (this) {
+            synchronized (primaryEventHandler) {
                 try {
-                    this.wait();
+                    primaryEventHandler.wait();
                 } catch (InterruptedException e) {
                     // SQUASH
                 }
             }
         }
+
+        // System.err.printf("EXIT YIELD\n");
     }
 
     /**
@@ -884,7 +969,7 @@ public class TApplication {
         Date now = new Date();
         List<TTimer> keepTimers = new LinkedList<TTimer>();
         for (TTimer timer: timers) {
-            if (timer.getNextTick().getTime() < now.getTime()) {
+            if (timer.getNextTick().getTime() <= now.getTime()) {
                 timer.tick();
                 if (timer.recurring) {
                     keepTimers.add(timer);
@@ -904,24 +989,27 @@ public class TApplication {
     /**
      * Get the amount of time I can sleep before missing a Timer tick.
      *
-     * @param timeout = initial (maximum) timeout
+     * @param timeout = initial (maximum) timeout in millis
      * @return number of milliseconds between now and the next timer event
      */
-    protected int getSleepTime(final int timeout) {
+    protected long getSleepTime(final long timeout) {
         Date now = new Date();
+        long nowTime = now.getTime();
         long sleepTime = timeout;
         for (TTimer timer: timers) {
-            if (timer.getNextTick().getTime() < now.getTime()) {
+            long nextTickTime = timer.getNextTick().getTime();
+            if (nextTickTime < nowTime) {
                 return 0;
             }
-            if ((timer.getNextTick().getTime() > now.getTime())
-                && ((timer.getNextTick().getTime() - now.getTime()) < sleepTime)
-            ) {
-                sleepTime = timer.getNextTick().getTime() - now.getTime();
+
+            long timeDifference = nextTickTime - nowTime;
+            if (timeDifference < sleepTime) {
+                sleepTime = timeDifference;
             }
         }
         assert (sleepTime >= 0);
-        return (int)sleepTime;
+        assert (sleepTime <= timeout);
+        return sleepTime;
     }
 
     /**
@@ -963,10 +1051,10 @@ public class TApplication {
             // window is closed.
             secondaryEventReceiver = null;
 
-            // Wake all threads: primary thread will be consuming events
-            // again, and secondary thread will exit.
-            synchronized (this) {
-                this.notifyAll();
+            // Wake the secondary thread, it will wake the primary as it
+            // exits.
+            synchronized (secondaryEventHandler) {
+                secondaryEventHandler.notify();
             }
         }
     }
index 24ee94c1abb9c0ff942ddeb90ef1a2c232cc356c..5e5edd03ca37a7a67285df1efd78bd810ede21c8 100644 (file)
@@ -51,6 +51,15 @@ public final class TButton extends TWidget {
      */
     private MnemonicString mnemonic;
 
+    /**
+     * Get the mnemonic string for this button.
+     *
+     * @return mnemonic string
+     */
+    public final MnemonicString getMnemonic() {
+        return mnemonic;
+    }
+
     /**
      * Remember mouse state.
      */
index 83cc86ef19d0f5a890699d41ec102d86de8dca50..58e60e567c15387ffd82379f696af8e1f303ff5a 100644 (file)
@@ -297,7 +297,7 @@ public class TTerminalWindow extends TWindow {
                     // The shell is still running, do nothing.
                 }
             }
-            
+
         } // synchronized (emulator)
     }
 
@@ -386,6 +386,15 @@ public class TTerminalWindow extends TWindow {
                 // Get out of scrollback
                 vScroller.setValue(0);
                 emulator.keypress(keypress.getKey());
+
+                // UGLY HACK TIME!  cmd.exe needs CRLF, not just CR, so if
+                // this is kBEnter then also send kbCtrlJ.
+                if (System.getProperty("os.name").startsWith("Windows")) {
+                    if (keypress.equals(kbEnter)) {
+                        emulator.keypress(kbCtrlJ);
+                    }
+                }
+
                 readEmulatorState();
                 return;
             }
index 0e711b967ec50ab6c1d4ac3fe612aa6573a3a7aa..442a55cc6ffbc7173ce8a29f58e7e5e7d7412e9f 100644 (file)
@@ -81,15 +81,6 @@ public final class TTimer {
         }
     }
 
-    /**
-     * Get the number of milliseconds between now and the next tick time.
-     *
-     * @return number of millis
-     */
-    public long getMillis() {
-        return nextTick.getTime() - (new Date()).getTime();
-    }
-
     /**
      * Package private constructor.
      *
@@ -103,7 +94,8 @@ public final class TTimer {
         this.duration  = duration;
         this.action    = action;
 
-        nextTick = new Date((new Date()).getTime() + duration);
+        Date now = new Date();
+        nextTick = new Date(now.getTime() + duration);
     }
 
 }
index d6864db746316659ef5d3ed4fed3cc76b0934d6a..6cb0f98b1fe4c6634334c13f081a07fbd78a7e0e 100644 (file)
@@ -86,13 +86,6 @@ public abstract class TWidget implements Comparable<TWidget> {
         this.height = height;
     }
 
-    /**
-     * Request full repaint on next screen refresh.
-     */
-    protected final void setRepaint() {
-        window.getApplication().setRepaint();
-    }
-
     /**
      * Get this TWidget's parent TApplication.
      *
@@ -487,14 +480,14 @@ public abstract class TWidget implements Comparable<TWidget> {
     public final void drawChildren() {
         // Set my clipping rectangle
         assert (window != null);
-        assert (window.getScreen() != null);
-        Screen screen = window.getScreen();
+        assert (getScreen() != null);
+        Screen screen = getScreen();
 
         screen.setClipRight(width);
         screen.setClipBottom(height);
 
-        int absoluteRightEdge = window.getAbsoluteX() + screen.getWidth();
-        int absoluteBottomEdge = window.getAbsoluteY() + screen.getHeight();
+        int absoluteRightEdge = window.getAbsoluteX() + window.getWidth();
+        int absoluteBottomEdge = window.getAbsoluteY() + window.getHeight();
         if (!(this instanceof TWindow) && !(this instanceof TVScroller)) {
             absoluteRightEdge -= 1;
         }
@@ -508,14 +501,14 @@ public abstract class TWidget implements Comparable<TWidget> {
             screen.setClipRight(0);
         } else if (myRightEdge > absoluteRightEdge) {
             screen.setClipRight(screen.getClipRight()
-                - myRightEdge - absoluteRightEdge);
+                - (myRightEdge - absoluteRightEdge));
         }
         if (getAbsoluteY() > absoluteBottomEdge) {
             // I am offscreen
             screen.setClipBottom(0);
         } else if (myBottomEdge > absoluteBottomEdge) {
             screen.setClipBottom(screen.getClipBottom()
-                - myBottomEdge - absoluteBottomEdge);
+                - (myBottomEdge - absoluteBottomEdge));
         }
 
         // Set my offset
@@ -727,9 +720,6 @@ public abstract class TWidget implements Comparable<TWidget> {
         activeChild.active = false;
         children.get(tabOrder).active = true;
         activeChild = children.get(tabOrder);
-
-        // Refresh
-        window.getApplication().setRepaint();
     }
 
     /**
@@ -789,27 +779,25 @@ public abstract class TWidget implements Comparable<TWidget> {
         // 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) {
-            /*
-            TODO
-
-            if (TButton button = cast(TButton)w) {
-                if (button.enabled &&
-                    !keypress.key.isKey &&
-                    keypress.key.alt &&
-                    !keypress.key.ctrl &&
-                    (toLowercase(button.mnemonic.shortcut) == toLowercase(keypress.key.ch))) {
-
-                    w.handleEvent(new TKeypressEvent(kbEnter));
+            if (widget instanceof TButton) {
+                TButton button = (TButton) widget;
+                if (button.getEnabled()
+                    && !keypress.getKey().getIsKey()
+                    && keypress.getKey().getAlt()
+                    && !keypress.getKey().getCtrl()
+                    && (Character.toLowerCase(button.getMnemonic().getShortcut())
+                        == Character.toLowerCase(keypress.getKey().getCh()))
+                ) {
+
+                    widget.handleEvent(new TKeypressEvent(kbEnter));
                     return;
                 }
             }
-             */
         }
 
         // Dispatch the keypress to an active widget
         for (TWidget widget: children) {
             if (widget.active) {
-                window.getApplication().setRepaint();
                 widget.handleEvent(keypress);
                 return;
             }
index 65eaa4835d342e00d1cf7065b44c5eea1b20bcce..0a74a40dcc830f8cf739c0ad0f56c9c7ea4f943a 100644 (file)
@@ -532,7 +532,6 @@ public class TWindow extends TWidget {
     @Override
     public void onMouseDown(final TMouseEvent mouse) {
         this.mouse = mouse;
-        application.setRepaint();
 
         inKeyboardResize = false;
 
@@ -605,7 +604,6 @@ public class TWindow extends TWidget {
     @Override
     public void onMouseUp(final TMouseEvent mouse) {
         this.mouse = mouse;
-        application.setRepaint();
 
         if ((inWindowMove) && (mouse.getMouse1())) {
             // Stop moving window
@@ -653,7 +651,6 @@ public class TWindow extends TWidget {
     @Override
     public void onMouseMotion(final TMouseEvent mouse) {
         this.mouse = mouse;
-        application.setRepaint();
 
         if (inWindowMove) {
             // Move window over
index fce11eaef1e4f9a11bfe2d2ba65c7b968b613c19..af2d181a6343da5eef95ce3a66995e070adcc1a0 100644 (file)
@@ -49,13 +49,18 @@ public final class AWTBackend extends Backend {
 
     /**
      * Public constructor.
+     *
+     * @param listener the object this backend needs to wake up when new
+     * input comes in
      */
-    public AWTBackend() {
+    public AWTBackend(final Object listener) {
         // Create a screen
         AWTScreen screen = new AWTScreen();
         this.screen = screen;
-        // Create the listeners
-        terminal = new AWTTerminal(screen);
+
+        // Create the AWT event listeners
+        terminal = new AWTTerminal(listener, screen);
+
         // Hang onto the session info
         this.sessionInfo = terminal.getSessionInfo();
     }
@@ -72,36 +77,10 @@ public final class AWTBackend extends Backend {
      * Get keyboard, mouse, and screen resize events.
      *
      * @param queue list to append new events to
-     * @param timeout maximum amount of time (in millis) to wait for an
-     * event.  0 means to return immediately, i.e. perform a poll.
      */
     @Override
-    public void getEvents(final List<TInputEvent> queue, final int timeout) {
-        if (timeout > 0) {
-            // Try to sleep, let the terminal's input thread wake me up if
-            // something came in.
-            synchronized (terminal) {
-                try {
-                    terminal.wait(timeout);
-                    if (terminal.hasEvents()) {
-                        // System.err.println("getEvents()");
-                        terminal.getEvents(queue);
-                    } else {
-                        // If I got here, then I timed out.  Call
-                        // terminal.getIdleEvents() to pick up stragglers
-                        // like bare resize.
-                        // System.err.println("getIdleEvents()");
-                        terminal.getIdleEvents(queue);
-                    }
-                } catch (InterruptedException e) {
-                    // Spurious interrupt, pretend it was like a timeout.
-                    // System.err.println("[interrupt] getEvents()");
-                    terminal.getIdleEvents(queue);
-                }
-            }
-        } else {
-            // Asking for a poll, go get it.
-            // System.err.println("[polled] getEvents()");
+    public void getEvents(final List<TInputEvent> queue) {
+        if (terminal.hasEvents()) {
             terminal.getEvents(queue);
         }
     }
index 3c1bffb58084071dfcea97843cdc33402630bd58..71c2ef01b14f62640e3b35e3e1c537db088d6460 100644 (file)
@@ -82,10 +82,8 @@ public abstract class Backend {
      * screen resize events.
      *
      * @param queue list to append new events to
-     * @param timeout maximum amount of time (in millis) to wait for an
-     * event.  0 means to return immediately, i.e. perform a poll.
      */
-    public abstract void getEvents(List<TInputEvent> queue, int timeout);
+    public abstract void getEvents(List<TInputEvent> queue);
 
     /**
      * Subclasses must provide an implementation that closes sockets,
index da9c9c35d8f13794cd820311eedcdd1ffc0fc99a..ed2563096de360694a54dc9ce36cc4fe421586cd 100644 (file)
@@ -53,6 +53,8 @@ public final class ECMA48Backend extends Backend {
     /**
      * Public constructor.
      *
+     * @param listener the object this backend needs to wake up when new
+     * input comes in
      * @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
@@ -63,11 +65,11 @@ public final class ECMA48Backend extends Backend {
      * @throws UnsupportedEncodingException if an exception is thrown when
      * creating the InputStreamReader
      */
-    public ECMA48Backend(final InputStream input,
+    public ECMA48Backend(final Object listener, final InputStream input,
         final OutputStream output) throws UnsupportedEncodingException {
 
         // Create a terminal and explicitly set stdin into raw mode
-        terminal = new ECMA48Terminal(input, output);
+        terminal = new ECMA48Terminal(listener, input, output);
 
         // Keep the terminal's sessionInfo so that TApplication can see it
         sessionInfo = terminal.getSessionInfo();
@@ -92,29 +94,10 @@ public final class ECMA48Backend extends Backend {
      * Get keyboard, mouse, and screen resize events.
      *
      * @param queue list to append new events to
-     * @param timeout maximum amount of time (in millis) to wait for an
-     * event.  0 means to return immediately, i.e. perform a poll.
      */
     @Override
-    public void getEvents(final List<TInputEvent> queue, final int timeout) {
-        if (timeout > 0) {
-            // Try to sleep, let the terminal's input thread wake me up if
-            // something came in.
-            synchronized (terminal) {
-                try {
-                    terminal.wait(timeout);
-                } catch (InterruptedException e) {
-                    // Spurious interrupt, pretend it was like a timeout.
-                    // System.err.println("[interrupt] getEvents()");
-                }
-                if (terminal.hasEvents()) {
-                    // System.err.println("getEvents()");
-                    terminal.getEvents(queue);
-                }
-            }
-        } else {
-            // Asking for a poll, go get it.
-            // System.err.println("[polled] getEvents()");
+    public void getEvents(final List<TInputEvent> queue) {
+        if (terminal.hasEvents()) {
             terminal.getEvents(queue);
         }
     }
index f0a8c59387278659b13c0d4bcba1b5502a8619e9..1afa354c7aebcb9aa34416ba1f999acb2a6bb39d 100644 (file)
@@ -422,7 +422,7 @@ class DemoMainWindow extends TWindow {
         progressBar = addProgressBar(1, row, 22, 0);
         row++;
         timerLabel = addLabel("Timer", 1, row);
-        timer = getApplication().addTimer(100, true,
+        timer = getApplication().addTimer(250, true,
             new TAction() {
 
                 public void DO() {
@@ -432,7 +432,6 @@ class DemoMainWindow extends TWindow {
                         timerI++;
                     }
                     progressBar.setValue(timerI);
-                    DemoMainWindow.this.setRepaint();
                 }
             }
         );
index 6cc252f22f0a65590990ce50595a2b9d6e647f30..27ba80aa12b7180accfd394e9c340627fe32a9f6 100644 (file)
@@ -81,6 +81,11 @@ public final class AWTTerminal implements ComponentListener, KeyListener,
         return sessionInfo;
     }
 
+    /**
+     * The listening object that run() wakes up on new input.
+     */
+    private Object listener;
+
     /**
      * The event queue, filled up by a thread reading on input.
      */
@@ -120,9 +125,12 @@ public final class AWTTerminal implements ComponentListener, KeyListener,
     /**
      * Constructor sets up state for getEvent().
      *
+     * @param listener the object this backend needs to wake up when new
+     * input comes in
      * @param screen the top-level AWT frame
      */
-    public AWTTerminal(final AWTScreen screen) {
+    public AWTTerminal(final Object listener, final AWTScreen screen) {
+        this.listener    = listener;
         this.screen      = screen;
         mouse1           = false;
         mouse2           = false;
@@ -162,26 +170,6 @@ public final class AWTTerminal implements ComponentListener, KeyListener,
         }
     }
 
-    /**
-     * Return any events in the IO queue due to timeout.
-     *
-     * @param queue list to append new events to
-     */
-    public void getIdleEvents(final List<TInputEvent> queue) {
-
-        // Insert any polling action here...
-
-        // Return any events that showed up
-        synchronized (eventQueue) {
-            if (eventQueue.size() > 0) {
-                synchronized (queue) {
-                    queue.addAll(eventQueue);
-                }
-                eventQueue.clear();
-            }
-        }
-    }
-
     /**
      * Pass AWT keystrokes into the event queue.
      *
@@ -396,9 +384,8 @@ public final class AWTTerminal implements ComponentListener, KeyListener,
         synchronized (eventQueue) {
             eventQueue.add(new TKeypressEvent(keypress));
         }
-        // Wake up the backend
-        synchronized (this) {
-            this.notifyAll();
+        synchronized (listener) {
+            listener.notifyAll();
         }
     }
 
@@ -433,9 +420,8 @@ public final class AWTTerminal implements ComponentListener, KeyListener,
         synchronized (eventQueue) {
             eventQueue.add(new TCommandEvent(cmAbort));
         }
-        // Wake up the backend
-        synchronized (this) {
-            this.notifyAll();
+        synchronized (listener) {
+            listener.notifyAll();
         }
     }
 
@@ -523,9 +509,8 @@ public final class AWTTerminal implements ComponentListener, KeyListener,
                 sessionInfo.getWindowWidth(), sessionInfo.getWindowHeight());
             eventQueue.add(windowResize);
         }
-        // Wake up the backend
-        synchronized (this) {
-            this.notifyAll();
+        synchronized (listener) {
+            listener.notifyAll();
         }
     }
 
@@ -561,9 +546,8 @@ public final class AWTTerminal implements ComponentListener, KeyListener,
         synchronized (eventQueue) {
             eventQueue.add(mouseEvent);
         }
-        // Wake up the backend
-        synchronized (this) {
-            this.notifyAll();
+        synchronized (listener) {
+            listener.notifyAll();
         }
     }
 
@@ -582,9 +566,8 @@ public final class AWTTerminal implements ComponentListener, KeyListener,
         synchronized (eventQueue) {
             eventQueue.add(mouseEvent);
         }
-        // Wake up the backend
-        synchronized (this) {
-            this.notifyAll();
+        synchronized (listener) {
+            listener.notifyAll();
         }
     }
 
@@ -650,9 +633,8 @@ public final class AWTTerminal implements ComponentListener, KeyListener,
         synchronized (eventQueue) {
             eventQueue.add(mouseEvent);
         }
-        // Wake up the backend
-        synchronized (this) {
-            this.notifyAll();
+        synchronized (listener) {
+            listener.notifyAll();
         }
     }
 
@@ -697,9 +679,8 @@ public final class AWTTerminal implements ComponentListener, KeyListener,
         synchronized (eventQueue) {
             eventQueue.add(mouseEvent);
         }
-        // Wake up the backend
-        synchronized (this) {
-            this.notifyAll();
+        synchronized (listener) {
+            listener.notifyAll();
         }
     }
 
@@ -743,9 +724,8 @@ public final class AWTTerminal implements ComponentListener, KeyListener,
         synchronized (eventQueue) {
             eventQueue.add(mouseEvent);
         }
-        // Wake up the backend
-        synchronized (this) {
-            this.notifyAll();
+        synchronized (listener) {
+            listener.notifyAll();
         }
     }
 
index d466646d5d129b312a8d93aa85c61812502738ec..9e9ffe3be47495f50d4cc3dd146ea7d0401faab1 100644 (file)
@@ -98,11 +98,6 @@ public final class ECMA48Terminal implements Runnable {
      */
     private ArrayList<String> params;
 
-    /**
-     * params[paramI] is being appended to.
-     */
-    private int paramI;
-
     /**
      * States in the input parser.
      */
@@ -187,6 +182,11 @@ public final class ECMA48Terminal implements Runnable {
      */
     private PrintWriter output;
 
+    /**
+     * The listening object that run() wakes up on new input.
+     */
+    private Object listener;
+
     /**
      * When true, the terminal is sending non-UTF8 bytes when reporting mouse
      * events.
@@ -284,6 +284,8 @@ public final class ECMA48Terminal implements Runnable {
     /**
      * Constructor sets up state for getEvent().
      *
+     * @param listener the object this backend needs to wake up when new
+     * input comes in
      * @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
@@ -294,7 +296,7 @@ public final class ECMA48Terminal implements Runnable {
      * @throws UnsupportedEncodingException if an exception is thrown when
      * creating the InputStreamReader
      */
-    public ECMA48Terminal(final InputStream input,
+    public ECMA48Terminal(final Object listener, final InputStream input,
         final OutputStream output) throws UnsupportedEncodingException {
 
         reset();
@@ -302,6 +304,7 @@ public final class ECMA48Terminal implements Runnable {
         mouse2           = false;
         mouse3           = false;
         stopReaderThread = false;
+        this.listener    = listener;
 
         if (input == null) {
             // inputStream = System.in;
@@ -402,7 +405,6 @@ public final class ECMA48Terminal implements Runnable {
     private void reset() {
         state = ParseState.GROUND;
         params = new ArrayList<String>();
-        paramI = 0;
         params.clear();
         params.add("");
     }
@@ -799,13 +801,13 @@ public final class ECMA48Terminal implements Runnable {
         case CSI_ENTRY:
             // Numbers - parameter values
             if ((ch >= '0') && (ch <= '9')) {
-                params.set(paramI, params.get(paramI) + ch);
+                params.set(params.size() - 1,
+                    params.get(params.size() - 1) + ch);
                 state = ParseState.CSI_PARAM;
                 return;
             }
             // Parameter separator
             if (ch == ';') {
-                paramI++;
                 params.add("");
                 return;
             }
@@ -907,14 +909,14 @@ public final class ECMA48Terminal implements Runnable {
         case CSI_PARAM:
             // Numbers - parameter values
             if ((ch >= '0') && (ch <= '9')) {
-                params.set(paramI, params.get(paramI) + ch);
+                params.set(params.size() - 1,
+                    params.get(params.size() - 1) + ch);
                 state = ParseState.CSI_PARAM;
                 return;
             }
             // Parameter separator
             if (ch == ';') {
-                paramI++;
-                params.add(paramI, "");
+                params.add("");
                 return;
             }
 
@@ -1000,7 +1002,7 @@ public final class ECMA48Terminal implements Runnable {
             return;
 
         case MOUSE:
-            params.set(0, params.get(paramI) + ch);
+            params.set(0, params.get(params.size() - 1) + ch);
             if (params.get(0).length() == 3) {
                 // We have enough to generate a mouse event
                 events.add(parseMouse());
@@ -1450,9 +1452,8 @@ public final class ECMA48Terminal implements Runnable {
                                 synchronized (eventQueue) {
                                     eventQueue.addAll(events);
                                 }
-                                // Now wake up the backend
-                                synchronized (this) {
-                                    this.notifyAll();
+                                synchronized (listener) {
+                                    listener.notifyAll();
                                 }
                                 events.clear();
                             }
@@ -1465,6 +1466,9 @@ public final class ECMA48Terminal implements Runnable {
                             eventQueue.addAll(events);
                         }
                         events.clear();
+                        synchronized (listener) {
+                            listener.notifyAll();
+                        }
                     }
 
                     // Wait 10 millis for more data
index 81dabd246a8f607a4c87994c70a65880482eda0a..e3f72191809b73565397c98c8b8523c800b9f8be 100644 (file)
@@ -185,6 +185,16 @@ public abstract class Screen {
      */
     protected boolean dirty;
 
+    /**
+     * Get dirty flag.
+     *
+     * @return if true, the logical screen is not in sync with the physical
+     * screen
+     */
+    public final boolean isDirty() {
+        return dirty;
+    }
+
     /**
      * Set if the user explicitly wants to redraw everything starting with a
      * ECMATerminal.clearAll().
index ad8e4acc871bb4cb73ca356e9b92fbc80717341f..ee1af554092ad442adb6b87c8318f15cc9ab1af8 100644 (file)
@@ -177,7 +177,6 @@ public final class TMenu extends TWindow {
     @Override
     public void onMouseDown(final TMouseEvent mouse) {
         this.mouse = mouse;
-        setRepaint();
 
         // Pass to children
         for (TWidget widget: getChildren()) {
@@ -202,7 +201,6 @@ public final class TMenu extends TWindow {
     @Override
     public void onMouseUp(final TMouseEvent mouse) {
         this.mouse = mouse;
-        setRepaint();
 
         // Pass to children
         for (TWidget widget: getChildren()) {
@@ -227,7 +225,6 @@ public final class TMenu extends TWindow {
     @Override
     public void onMouseMotion(final TMouseEvent mouse) {
         this.mouse = mouse;
-        setRepaint();
 
         // See if we should activate a different menu item
         for (TWidget widget: getChildren()) {
@@ -306,7 +303,6 @@ public final class TMenu extends TWindow {
         // Dispatch the keypress to an active widget
         for (TWidget widget: getChildren()) {
             if (widget.getActive()) {
-                setRepaint();
                 widget.handleEvent(keypress);
                 return;
             }