X-Git-Url: https://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fjexer%2FTApplication.java;h=8786e693271aa6511c747a6d8eb016b640ef1ed7;hb=70f5b2bbcbac3df8bf855895ff51d63a5e9d333c;hp=b2ee5071a74284b42a6068efed531c4dc3c90039;hpb=928811d8e292801029b7b6605453524f65e9ebd9;p=fanfix.git diff --git a/src/jexer/TApplication.java b/src/jexer/TApplication.java index b2ee507..8786e69 100644 --- a/src/jexer/TApplication.java +++ b/src/jexer/TApplication.java @@ -34,8 +34,11 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.util.Collections; +import java.util.Date; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; import jexer.bits.CellAttributes; import jexer.bits.ColorTheme; @@ -84,9 +87,15 @@ public class TApplication { private int mouseY; /** - * Event queue that will be drained by either primary or secondary Fiber. + * Event queue that is filled by run(). */ - private List eventQueue; + private List fillEventQueue; + + /** + * Event queue that will be drained by either primary or secondary + * Thread. + */ + private List drainEventQueue; /** * Top-level menus in this application. @@ -103,6 +112,11 @@ public class TApplication { */ private TMenu activeMenu = null; + /** + * Active keyboard accelerators. + */ + private Map accelerators; + /** * Windows and widgets pull colors from this ColorTheme. */ @@ -122,6 +136,11 @@ public class TApplication { */ private List windows; + /** + * Timers that are being ticked. + */ + private List timers; + /** * When true, exit the application. */ @@ -190,13 +209,16 @@ public class TApplication { public TApplication(final InputStream input, final OutputStream output) throws UnsupportedEncodingException { - backend = new ECMA48Backend(input, output); - theme = new ColorTheme(); - desktopBottom = getScreen().getHeight() - 1; - eventQueue = new LinkedList(); - windows = new LinkedList(); - menus = new LinkedList(); - subMenus = new LinkedList(); + backend = new ECMA48Backend(input, output); + theme = new ColorTheme(); + desktopBottom = getScreen().getHeight() - 1; + fillEventQueue = new LinkedList(); + drainEventQueue = new LinkedList(); + windows = new LinkedList(); + menus = new LinkedList(); + subMenus = new LinkedList(); + timers = new LinkedList(); + accelerators = new HashMap(); } /** @@ -315,23 +337,39 @@ public class TApplication { * Run this application until it exits. */ public final void run() { - List events = new LinkedList(); - while (!quit) { // Timeout is in milliseconds, so default timeout after 1 second // of inactivity. int timeout = getSleepTime(1000); - if (eventQueue.size() > 0) { - // Do not wait if there are definitely events waiting to be - // processed or a screen redraw to do. - timeout = 0; + // See if there are any definitely events waiting to be processed + // or a screen redraw to do. If so, do not wait if there is no + // I/O coming in. + synchronized (drainEventQueue) { + if (drainEventQueue.size() > 0) { + timeout = 0; + } } + synchronized (fillEventQueue) { + if (fillEventQueue.size() > 0) { + timeout = 0; + } + } + + // Pull any pending I/O events + backend.getEvents(fillEventQueue, timeout); - // Pull any pending input events - backend.getEvents(events, timeout); - metaHandleEvents(events); - events.clear(); + // Dispatch each event to the appropriate handler, one at a time. + for (;;) { + TInputEvent event = null; + synchronized (fillEventQueue) { + if (fillEventQueue.size() == 0) { + break; + } + event = fillEventQueue.remove(0); + } + metaHandleEvent(event); + } // Process timers and call doIdle()'s doIdle(); @@ -364,92 +402,78 @@ public class TApplication { /** * Peek at certain application-level events, add to eventQueue, and wake - * up the consuming Fiber. + * up the consuming Thread. * - * @param events the input events to consume + * @param event the input event to consume */ - private void metaHandleEvents(final List events) { - - for (TInputEvent event: events) { - - /* - System.err.printf(String.format("metaHandleEvents event: %s\n", - event)); System.err.flush(); - */ + private void metaHandleEvent(final TInputEvent event) { - if (quit) { - // Do no more processing if the application is already trying - // to exit. - return; - } + /* + System.err.printf(String.format("metaHandleEvents event: %s\n", + event)); System.err.flush(); + */ - // DEBUG - if (event instanceof TKeypressEvent) { - TKeypressEvent keypress = (TKeypressEvent) event; - if (keypress.equals(kbAltX)) { - quit = true; - return; - } - } - // DEBUG + if (quit) { + // Do no more processing if the application is already trying + // to exit. + return; + } - // Special application-wide events ------------------------------- + // Special application-wide events ------------------------------- - // Abort everything - if (event instanceof TCommandEvent) { - TCommandEvent command = (TCommandEvent) event; - if (command.getCmd().equals(cmAbort)) { - quit = true; - return; - } + // Abort everything + if (event instanceof TCommandEvent) { + TCommandEvent command = (TCommandEvent) event; + if (command.getCmd().equals(cmAbort)) { + quit = true; + return; } + } - // Screen resize - if (event instanceof TResizeEvent) { - TResizeEvent resize = (TResizeEvent) event; - getScreen().setDimensions(resize.getWidth(), - resize.getHeight()); - desktopBottom = getScreen().getHeight() - 1; - repaint = true; - mouseX = 0; - mouseY = 0; - continue; - } + // Screen resize + if (event instanceof TResizeEvent) { + TResizeEvent resize = (TResizeEvent) event; + getScreen().setDimensions(resize.getWidth(), + resize.getHeight()); + desktopBottom = getScreen().getHeight() - 1; + repaint = true; + mouseX = 0; + mouseY = 0; + return; + } - // Peek at the mouse position - if (event instanceof TMouseEvent) { - TMouseEvent mouse = (TMouseEvent) event; - if ((mouseX != mouse.getX()) || (mouseY != mouse.getY())) { - mouseX = mouse.getX(); - mouseY = mouse.getY(); - drawMouse(); - } + // Peek at the mouse position + if (event instanceof TMouseEvent) { + TMouseEvent mouse = (TMouseEvent) event; + if ((mouseX != mouse.getX()) || (mouseY != mouse.getY())) { + mouseX = mouse.getX(); + mouseY = mouse.getY(); + drawMouse(); } + } - // TODO: change to two separate threads - primaryHandleEvent(event); + // TODO: change to two separate threads + primaryHandleEvent(event); - /* + /* - // Put into the main queue - addEvent(event); + // Put into the main queue + addEvent(event); - // Have one of the two consumer Fibers peel the events off - // the queue. - if (secondaryEventFiber !is null) { - assert(secondaryEventFiber.state == Fiber.State.HOLD); + // Have one of the two consumer Fibers peel the events off + // the queue. + if (secondaryEventFiber !is null) { + assert(secondaryEventFiber.state == Fiber.State.HOLD); - // Wake up the secondary handler for these events - secondaryEventFiber.call(); - } else { - assert(primaryEventFiber.state == Fiber.State.HOLD); + // Wake up the secondary handler for these events + secondaryEventFiber.call(); + } else { + assert(primaryEventFiber.state == Fiber.State.HOLD); - // Wake up the primary handler for these events - primaryEventFiber.call(); - } - */ - - } // for (TInputEvent event: events) + // Wake up the primary handler for these events + primaryEventFiber.call(); + } + */ } @@ -511,15 +535,16 @@ public class TApplication { return; } - /* - TODO - if (event instanceof TKeypressEvent) { TKeypressEvent keypress = (TKeypressEvent) event; + // See if this key matches an accelerator, and if so dispatch the // menu event. TKeypress keypressLowercase = keypress.getKey().toLowerCase(); - TMenuItem item = accelerators.get(keypressLowercase); + TMenuItem item = null; + synchronized (accelerators) { + item = accelerators.get(keypressLowercase); + } if (item != null) { // Let the menu item dispatch item.dispatch(); @@ -531,7 +556,6 @@ public class TApplication { } } } - */ if (event instanceof TCommandEvent) { if (onCommand((TCommandEvent) event)) { @@ -578,28 +602,25 @@ public class TApplication { * 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; - foreach (t; timers) { - if (t.nextTick < now) { - t.tick(); - if (t.recurring == true) { - keepTimers ~= t; + Date now = new Date(); + List keepTimers = new LinkedList(); + for (TTimer timer: timers) { + if (timer.getNextTick().getTime() < now.getTime()) { + timer.tick(); + if (timer.recurring == true) { + keepTimers.add(timer); } } else { - keepTimers ~= t; + keepTimers.add(timer); } } timers = keepTimers; // Call onIdle's - foreach (w; windows) { - w.onIdle(); + for (TWindow window: windows) { + window.onIdle(); } - */ } /** @@ -609,24 +630,20 @@ public class TApplication { * @return number of milliseconds between now and the next timer event */ protected int getSleepTime(final int timeout) { - /* - auto now = Clock.currTime; - auto sleepTime = dur!("msecs")(timeout); - foreach (t; timers) { - if (t.nextTick < now) { + Date now = new Date(); + long sleepTime = timeout; + for (TTimer timer: timers) { + if (timer.getNextTick().getTime() < now.getTime()) { return 0; } - if ((t.nextTick > now) && - ((t.nextTick - now) < sleepTime) + if ((timer.getNextTick().getTime() > now.getTime()) + && ((timer.getNextTick().getTime() - now.getTime()) < sleepTime) ) { - sleepTime = t.nextTick - now; + sleepTime = timer.getNextTick().getTime() - now.getTime(); } } - assert(sleepTime.total!("msecs")() >= 0); - return cast(uint)sleepTime.total!("msecs")(); - */ - // TODO: fix timers. Until then, come back after 250 millis. - return 250; + assert (sleepTime >= 0); + return (int)sleepTime; } /** @@ -1022,10 +1039,10 @@ public class TApplication { */ protected boolean onMenu(final TMenuEvent menu) { - /* - TODO // Default: handle MID_EXIT - if (menu.id == TMenu.MID_EXIT) { + if (menu.getId() == TMenu.MID_EXIT) { + /* + TODO if (messageBox("Confirmation", "Exit application?", TMessageBox.Type.YESNO).result == TMessageBox.Result.YES) { quit = true; @@ -1033,30 +1050,36 @@ public class TApplication { // System.err.printf("onMenu MID_EXIT result: quit = %s\n", quit); repaint = true; return true; + */ + quit = true; + repaint = true; + return true; } + /* + TODO if (menu.id == TMenu.MID_SHELL) { openTerminal(0, 0, TWindow.Flag.RESIZABLE); repaint = true; return true; } + */ - if (menu.id == TMenu.MID_TILE) { + if (menu.getId() == TMenu.MID_TILE) { tileWindows(); repaint = true; return true; } - if (menu.id == TMenu.MID_CASCADE) { + if (menu.getId() == TMenu.MID_CASCADE) { cascadeWindows(); repaint = true; return true; } - if (menu.id == TMenu.MID_CLOSE_ALL) { + if (menu.getId() == TMenu.MID_CLOSE_ALL) { closeAllWindows(); repaint = true; return true; } - */ return false; } @@ -1101,11 +1124,13 @@ public class TApplication { */ public final void addAccelerator(final TMenuItem item, final TKeypress keypress) { - /* - TODO - assert((keypress in accelerators) is null); - accelerators[keypress] = item; - */ + + // System.err.printf("addAccelerator: key %s item %s\n", keypress, item); + + synchronized (accelerators) { + assert (accelerators.get(keypress) == null); + accelerators.put(keypress, item); + } } /** @@ -1125,10 +1150,9 @@ public class TApplication { * @param event new event to add to the queue */ public final void addMenuEvent(final TInputEvent event) { - /* - TODO - synchronize correctly - eventQueue ~= event; - */ + synchronized (fillEventQueue) { + fillEventQueue.add(event); + } closeMenu(); } @@ -1141,4 +1165,191 @@ public class TApplication { subMenus.add(menu); } + /** + * Convenience function to add a top-level menu. + * + * @param title menu title + * @return the new menu + */ + public final TMenu addMenu(String title) { + int x = 0; + int y = 0; + TMenu menu = new TMenu(this, x, y, title); + menus.add(menu); + recomputeMenuX(); + return menu; + } + + /** + * Convenience function to add a default "File" menu. + * + * @return the new menu + */ + public final TMenu addFileMenu() { + TMenu fileMenu = addMenu("&File"); + fileMenu.addDefaultItem(TMenu.MID_OPEN_FILE); + fileMenu.addSeparator(); + fileMenu.addDefaultItem(TMenu.MID_SHELL); + fileMenu.addDefaultItem(TMenu.MID_EXIT); + return fileMenu; + } + + /** + * Convenience function to add a default "Edit" menu. + * + * @return the new menu + */ + public final TMenu addEditMenu() { + TMenu editMenu = addMenu("&Edit"); + editMenu.addDefaultItem(TMenu.MID_CUT); + editMenu.addDefaultItem(TMenu.MID_COPY); + editMenu.addDefaultItem(TMenu.MID_PASTE); + editMenu.addDefaultItem(TMenu.MID_CLEAR); + return editMenu; + } + + /** + * Convenience function to add a default "Window" menu. + * + * @return the new menu + */ + final public TMenu addWindowMenu() { + TMenu windowMenu = addMenu("&Window"); + windowMenu.addDefaultItem(TMenu.MID_TILE); + windowMenu.addDefaultItem(TMenu.MID_CASCADE); + windowMenu.addDefaultItem(TMenu.MID_CLOSE_ALL); + windowMenu.addSeparator(); + windowMenu.addDefaultItem(TMenu.MID_WINDOW_MOVE); + windowMenu.addDefaultItem(TMenu.MID_WINDOW_ZOOM); + windowMenu.addDefaultItem(TMenu.MID_WINDOW_NEXT); + windowMenu.addDefaultItem(TMenu.MID_WINDOW_PREVIOUS); + windowMenu.addDefaultItem(TMenu.MID_WINDOW_CLOSE); + return windowMenu; + } + + /** + * Close all open windows. + */ + private void closeAllWindows() { + // Don't do anything if we are in the menu + if (activeMenu != null) { + return; + } + for (TWindow window: windows) { + closeWindow(window); + } + } + + /** + * Re-layout the open windows as non-overlapping tiles. This produces + * almost the same results as Turbo Pascal 7.0's IDE. + */ + private void tileWindows() { + // Don't do anything if we are in the menu + if (activeMenu != null) { + return; + } + int z = windows.size(); + if (z == 0) { + return; + } + int a = 0; + int b = 0; + a = (int)(Math.sqrt(z)); + int c = 0; + while (c < a) { + b = (z - c) / a; + if (((a * b) + c) == z) { + break; + } + c++; + } + assert (a > 0); + assert (b > 0); + assert (c < a); + 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 sorted = new LinkedList(windows); + Collections.sort(sorted); + Collections.reverse(sorted); + for (int i = 0; i < sorted.size(); i++) { + int logicalX = i / b; + int logicalY = i % b; + if (i >= ((a - 1) * b)) { + logicalX = a - 1; + logicalY = i - ((a - 1) * b); + } + + TWindow w = sorted.get(i); + w.setX(logicalX * newWidth); + w.setWidth(newWidth); + if (i >= ((a - 1) * b)) { + w.setY((logicalY * newHeight2) + 1); + w.setHeight(newHeight2); + } else { + w.setY((logicalY * newHeight1) + 1); + w.setHeight(newHeight1); + } + } + } + + /** + * Re-layout the open windows as overlapping cascaded windows. + */ + private void cascadeWindows() { + // Don't do anything if we are in the menu + if (activeMenu != null) { + return; + } + int x = 0; + int y = 1; + List sorted = new LinkedList(windows); + Collections.sort(sorted); + Collections.reverse(sorted); + for (TWindow window: sorted) { + window.setX(x); + window.setY(y); + x++; + y++; + if (x > getScreen().getWidth()) { + x = 0; + } + if (y >= getScreen().getHeight()) { + y = 1; + } + } + } + + /** + * Convenience function to add a timer. + * + * @param duration number of milliseconds to wait between ticks + * @param recurring if true, re-schedule this timer after every tick + * @param action function to call when button is pressed + */ + public final TTimer addTimer(final long duration, final boolean recurring, + final TAction action) { + + TTimer timer = new TTimer(duration, recurring, action); + synchronized (timers) { + timers.add(timer); + } + return timer; + } + + /** + * Convenience function to remove a timer. + * + * @param timer timer to remove + */ + public final void removeTimer(final TTimer timer) { + synchronized (timers) { + timers.remove(timer); + } + } + }