misc cleanup
[nikiroo-utils.git] / src / jexer / TApplication.java
index 8071ae8edc2a5a0f5762dcdd2d2871db960dbe54..9d845be18affeecbd94a91ad2939a9ad8da4fa2c 100644 (file)
@@ -64,6 +64,16 @@ import static jexer.TKeypress.*;
  */
 public class TApplication {
 
+    /**
+     * If true, emit thread stuff to System.err.
+     */
+    private static final boolean debugThreads = false;
+
+    /**
+     * If true, emit events being processed to System.err.
+     */
+    private static final boolean debugEvents = false;
+
     /**
      * WidgetEventHandler is the main event consumer loop.  There are at most
      * two such threads in existence: the primary for normal case and a
@@ -134,6 +144,10 @@ public class TApplication {
                         }
                         event = application.drainEventQueue.remove(0);
                     }
+                    // Wait for drawAll() or doIdle() to be done, then handle
+                    // the event.
+                    boolean oldLock = lockHandleEvent();
+                    assert (oldLock == false);
                     if (primary) {
                         primaryHandleEvent(event);
                     } else {
@@ -142,8 +156,17 @@ public class TApplication {
                     if ((!primary)
                         && (application.secondaryEventReceiver == null)
                     ) {
-                        // Secondary thread, time to exit
+                        // Secondary thread, time to exit.
+
+                        // DO NOT UNLOCK.  Primary thread just came back from
+                        // primaryHandleEvent() and will unlock in the else
+                        // block below.
                         return;
+                    } else {
+                        // Unlock.  Either I am primary thread, or I am
+                        // secondary thread and still running.
+                        oldLock = unlockHandleEvent();
+                        assert (oldLock == true);
                     }
                 }
             } // while (true) (main runnable loop)
@@ -165,6 +188,96 @@ public class TApplication {
      */
     private TWidget secondaryEventReceiver;
 
+    /**
+     * Spinlock for the primary and secondary event handlers.
+     * WidgetEventHandler.run() is responsible for setting this value.
+     */
+    private volatile boolean insideHandleEvent = false;
+
+    /**
+     * Set the insideHandleEvent flag to true.  lockoutEventHandlers() will
+     * spin indefinitely until unlockHandleEvent() is called.
+     *
+     * @return the old value of insideHandleEvent
+     */
+    private boolean lockHandleEvent() {
+        if (debugThreads) {
+            System.err.printf("  >> lockHandleEvent(): oldValue %s",
+                insideHandleEvent);
+        }
+        boolean oldValue = true;
+
+        synchronized (this) {
+            // Wait for TApplication.run() to finish using the global state
+            // before allowing further event processing.
+            while (lockoutHandleEvent == true);
+
+            oldValue = insideHandleEvent;
+            insideHandleEvent = true;
+        }
+
+        if (debugThreads) {
+            System.err.printf(" ***\n");
+        }
+        return oldValue;
+    }
+
+    /**
+     * Set the insideHandleEvent flag to false.  lockoutEventHandlers() will
+     * spin indefinitely until unlockHandleEvent() is called.
+     *
+     * @return the old value of insideHandleEvent
+     */
+    private boolean unlockHandleEvent() {
+        if (debugThreads) {
+            System.err.printf("  << unlockHandleEvent(): oldValue %s\n",
+                insideHandleEvent);
+        }
+        synchronized (this) {
+            boolean oldValue = insideHandleEvent;
+            insideHandleEvent = false;
+            return oldValue;
+        }
+    }
+
+    /**
+     * Spinlock for the primary and secondary event handlers.  When true, the
+     * event handlers will spinlock wait before calling handleEvent().
+     */
+    private volatile boolean lockoutHandleEvent = false;
+
+    /**
+     * TApplication.run() needs to be able rely on the global data structures
+     * being intact when calling doIdle() and drawAll().  Tell the event
+     * handlers to wait for an unlock before handling their events.
+     */
+    private void stopEventHandlers() {
+        if (debugThreads) {
+            System.err.printf(">> stopEventHandlers()");
+        }
+
+        lockoutHandleEvent = true;
+        // Wait for the last event to finish processing before returning
+        // control to TApplication.run().
+        while (insideHandleEvent == true);
+
+        if (debugThreads) {
+            System.err.printf(" XXX\n");
+        }
+    }
+
+    /**
+     * TApplication.run() needs to be able rely on the global data structures
+     * being intact when calling doIdle() and drawAll().  Tell the event
+     * handlers that it is now OK to handle their events.
+     */
+    private void startEventHandlers() {
+        if (debugThreads) {
+            System.err.printf("<< startEventHandlers()\n");
+        }
+        lockoutHandleEvent = false;
+    }
+
     /**
      * Access to the physical screen, keyboard, and mouse.
      */
@@ -366,6 +479,10 @@ public class TApplication {
      * Draw everything.
      */
     public final void drawAll() {
+        if (debugThreads) {
+            System.err.printf("drawAll() enter\n");
+        }
+
         if ((flush) && (!repaint)) {
             backend.flushScreen();
             flush = false;
@@ -376,6 +493,10 @@ public class TApplication {
             return;
         }
 
+        if (debugThreads) {
+            System.err.printf("drawAll() REDRAW\n");
+        }
+
         // If true, the cursor is not visible
         boolean cursor = false;
 
@@ -497,11 +618,19 @@ public class TApplication {
                 metaHandleEvent(event);
             }
 
+            // Prevent stepping on the primary or secondary event handler.
+            stopEventHandlers();
+
             // Process timers and call doIdle()'s
             doIdle();
 
             // Update the screen
-            drawAll();
+            synchronized (getScreen()) {
+                drawAll();
+            }
+
+            // Let the event handlers run again.
+            startEventHandlers();
         }
 
         // Shutdown the consumer threads
@@ -520,10 +649,10 @@ public class TApplication {
      */
     private void metaHandleEvent(final TInputEvent event) {
 
-        /*
-        System.err.printf(String.format("metaHandleEvents event: %s\n",
-                event)); System.err.flush();
-         */
+        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
@@ -587,7 +716,9 @@ public class TApplication {
      */
     private void primaryHandleEvent(final TInputEvent event) {
 
-        // System.err.printf("Handle event: %s\n", event);
+        if (debugEvents) {
+            System.err.printf("Handle event: %s\n", event);
+        }
 
         // Special application-wide events -----------------------------------
 
@@ -680,7 +811,10 @@ public class TApplication {
                     mouse.setX(mouse.getX() - window.getX());
                     mouse.setY(mouse.getY() - window.getY());
                 }
-                // System.err("TApplication dispatch event: %s\n", event);
+                if (debugEvents) {
+                    System.err.printf("TApplication dispatch event: %s\n",
+                        event);
+                }
                 window.handleEvent(event);
                 break;
             }
@@ -720,6 +854,13 @@ public class TApplication {
      */
     public final void yield() {
         assert (secondaryEventReceiver != null);
+        // This is where we handoff the event handler lock from the primary
+        // to secondary thread.  We unlock here, and in a future loop the
+        // secondary thread locks again.  When it gives up, we have the
+        // single lock back.
+        boolean oldLock = unlockHandleEvent();
+        assert (oldLock == true);
+
         while (secondaryEventReceiver != null) {
             synchronized (this) {
                 try {
@@ -735,6 +876,10 @@ public class TApplication {
      * Do stuff when there is no user input.
      */
     private void doIdle() {
+        if (debugThreads) {
+            System.err.printf("doIdle()\n");
+        }
+
         // Now run any timers that have timed out
         Date now = new Date();
         List<TTimer> keepTimers = new LinkedList<TTimer>();
@@ -1124,14 +1269,11 @@ public class TApplication {
             return true;
         }
 
-        /*
-         TODO
         if (command.equals(cmShell)) {
-            openTerminal(0, 0, TWindow.Flag.RESIZABLE);
+            openTerminal(0, 0, TWindow.RESIZABLE);
             repaint = true;
             return true;
         }
-         */
 
         if (command.equals(cmTile)) {
             tileWindows();
@@ -1172,14 +1314,11 @@ public class TApplication {
             return true;
         }
 
-        /*
-         TODO
-        if (menu.id == TMenu.MID_SHELL) {
-            openTerminal(0, 0, TWindow.Flag.RESIZABLE);
+        if (menu.getId() == TMenu.MID_SHELL) {
+            openTerminal(0, 0, TWindow.RESIZABLE);
             repaint = true;
             return true;
         }
-         */
 
         if (menu.getId() == TMenu.MID_TILE) {
             tileWindows();
@@ -1287,7 +1426,7 @@ public class TApplication {
      * @param title menu title
      * @return the new menu
      */
-    public final TMenu addMenu(String title) {
+    public final TMenu addMenu(final String title) {
         int x = 0;
         int y = 0;
         TMenu menu = new TMenu(this, x, y, title);
@@ -1386,8 +1525,6 @@ public class TApplication {
         int newWidth = (getScreen().getWidth() / a);
         int newHeight1 = ((getScreen().getHeight() - 1) / b);
         int newHeight2 = ((getScreen().getHeight() - 1) / (b + c));
-        // System.err.printf("Z %s a %s b %s c %s newWidth %s newHeight1 %s newHeight2 %s",
-        //     z, a, b, c, newWidth, newHeight1, newHeight2);
 
         List<TWindow> sorted = new LinkedList<TWindow>(windows);
         Collections.sort(sorted);
@@ -1527,4 +1664,29 @@ public class TApplication {
         return new TInputBox(this, title, caption, text);
     }
 
+    /**
+     * Convenience function to open a terminal window.
+     *
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @return the terminal new window
+     */
+    public final TTerminalWindow openTerminal(final int x, final int y) {
+        return openTerminal(x, y, TWindow.RESIZABLE);
+    }
+
+    /**
+     * Convenience function to open a terminal window.
+     *
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param flags mask of CENTERED, MODAL, or RESIZABLE
+     * @return the terminal new window
+     */
+    public final TTerminalWindow openTerminal(final int x, final int y,
+        final int flags) {
+
+        return new TTerminalWindow(this, x, y, flags);
+    }
+
 }