Fix bounds check
[nikiroo-utils.git] / src / jexer / TApplication.java
index 4457197caeec733fbea251085b9105e1ec4e9940..e319a4df410dba7ae7c7ab552dfa1d5e824fb47d 100644 (file)
@@ -283,6 +283,10 @@ public class TApplication implements Runnable {
      * Wake the sleeping active event handler.
      */
     private void wakeEventHandler() {
+        if (!started) {
+            return;
+        }
+
         if (secondaryEventHandler != null) {
             synchronized (secondaryEventHandler) {
                 secondaryEventHandler.notify();
@@ -350,6 +354,17 @@ public class TApplication implements Runnable {
      */
     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().
      */
@@ -415,6 +430,11 @@ public class TApplication implements Runnable {
      */
     private List<TTimer> timers;
 
+    /**
+     * When true, the application has been started.
+     */
+    private volatile boolean started = false;
+
     /**
      * When true, exit the application.
      */
@@ -920,6 +940,8 @@ public class TApplication implements Runnable {
         primaryEventHandler = new WidgetEventHandler(this, true);
         (new Thread(primaryEventHandler)).start();
 
+        started = true;
+
         while (!quit) {
             synchronized (this) {
                 boolean doWait = false;
@@ -1071,6 +1093,7 @@ public class TApplication implements Runnable {
         if (debugEvents) {
             System.err.printf("Handle event: %s\n", event);
         }
+        TMouseEvent doubleClick = null;
 
         // Special application-wide events -----------------------------------
 
@@ -1082,6 +1105,25 @@ public class TApplication implements Runnable {
                 oldMouseY = mouseY;
                 mouseX = mouse.getX();
                 mouseY = mouse.getY();
+            } else {
+                if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
+                    if ((mouse.getTime().getTime() - lastMouseUpTime) <
+                        doubleClickTime) {
+
+                        // 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();
+                    }
+                }
             }
 
             // See if we need to switch focus to another window or the menu
@@ -1189,6 +1231,11 @@ public class TApplication implements Runnable {
                 mouse.setX(mouse.getX() - window.getX());
                 mouse.setY(mouse.getY() - window.getY());
 
+                if (doubleClick != null) {
+                    doubleClick.setX(doubleClick.getX() - window.getX());
+                    doubleClick.setY(doubleClick.getY() - window.getY());
+                }
+
                 if (window.mouseWouldHit(mouse)) {
                     dispatchToDesktop = false;
                 }
@@ -1201,11 +1248,17 @@ public class TApplication implements Runnable {
                     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);
+                }
             }
         }
     }
@@ -1219,6 +1272,8 @@ public class TApplication implements Runnable {
      * @see #primaryHandleEvent(TInputEvent event)
      */
     private void secondaryHandleEvent(final TInputEvent event) {
+        TMouseEvent doubleClick = null;
+
         // Peek at the mouse position
         if (event instanceof TMouseEvent) {
             TMouseEvent mouse = (TMouseEvent) event;
@@ -1227,10 +1282,32 @@ public class TApplication implements Runnable {
                 oldMouseY = mouseY;
                 mouseX = mouse.getX();
                 mouseY = mouse.getY();
+            } else {
+                if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
+                    if ((mouse.getTime().getTime() - lastMouseUpTime) <
+                        doubleClickTime) {
+
+                        // 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();
+                    }
+                }
             }
         }
 
         secondaryEventReceiver.handleEvent(event);
+        if (doubleClick != null) {
+            secondaryEventReceiver.handleEvent(doubleClick);
+        }
     }
 
     /**
@@ -1431,9 +1508,15 @@ public class TApplication implements Runnable {
         if (activeWindow != null) {
             assert (activeWindow.getZ() == 0);
 
-            activeWindow.onUnfocus();
             activeWindow.setActive(false);
             activeWindow.setZ(window.getZ());
+
+            // Unset activeWindow now before unfocus, so that a window
+            // lifecycle change inside onUnfocus() doesn't call
+            // switchWindow() and lead to a stack overflow.
+            TWindow oldActiveWindow = activeWindow;
+            activeWindow = null;
+            oldActiveWindow.onUnfocus();
         }
         activeWindow = window;
         activeWindow.setZ(0);
@@ -1858,10 +1941,16 @@ public class TApplication implements Runnable {
                 continue;
             }
             for (int x = w.getX(); x < w.getX() + w.getWidth(); x++) {
+                if (x < 0) {
+                    continue;
+                }
                 if (x >= width) {
                     continue;
                 }
                 for (int y = w.getY(); y < w.getY() + w.getHeight(); y++) {
+                    if (y < 0) {
+                        continue;
+                    }
                     if (y >= height) {
                         continue;
                     }
@@ -2311,6 +2400,24 @@ public class TApplication implements Runnable {
         }
     }
 
+    /**
+     * Post an event to process.
+     *
+     * @param event new event to add to the queue
+     */
+    public final void postEvent(final TInputEvent event) {
+        synchronized (this) {
+            synchronized (fillEventQueue) {
+                fillEventQueue.add(event);
+            }
+            if (debugThreads) {
+                System.err.println(System.currentTimeMillis() + " " +
+                    Thread.currentThread() + " postEvent() wake up main");
+            }
+            this.notify();
+        }
+    }
+
     /**
      * Post an event to process and turn off the menu.
      *
@@ -2714,6 +2821,37 @@ public class TApplication implements Runnable {
         return new TTerminalWindow(this, x, y, flags);
     }
 
+    /**
+     * Convenience function to open a terminal window and execute a custom
+     * command line inside it.
+     *
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param commandLine the command line to execute
+     * @return the terminal new window
+     */
+    public final TTerminalWindow openTerminal(final int x, final int y,
+        final String commandLine) {
+
+        return openTerminal(x, y, TWindow.RESIZABLE, commandLine);
+    }
+
+    /**
+     * Convenience function to open a terminal window and execute a custom
+     * command line inside it.
+     *
+     * @param x column relative to parent
+     * @param y row relative to parent
+     * @param flags mask of CENTERED, MODAL, or RESIZABLE
+     * @param commandLine the command line to execute
+     * @return the terminal new window
+     */
+    public final TTerminalWindow openTerminal(final int x, final int y,
+        final int flags, final String commandLine) {
+
+        return new TTerminalWindow(this, x, y, flags, commandLine);
+    }
+
     /**
      * Convenience function to spawn an file open box.
      *