X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fjexer%2FTApplication.java;h=d8bec5e5d07b00dd54faca3f0660fbfcf9e4884a;hb=9245321388306b5b49d6385ce2f46ea6a82ab619;hp=9f15ccbc4f5d33b6b21291a151da2cd41c78a870;hpb=a7986f7b289a17ca812a2f0cf04e48071accd636;p=fanfix.git diff --git a/src/jexer/TApplication.java b/src/jexer/TApplication.java index 9f15ccb..d8bec5e 100644 --- a/src/jexer/TApplication.java +++ b/src/jexer/TApplication.java @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (C) 2016 Kevin Lamonte + * 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"), @@ -449,7 +449,7 @@ public class TApplication implements Runnable { private List subMenus; /** - * The currently acive menu. + * The currently active menu. */ private TMenu activeMenu = null; @@ -482,6 +482,11 @@ public class TApplication implements Runnable { */ private List windows; + /** + * The currently acive window. + */ + private TWindow activeWindow = null; + /** * Timers that are being ticked. */ @@ -527,6 +532,54 @@ public class TApplication implements Runnable { return desktopBottom; } + /** + * An optional TDesktop background window that is drawn underneath + * everything else. + */ + private TDesktop desktop; + + /** + * 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; + } + + /** + * Get the TDesktop instance. + * + * @return the desktop, or null if it is not set + */ + public final TDesktop getDesktop() { + return desktop; + } + + /** + * Get the current active window. + * + * @return the active window, or null if it is not set + */ + public final TWindow getActiveWindow() { + return activeWindow; + } + + /** + * Get the list of windows. + * + * @return a copy of the list of windows for this application + */ + public final List getAllWindows() { + List result = new LinkedList(); + result.addAll(windows); + return result; + } + // ------------------------------------------------------------------------ // General behavior ------------------------------------------------------- // ------------------------------------------------------------------------ @@ -650,6 +703,7 @@ public class TApplication implements Runnable { timers = new LinkedList(); accelerators = new HashMap(); menuItems = new ArrayList(); + desktop = new TDesktop(this); // Setup the main consumer thread primaryEventHandler = new WidgetEventHandler(this, true); @@ -714,17 +768,23 @@ public class TApplication implements Runnable { // Start with a clean screen getScreen().clear(); - // Draw the background - CellAttributes background = theme.getColor("tapplication.background"); - getScreen().putAll(GraphicsChars.HATCH, background); + // Draw the desktop + if (desktop != null) { + desktop.drawChildren(); + } // Draw each window in reverse Z order List sorted = new LinkedList(windows); Collections.sort(sorted); - TWindow topLevel = sorted.get(0); + TWindow topLevel = null; + if (sorted.size() > 0) { + topLevel = sorted.get(0); + } Collections.reverse(sorted); for (TWindow window: sorted) { - window.drawChildren(); + if (window.isShown()) { + window.drawChildren(); + } } // Draw the blank menubar line - reset the screen clipping first so @@ -768,7 +828,10 @@ public class TApplication implements Runnable { } // Draw the status bar of the top-level window - TStatusBar statusBar = topLevel.getStatusBar(); + TStatusBar statusBar = null; + if (topLevel != null) { + statusBar = topLevel.getStatusBar(); + } if (statusBar != null) { getScreen().resetClipping(); statusBar.setWidth(getScreen().getWidth()); @@ -821,7 +884,7 @@ public class TApplication implements Runnable { while (!quit) { // Timeout is in milliseconds, so default timeout after 1 second // of inactivity. - long timeout = 0; + long timeout = 1000; // If I've got no updates to render, wait for something from the // backend or a timer. @@ -951,6 +1014,10 @@ public class TApplication implements Runnable { oldMouseX = 0; oldMouseY = 0; } + if (desktop != null) { + desktop.setDimensions(0, 0, resize.getWidth(), + resize.getHeight() - 1); + } return; } @@ -1038,13 +1105,12 @@ public class TApplication implements Runnable { // shortcutted by the active window, and if so dispatch the menu // event. boolean windowWillShortcut = false; - for (TWindow window: windows) { - if (window.isActive()) { - if (window.isShortcutKeypress(keypress.getKey())) { - // We do not process this key, it will be passed to - // the window instead. - windowWillShortcut = true; - } + 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; } } @@ -1082,25 +1148,40 @@ public class TApplication implements Runnable { } // Dispatch events to the active window ------------------------------- - for (TWindow window: windows) { - if (window.isActive()) { - 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 (debugEvents) { - System.err.printf("TApplication dispatch event: %s\n", - event); + 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 (window.mouseWouldHit(mouse)) { + dispatchToDesktop = false; } - window.handleEvent(event); - break; + } else if (event instanceof TKeypressEvent) { + dispatchToDesktop = false; + } + + if (debugEvents) { + System.err.printf("TApplication dispatch event: %s\n", + event); + } + window.handleEvent(event); + } + if (dispatchToDesktop) { + // This event is fair game for the desktop to process. + if (desktop != null) { + desktop.handleEvent(event); } } } + /** * Dispatch one event to the appropriate widget or application-level * event handler. This is the secondary event handler used by certain @@ -1178,12 +1259,170 @@ public class TApplication implements Runnable { for (TWindow window: windows) { window.onIdle(); } + if (desktop != null) { + desktop.onIdle(); + } } // ------------------------------------------------------------------------ // TWindow management ----------------------------------------------------- // ------------------------------------------------------------------------ + /** + * Return the total number of windows. + * + * @return the total number of windows + */ + public final int windowCount() { + return windows.size(); + } + + /** + * Return the number of windows that are visible. + * + * @return the number of windows that are visible + */ + public final int shownWindowCount() { + int n = 0; + for (TWindow w: windows) { + if (w.isShown()) { + n++; + } + } + return n; + } + + /** + * Check if a window instance is in this application's window list. + * + * @param window window to look for + * @return true if this window is in the list + */ + public final boolean hasWindow(final TWindow window) { + if (windows.size() == 0) { + return false; + } + for (TWindow w: windows) { + if (w == window) { + return true; + } + } + return false; + } + + /** + * Activate a window: bring it to the top and have it receive events. + * + * @param window the window to become the new active window + */ + public void activateWindow(final TWindow window) { + if (hasWindow(window) == false) { + /* + * Someone has a handle to a window I don't have. Ignore this + * request. + */ + return; + } + + assert (windows.size() > 0); + + if (window.isHidden()) { + // Unhiding will also activate. + showWindow(window); + return; + } + assert (window.isShown()); + + if (windows.size() == 1) { + assert (window == windows.get(0)); + if (activeWindow == null) { + activeWindow = window; + window.setZ(0); + activeWindow.setActive(true); + activeWindow.onFocus(); + } + + assert (window.isActive()); + assert (activeWindow == window); + return; + } + + if (activeWindow == window) { + assert (window.isActive()); + + // Window is already active, do nothing. + return; + } + + assert (!window.isActive()); + if (activeWindow != null) { + assert (activeWindow.getZ() == 0); + + activeWindow.onUnfocus(); + activeWindow.setActive(false); + activeWindow.setZ(window.getZ()); + } + activeWindow = window; + activeWindow.setZ(0); + activeWindow.setActive(true); + activeWindow.onFocus(); + return; + } + + /** + * Hide a window. + * + * @param window the window to hide + */ + public void hideWindow(final TWindow window) { + if (hasWindow(window) == false) { + /* + * Someone has a handle to a window I don't have. Ignore this + * request. + */ + return; + } + + assert (windows.size() > 0); + + if (!window.hidden) { + if (window == activeWindow) { + if (shownWindowCount() > 1) { + switchWindow(true); + } else { + activeWindow = null; + window.setActive(false); + window.onUnfocus(); + } + } + window.hidden = true; + window.onHide(); + } + } + + /** + * Show a window. + * + * @param window the window to show + */ + public void showWindow(final TWindow window) { + if (hasWindow(window) == false) { + /* + * Someone has a handle to a window I don't have. Ignore this + * request. + */ + return; + } + + assert (windows.size() > 0); + + if (window.hidden) { + window.hidden = false; + window.onShow(); + activateWindow(window); + } + } + /** * Close window. Note that the window's destructor is NOT called by this * method, instead the GC is assumed to do the cleanup. @@ -1191,13 +1430,21 @@ public class TApplication implements Runnable { * @param window the window to remove */ public final void closeWindow(final TWindow window) { + if (hasWindow(window) == false) { + /* + * Someone has a handle to a window I don't have. Ignore this + * request. + */ + return; + } + synchronized (windows) { int z = window.getZ(); window.setZ(-1); window.onUnfocus(); Collections.sort(windows); windows.remove(0); - TWindow activeWindow = null; + activeWindow = null; for (TWindow w: windows) { if (w.getZ() > z) { w.setZ(w.getZ() - 1); @@ -1233,6 +1480,13 @@ public class TApplication implements Runnable { secondaryEventHandler.notify(); } } + + // Permit desktop to be active if it is the only thing left. + if (desktop != null) { + if (windows.size() == 0) { + desktop.setActive(true); + } + } } /** @@ -1246,21 +1500,25 @@ public class TApplication implements Runnable { if (windows.size() < 2) { return; } + assert (activeWindow != null); synchronized (windows) { // Swap z/active between active window and the next in the list int activeWindowI = -1; for (int i = 0; i < windows.size(); i++) { - if (windows.get(i).isActive()) { + if (windows.get(i) == activeWindow) { + assert (activeWindow.isActive()); activeWindowI = i; break; + } else { + assert (!windows.get(0).isActive()); } } assert (activeWindowI >= 0); // Do not switch if a window is modal - if (windows.get(activeWindowI).isModal()) { + if (activeWindow.isModal()) { return; } @@ -1274,13 +1532,8 @@ public class TApplication implements Runnable { nextWindowI = activeWindowI - 1; } } - windows.get(activeWindowI).setActive(false); - windows.get(activeWindowI).setZ(windows.get(nextWindowI).getZ()); - windows.get(activeWindowI).onUnfocus(); - windows.get(nextWindowI).setZ(0); - windows.get(nextWindowI).setActive(true); - windows.get(nextWindowI).onFocus(); + activateWindow(windows.get(nextWindowI)); } // synchronized (windows) } @@ -1297,6 +1550,11 @@ public class TApplication implements Runnable { return; } + // Do not add the desktop to the window list. + if (window instanceof TDesktop) { + return; + } + synchronized (windows) { // Do not allow a modal window to spawn a non-modal window. If a // modal window is active, then this window will become modal @@ -1304,18 +1562,24 @@ public class TApplication implements Runnable { if (modalWindowActive()) { window.flags |= TWindow.MODAL; window.flags |= TWindow.CENTERED; + window.hidden = false; } - for (TWindow w: windows) { - if (w.isActive()) { - w.setActive(false); - w.onUnfocus(); + if (window.isShown()) { + for (TWindow w: windows) { + if (w.isActive()) { + w.setActive(false); + w.onUnfocus(); + } + w.setZ(w.getZ() + 1); } - w.setZ(w.getZ() + 1); } windows.add(window); - window.setZ(0); - window.setActive(true); - window.onFocus(); + if (window.isShown()) { + activeWindow = window; + activeWindow.setZ(0); + activeWindow.setActive(true); + activeWindow.onFocus(); + } if (((window.flags & TWindow.CENTERED) == 0) && smartWindowPlacement) { @@ -1323,6 +1587,11 @@ public class TApplication implements Runnable { doSmartPlacement(window); } } + + // Desktop cannot be active over any other window. + if (desktop != null) { + desktop.setActive(false); + } } /** @@ -1691,18 +1960,27 @@ public class TApplication implements Runnable { for (TWindow window: windows) { assert (!window.isModal()); + + 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()); return; } // We will be switching to another window assert (windows.get(0).isActive()); + assert (windows.get(0) == activeWindow); assert (!window.isActive()); - windows.get(0).onUnfocus(); - windows.get(0).setActive(false); - windows.get(0).setZ(window.getZ()); + activeWindow.onUnfocus(); + activeWindow.setActive(false); + activeWindow.setZ(window.getZ()); + activeWindow = window; window.setZ(0); window.setActive(true); window.onFocus(); @@ -2261,4 +2539,69 @@ public class TApplication implements Runnable { return box.getFilename(); } + /** + * Convenience function to create a new window and make it active. + * Window will be located at (0, 0). + * + * @param title window title, will be centered along the top border + * @param width width of window + * @param height height of window + */ + public final TWindow addWindow(final String title, final int width, + final int height) { + + TWindow window = new TWindow(this, title, 0, 0, width, height); + return window; + } + /** + * Convenience function to create a new window and make it active. + * Window will be located at (0, 0). + * + * @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 TWindow addWindow(final String title, + final int width, final int height, final int flags) { + + TWindow window = new TWindow(this, title, 0, 0, width, height, flags); + return window; + } + + /** + * Convenience function to create a new window and make it active. + * + * @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 final TWindow addWindow(final String title, + final int x, final int y, final int width, final int height) { + + TWindow window = new TWindow(this, title, x, y, width, height); + return window; + } + + /** + * 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 + * @param width width of window + * @param height height of window + * @param flags mask of RESIZABLE, CENTERED, or MODAL + */ + public final TWindow addWindow(final String title, + final int x, final int y, final int width, final int height, + final int flags) { + + TWindow window = new TWindow(this, title, x, y, width, height, flags); + return window; + } + }