TEditor 50% complete
[nikiroo-utils.git] / src / jexer / TApplication.java
index d8bec5e5d07b00dd54faca3f0660fbfcf9e4884a..8b436ab9a99ec9219b9755ffce379c62d834e10f 100644 (file)
@@ -44,7 +44,6 @@ import java.util.Map;
 
 import jexer.bits.CellAttributes;
 import jexer.bits.ColorTheme;
-import jexer.bits.GraphicsChars;
 import jexer.event.TCommandEvent;
 import jexer.event.TInputEvent;
 import jexer.event.TKeypressEvent;
@@ -52,16 +51,19 @@ import jexer.event.TMenuEvent;
 import jexer.event.TMouseEvent;
 import jexer.event.TResizeEvent;
 import jexer.backend.Backend;
+import jexer.backend.Screen;
 import jexer.backend.SwingBackend;
 import jexer.backend.ECMA48Backend;
-import jexer.io.Screen;
+import jexer.backend.TWindowBackend;
 import jexer.menu.TMenu;
 import jexer.menu.TMenuItem;
 import static jexer.TCommand.*;
 import static jexer.TKeypress.*;
 
 /**
- * TApplication sets up a full Text User Interface application.
+ * TApplication is the main driver class for a full Text User Interface
+ * application.  It manages windows, provides a menu bar and status bar, and
+ * processes events received from the user.
  */
 public class TApplication implements Runnable {
 
@@ -404,7 +406,15 @@ public class TApplication implements Runnable {
      * @return the Screen
      */
     public final Screen getScreen() {
-        return backend.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();
+        }
     }
 
     /**
@@ -570,7 +580,7 @@ public class TApplication implements Runnable {
     }
 
     /**
-     * Get the list of windows.
+     * Get a (shallow) copy of the window list.
      *
      * @return a copy of the list of windows for this application
      */
@@ -580,6 +590,32 @@ public class TApplication implements Runnable {
         return result;
     }
 
+    /**
+     * If true, focus follows mouse: windows automatically raised if the
+     * mouse passes over them.
+     */
+    private boolean focusFollowsMouse = false;
+
+    /**
+     * Get focusFollowsMouse flag.
+     *
+     * @return true if focus follows mouse: windows automatically raised if
+     * the mouse passes over them
+     */
+    public boolean getFocusFollowsMouse() {
+        return focusFollowsMouse;
+    }
+
+    /**
+     * 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;
+    }
+
     // ------------------------------------------------------------------------
     // General behavior -------------------------------------------------------
     // ------------------------------------------------------------------------
@@ -610,6 +646,12 @@ public class TApplication implements Runnable {
 
         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:
@@ -686,6 +728,7 @@ public class TApplication implements Runnable {
      */
     public TApplication(final Backend backend) {
         this.backend = backend;
+        backend.setListener(this);
         TApplicationImpl();
     }
 
@@ -877,6 +920,13 @@ public class TApplication implements Runnable {
     // Main loop --------------------------------------------------------------
     // ------------------------------------------------------------------------
 
+    /**
+     * Force this application to exit.
+     */
+    public void exit() {
+        quit = true;
+    }
+
     /**
      * Run this application until it exits.
      */
@@ -1021,19 +1071,6 @@ public class TApplication implements Runnable {
             return;
         }
 
-        // Peek at the mouse position
-        if (event instanceof TMouseEvent) {
-            TMouseEvent mouse = (TMouseEvent) event;
-            synchronized (getScreen()) {
-                if ((mouseX != mouse.getX()) || (mouseY != mouse.getY())) {
-                    oldMouseX = mouseX;
-                    oldMouseY = mouseY;
-                    mouseX = mouse.getX();
-                    mouseY = mouse.getY();
-                }
-            }
-        }
-
         // Put into the main queue
         drainEventQueue.add(event);
     }
@@ -1056,6 +1093,14 @@ public class TApplication implements Runnable {
 
         // 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();
+            }
+
             // See if we need to switch focus to another window or the menu
             checkSwitchFocus((TMouseEvent) event);
         }
@@ -1191,6 +1236,17 @@ public class TApplication implements Runnable {
      * @see #primaryHandleEvent(TInputEvent event)
      */
     private void secondaryHandleEvent(final TInputEvent event) {
+        // 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();
+            }
+        }
+
         secondaryEventReceiver.handleEvent(event);
     }
 
@@ -1278,9 +1334,9 @@ public class TApplication implements Runnable {
     }
 
     /**
-     * Return the number of windows that are visible.
+     * Return the number of windows that are showing.
      *
-     * @return the number of windows that are visible
+     * @return the number of windows that are showing on screen
      */
     public final int shownWindowCount() {
         int n = 0;
@@ -1292,6 +1348,21 @@ public class TApplication implements Runnable {
         return n;
     }
 
+    /**
+     * Return the number of windows that are hidden.
+     *
+     * @return the number of windows that are hidden
+     */
+    public final int hiddenWindowCount() {
+        int n = 0;
+        for (TWindow w: windows) {
+            if (w.isHidden()) {
+                n++;
+            }
+        }
+        return n;
+    }
+
     /**
      * Check if a window instance is in this application's window list.
      *
@@ -1304,6 +1375,7 @@ public class TApplication implements Runnable {
         }
         for (TWindow w: windows) {
             if (w == window) {
+                assert (window.getApplication() == this);
                 return true;
             }
         }
@@ -1496,8 +1568,8 @@ public class TApplication implements Runnable {
      * otherwise switch to the previous window in the list
      */
     public final void switchWindow(final boolean forward) {
-        // Only switch if there are multiple windows
-        if (windows.size() < 2) {
+        // Only switch if there are multiple visible windows
+        if (shownWindowCount() < 2) {
             return;
         }
         assert (activeWindow != null);
@@ -1522,18 +1594,23 @@ public class TApplication implements Runnable {
                 return;
             }
 
-            int nextWindowI;
-            if (forward) {
-                nextWindowI = (activeWindowI + 1) % windows.size();
-            } else {
-                if (activeWindowI == 0) {
-                    nextWindowI = windows.size() - 1;
+            int nextWindowI = activeWindowI;
+            for (;;) {
+                if (forward) {
+                    nextWindowI++;
+                    nextWindowI %= windows.size();
                 } else {
-                    nextWindowI = activeWindowI - 1;
+                    nextWindowI--;
+                    if (nextWindowI < 0) {
+                        nextWindowI = windows.size() - 1;
+                    }
                 }
-            }
 
-            activateWindow(windows.get(nextWindowI));
+                if (windows.get(nextWindowI).isShown()) {
+                    activateWindow(windows.get(nextWindowI));
+                    break;
+                }
+            }
         } // synchronized (windows)
 
     }
@@ -1746,11 +1823,11 @@ public class TApplication implements Runnable {
                 continue;
             }
             for (int x = w.getX(); x < w.getX() + w.getWidth(); x++) {
-                if (x == width) {
+                if (x >= width) {
                     continue;
                 }
                 for (int y = w.getY(); y < w.getY() + w.getHeight(); y++) {
-                    if (y == height) {
+                    if (y >= height) {
                         continue;
                     }
                     overlapMatrix[x][y]++;
@@ -1793,11 +1870,11 @@ public class TApplication implements Runnable {
                 long newOverlapN = 0;
                 // Start by adding each new cell.
                 for (int wx = x; wx < x + window.getWidth(); wx++) {
-                    if (wx == width) {
+                    if (wx >= width) {
                         continue;
                     }
                     for (int wy = y; wy < y + window.getHeight(); wy++) {
-                        if (wy == height) {
+                        if (wy >= height) {
                             continue;
                         }
                         newMatrix[wx][wy]++;
@@ -1941,55 +2018,64 @@ public class TApplication implements Runnable {
             return;
         }
 
-        // Only switch if there are multiple windows
-        if (windows.size() < 2) {
+        // If a menu is still active, don't switch windows
+        if (activeMenu != null) {
             return;
         }
 
-        // Switch on the upclick
-        if (mouse.getType() != TMouseEvent.Type.MOUSE_UP) {
+        // Only switch if there are multiple windows
+        if (windows.size() < 2) {
             return;
         }
 
-        synchronized (windows) {
-            Collections.sort(windows);
-            if (windows.get(0).isModal()) {
-                // Modal windows don't switch
-                return;
-            }
+        if (((focusFollowsMouse == true)
+                && (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION))
+            || (mouse.getType() == TMouseEvent.Type.MOUSE_UP)
+        ) {
+            synchronized (windows) {
+                Collections.sort(windows);
+                if (windows.get(0).isModal()) {
+                    // Modal windows don't switch
+                    return;
+                }
 
-            for (TWindow window: windows) {
-                assert (!window.isModal());
+                for (TWindow window: windows) {
+                    assert (!window.isModal());
 
-                if (window.isHidden()) {
-                    assert (!window.isActive());
-                    continue;
-                }
+                    if (window.isHidden()) {
+                        assert (!window.isActive());
+                        continue;
+                    }
 
-                if (window.mouseWouldHit(mouse)) {
-                    if (window == windows.get(0)) {
-                        // Clicked on the same window, nothing to do
-                        assert (window.isActive());
+                    if (window.mouseWouldHit(mouse)) {
+                        if (window == windows.get(0)) {
+                            // Clicked on the same window, nothing to do
+                            assert (window.isActive());
+                            return;
+                        }
+
+                        // We will be switching to another window
+                        assert (windows.get(0).isActive());
+                        assert (windows.get(0) == activeWindow);
+                        assert (!window.isActive());
+                        activeWindow.onUnfocus();
+                        activeWindow.setActive(false);
+                        activeWindow.setZ(window.getZ());
+                        activeWindow = window;
+                        window.setZ(0);
+                        window.setActive(true);
+                        window.onFocus();
                         return;
                     }
-
-                    // We will be switching to another window
-                    assert (windows.get(0).isActive());
-                    assert (windows.get(0) == activeWindow);
-                    assert (!window.isActive());
-                    activeWindow.onUnfocus();
-                    activeWindow.setActive(false);
-                    activeWindow.setZ(window.getZ());
-                    activeWindow = window;
-                    window.setZ(0);
-                    window.setActive(true);
-                    window.onFocus();
-                    return;
                 }
             }
+
+            // Clicked on the background, nothing to do
+            return;
         }
 
-        // Clicked on the background, nothing to do
+        // Nothing to do: this isn't a mouse up, or focus isn't following
+        // mouse.
         return;
     }
 
@@ -2007,6 +2093,53 @@ public class TApplication implements Runnable {
         }
     }
 
+    /**
+     * Get a (shallow) copy of the menu list.
+     *
+     * @return a copy of the menu list
+     */
+    public final List<TMenu> getAllMenus() {
+        return new LinkedList<TMenu>(menus);
+    }
+
+    /**
+     * Add a top-level menu to the list.
+     *
+     * @param menu the menu to add
+     * @throws IllegalArgumentException if the menu is already used in
+     * another TApplication
+     */
+    public final void addMenu(final TMenu menu) {
+        if ((menu.getApplication() != null)
+            && (menu.getApplication() != this)
+        ) {
+            throw new IllegalArgumentException("Menu " + menu + " is already " +
+                "part of application " + menu.getApplication());
+        }
+        closeMenu();
+        menus.add(menu);
+        recomputeMenuX();
+    }
+
+    /**
+     * Remove a top-level menu from the list.
+     *
+     * @param menu the menu to remove
+     * @throws IllegalArgumentException if the menu is already used in
+     * another TApplication
+     */
+    public final void removeMenu(final TMenu menu) {
+        if ((menu.getApplication() != null)
+            && (menu.getApplication() != this)
+        ) {
+            throw new IllegalArgumentException("Menu " + menu + " is already " +
+                "part of application " + menu.getApplication());
+        }
+        closeMenu();
+        menus.remove(menu);
+        recomputeMenuX();
+    }
+
     /**
      * Turn off a sub-menu.
      */
@@ -2588,7 +2721,6 @@ public class TApplication implements Runnable {
     /**
      * Convenience function to create a new window and make it active.
      *
-     * @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