X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fjexer%2FTApplication.java;h=ef9bd04028f1b8ffc4b60863a8a1ea9581fc6900;hb=a06459bd6b0e65c9b590dbdf6ed9349043119215;hp=e9256a1b74955f6168290f372f56d8d5e4f8df0f;hpb=7b5261bc5b641e0769902f014e3b21f61050b02b;p=fanfix.git diff --git a/src/jexer/TApplication.java b/src/jexer/TApplication.java index e9256a1..ef9bd04 100644 --- a/src/jexer/TApplication.java +++ b/src/jexer/TApplication.java @@ -33,6 +33,7 @@ package jexer; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; +import java.util.Collections; import java.util.LinkedList; import java.util.List; @@ -46,6 +47,7 @@ import jexer.event.TMouseEvent; import jexer.event.TResizeEvent; import jexer.backend.Backend; import jexer.backend.ECMA48Backend; +import jexer.io.Screen; import static jexer.TCommand.*; import static jexer.TKeypress.*; @@ -59,6 +61,15 @@ public class TApplication { */ private Backend backend; + /** + * Get the Screen. + * + * @return the Screen + */ + public final Screen getScreen() { + return backend.getScreen(); + } + /** * Actual mouse coordinate X. */ @@ -88,32 +99,62 @@ public class TApplication { return theme; } + /** + * The top-level windows (but not menus). + */ + List windows; + /** * When true, exit the application. */ - public boolean quit = false; + private boolean quit = false; /** * When true, repaint the entire screen. */ - public boolean repaint = true; + private boolean repaint = true; + + /** + * Request full repaint on next screen refresh. + */ + public void setRepaint() { + repaint = true; + } /** * When true, just flush updates from the screen. */ - public boolean flush = false; + private boolean flush = false; /** * Y coordinate of the top edge of the desktop. For now this is a * constant. Someday it would be nice to have a multi-line menu or * toolbars. */ - public static final int desktopTop = 1; + private static final int desktopTop = 1; + + /** + * Get Y coordinate of the top edge of the desktop. + * + * @return Y coordinate of the top edge of the desktop + */ + public final int getDesktopTop() { + return desktopTop; + } /** * Y coordinate of the bottom edge of the desktop. */ - public int desktopBottom; + private int desktopBottom; + + /** + * Get Y coordinate of the bottom edge of the desktop. + * + * @return Y coordinate of the bottom edge of the desktop + */ + public final int getDesktopBottom() { + return desktopBottom; + } /** * Public constructor. @@ -133,28 +174,24 @@ public class TApplication { backend = new ECMA48Backend(input, output); theme = new ColorTheme(); - desktopBottom = backend.getScreen().getHeight() - 1; + desktopBottom = getScreen().getHeight() - 1; eventQueue = new LinkedList(); + windows = new LinkedList(); } /** * Invert the cell at the mouse pointer position. */ private void drawMouse() { - CellAttributes attr = backend.getScreen().getAttrXY(mouseX, mouseY); + CellAttributes attr = getScreen().getAttrXY(mouseX, mouseY); attr.setForeColor(attr.getForeColor().invert()); attr.setBackColor(attr.getBackColor().invert()); - backend.getScreen().putAttrXY(mouseX, mouseY, attr, false); + getScreen().putAttrXY(mouseX, mouseY, attr, false); flush = true; - /* - if (windows.length == 0) { + if (windows.size() == 0) { repaint = true; } - */ - // TODO: remove this repaint after the above if (windows.length == 0) - // can be used again. - repaint = true; } /** @@ -175,31 +212,32 @@ public class TApplication { boolean cursor = false; // Start with a clean screen - backend.getScreen().clear(); + getScreen().clear(); // Draw the background CellAttributes background = theme.getColor("tapplication.background"); - backend.getScreen().putAll(GraphicsChars.HATCH, background); + getScreen().putAll(GraphicsChars.HATCH, background); - /* // Draw each window in reverse Z order - TWindow [] sorted = windows.dup; - sorted.sort.reverse; - foreach (w; sorted) { - w.drawChildren(); + List sorted = new LinkedList(windows); + Collections.sort(sorted); + Collections.reverse(sorted); + for (TWindow window: sorted) { + window.drawChildren(); } + /* // Draw the blank menubar line - reset the screen clipping first so // it won't trim it out. - backend.getScreen().resetClipping(); - backend.getScreen().hLineXY(0, 0, backend.getScreen().getWidth(), ' ', + getScreen().resetClipping(); + getScreen().hLineXY(0, 0, getScreen().getWidth(), ' ', theme.getColor("tmenu")); // Now draw the menus. int x = 1; - foreach (m; menus) { + for (TMenu m: menus) { CellAttributes menuColor; CellAttributes menuMnemonicColor; - if (m.active) { + if (menu.active) { menuColor = theme.getColor("tmenu.highlighted"); menuMnemonicColor = theme.getColor("tmenu.mnemonic.highlighted"); } else { @@ -207,38 +245,37 @@ public class TApplication { menuMnemonicColor = theme.getColor("tmenu.mnemonic"); } // Draw the menu title - backend.getScreen().hLineXY(x, 0, cast(int)m.title.length + 2, ' ', + getScreen().hLineXY(x, 0, menu.title.length() + 2, ' ', menuColor); - backend.getScreen().putStrXY(x + 1, 0, m.title, menuColor); + getScreen().putStrXY(x + 1, 0, menu.title, menuColor); // Draw the highlight character - backend.getScreen().putCharXY(x + 1 + m.mnemonic.shortcutIdx, 0, + getScreen().putCharXY(x + 1 + m.mnemonic.shortcutIdx, 0, m.mnemonic.shortcut, menuMnemonicColor); - if (m.active) { - m.drawChildren(); + if (menu.active) { + menu.drawChildren(); // Reset the screen clipping so we can draw the next title. - backend.getScreen().resetClipping(); + getScreen().resetClipping(); } - x += m.title.length + 2; + x += menu.title.length + 2; } - foreach (m; subMenus) { + for (TMenu menu: subMenus) { // Reset the screen clipping so we can draw the next sub-menu. - backend.getScreen().resetClipping(); - m.drawChildren(); + getScreen().resetClipping(); + menu.drawChildren(); } - */ + */ // Draw the mouse pointer drawMouse(); - /* // Place the cursor if it is visible TWidget activeWidget = null; - if (sorted.length > 0) { - activeWidget = sorted[$ - 1].getActiveChild(); - if (activeWidget.hasCursor) { - backend.getScreen().putCursor(true, activeWidget.getCursorAbsoluteX(), + if (sorted.size() > 0) { + activeWidget = sorted.get(sorted.size() - 1).getActiveChild(); + if (activeWidget.visibleCursor()) { + getScreen().putCursor(true, activeWidget.getCursorAbsoluteX(), activeWidget.getCursorAbsoluteY()); cursor = true; } @@ -246,9 +283,8 @@ public class TApplication { // Kill the cursor if (cursor == false) { - backend.getScreen().hideCursor(); + getScreen().hideCursor(); } - */ // Flush the screen contents backend.flushScreen(); @@ -332,7 +368,7 @@ public class TApplication { // DEBUG if (event instanceof TKeypressEvent) { TKeypressEvent keypress = (TKeypressEvent) event; - if (keypress.key.equals(kbAltX)) { + if (keypress.equals(kbAltX)) { quit = true; return; } @@ -353,9 +389,9 @@ public class TApplication { // Screen resize if (event instanceof TResizeEvent) { TResizeEvent resize = (TResizeEvent) event; - backend.getScreen().setDimensions(resize.getWidth(), + getScreen().setDimensions(resize.getWidth(), resize.getHeight()); - desktopBottom = backend.getScreen().getHeight() - 1; + desktopBottom = getScreen().getHeight() - 1; repaint = true; mouseX = 0; mouseY = 0; @@ -365,13 +401,16 @@ public class TApplication { // Peek at the mouse position if (event instanceof TMouseEvent) { TMouseEvent mouse = (TMouseEvent) event; - if ((mouseX != mouse.x) || (mouseY != mouse.y)) { - mouseX = mouse.x; - mouseY = mouse.y; + if ((mouseX != mouse.getX()) || (mouseY != mouse.getY())) { + mouseX = mouse.getX(); + mouseY = mouse.getY(); drawMouse(); } } + // TODO: change to two separate threads + handleEvent(event); + /* // Put into the main queue @@ -396,11 +435,115 @@ public class TApplication { } + /** + * Dispatch one event to the appropriate widget or application-level + * event handler. + * + * @param event the input event to consume + */ + private final void handleEvent(TInputEvent event) { + + /* + // std.stdio.stderr.writefln("Handle event: %s", event); + + // Special application-wide events ----------------------------------- + + // Peek at the mouse position + if (auto mouse = cast(TMouseEvent)event) { + // See if we need to switch focus to another window or the menu + checkSwitchFocus(mouse); + } + + // Handle menu events + if ((activeMenu !is null) && (!cast(TCommandEvent)event)) { + TMenu menu = activeMenu; + if (auto mouse = cast(TMouseEvent)event) { + + while (subMenus.length > 0) { + TMenu subMenu = subMenus[$ - 1]; + if (subMenu.mouseWouldHit(mouse)) { + break; + } + if ((mouse.type == TMouseEvent.Type.MOUSE_MOTION) && + (!mouse.mouse1) && + (!mouse.mouse2) && + (!mouse.mouse3) && + (!mouse.mouseWheelUp) && + (!mouse.mouseWheelDown) + ) { + break; + } + // We navigated away from a sub-menu, so close it + closeSubMenu(); + } + + // Convert the mouse relative x/y to menu coordinates + assert(mouse.x == mouse.absoluteX); + assert(mouse.y == mouse.absoluteY); + if (subMenus.length > 0) { + menu = subMenus[$ - 1]; + } + mouse.x -= menu.x; + mouse.y -= menu.y; + } + menu.handleEvent(event); + return; + } + + if (auto keypress = cast(TKeypressEvent)event) { + // See if this key matches an accelerator, and if so dispatch the + // menu event. + TKeypress keypressLowercase = toLower(keypress.key); + TMenuItem *item = (keypressLowercase in accelerators); + if (item !is null) { + // Let the menu item dispatch + item.dispatch(); + return; + } else { + // Handle the keypress + if (onKeypress(keypress)) { + return; + } + } + } + + if (auto cmd = cast(TCommandEvent)event) { + if (onCommand(cmd)) { + return; + } + } + + if (auto menu = cast(TMenuEvent)event) { + if (onMenu(menu)) { + return; + } + } + */ + + // Dispatch events to the active window ------------------------------- + for (TWindow window: windows) { + if (window.active) { + 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.x); + mouse.setY(mouse.getY() - window.y); + } + // System.err("TApplication dispatch event: %s\n", event); + window.handleEvent(event); + break; + } + } + } + /** * Do stuff when there is no user input. */ private void doIdle() { /* + TODO // Now run any timers that have timed out auto now = Clock.currTime; TTimer [] keepTimers; @@ -450,4 +593,133 @@ public class TApplication { return 250; } + /** + * Close window. Note that the window's destructor is NOT called by this + * method, instead the GC is assumed to do the cleanup. + * + * @param window the window to remove + */ + public final void closeWindow(final TWindow window) { + /* + TODO + + uint z = window.z; + window.z = -1; + windows.sort; + windows = windows[1 .. $]; + TWindow activeWindow = null; + foreach (w; windows) { + if (w.z > z) { + w.z--; + if (w.z == 0) { + w.active = true; + assert(activeWindow is null); + activeWindow = w; + } else { + w.active = false; + } + } + } + + // Perform window cleanup + window.onClose(); + + // Refresh screen + repaint = true; + + // Check if we are closing a TMessageBox or similar + if (secondaryEventReceiver !is null) { + assert(secondaryEventFiber !is null); + + // Do not send events to the secondaryEventReceiver anymore, the + // window is closed. + secondaryEventReceiver = null; + + // Special case: if this is called while executing on a + // secondaryEventFiber, call it so that widgetEventHandler() can + // terminate. + if (secondaryEventFiber.state == Fiber.State.HOLD) { + secondaryEventFiber.call(); + } + secondaryEventFiber = null; + + // Unfreeze the logic in handleEvent() + if (primaryEventFiber.state == Fiber.State.HOLD) { + primaryEventFiber.call(); + } + } + */ + } + + /** + * Switch to the next window. + * + * @param forward if true, then switch to the next window in the list, + * otherwise switch to the previous window in the list + */ + public final void switchWindow(final boolean forward) { + /* + TODO + + // Only switch if there are multiple windows + if (windows.length < 2) { + return; + } + + // Swap z/active between active window and the next in the + // list + ptrdiff_t activeWindowI = -1; + for (auto i = 0; i < windows.length; i++) { + if (windows[i].active) { + activeWindowI = i; + break; + } + } + assert(activeWindowI >= 0); + + // Do not switch if a window is modal + if (windows[activeWindowI].isModal()) { + return; + } + + size_t nextWindowI; + if (forward) { + nextWindowI = (activeWindowI + 1) % windows.length; + } else { + if (activeWindowI == 0) { + nextWindowI = windows.length - 1; + } else { + nextWindowI = activeWindowI - 1; + } + } + windows[activeWindowI].active = false; + windows[activeWindowI].z = windows[nextWindowI].z; + windows[nextWindowI].z = 0; + windows[nextWindowI].active = true; + + // Refresh + repaint = true; + */ + } + + /** + * Add a window to my window list and make it active. + * + * @param window new window to add + */ + public final void addWindow(final TWindow window) { + // Do not allow a modal window to spawn a non-modal window + if ((windows.size() > 0) && (windows.get(0).isModal())) { + assert (window.isModal()); + } + for (TWindow w: windows) { + w.active = false; + w.setZ(w.getZ() + 1); + } + windows.add(window); + window.active = true; + window.setZ(0); + } + + }