X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fjexer%2FTApplication.java;h=4b0efa9412f947a739a651ae802b7e97b68b78d2;hb=0ee88b6d705993df0d9e32cdc08c619605c7d75c;hp=ff9c19a0b00c9b7d18fa554fc80787a4edb9dabc;hpb=2ce6dab2bbd951e6d0f09f94759efda5ee4b65ac;p=nikiroo-utils.git diff --git a/src/jexer/TApplication.java b/src/jexer/TApplication.java index ff9c19a..4b0efa9 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"), @@ -79,6 +79,12 @@ public class TApplication implements Runnable { */ private static final boolean debugEvents = false; + /** + * If true, do "smart placement" on new windows that are not specified to + * be centered. + */ + private static final boolean smartWindowPlacement = true; + /** * Two backend types are available. */ @@ -521,6 +527,34 @@ 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; + } + // ------------------------------------------------------------------------ // General behavior ------------------------------------------------------- // ------------------------------------------------------------------------ @@ -644,6 +678,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); @@ -708,14 +743,18 @@ 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(); @@ -762,7 +801,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()); @@ -945,6 +987,10 @@ public class TApplication implements Runnable { oldMouseX = 0; oldMouseY = 0; } + if (desktop != null) { + desktop.setDimensions(0, 0, resize.getWidth(), + resize.getHeight() - 1); + } return; } @@ -1076,6 +1122,7 @@ public class TApplication implements Runnable { } // Dispatch events to the active window ------------------------------- + boolean dispatchToDesktop = true; for (TWindow window: windows) { if (window.isActive()) { if (event instanceof TMouseEvent) { @@ -1085,7 +1132,14 @@ public class TApplication implements Runnable { assert (mouse.getY() == mouse.getAbsoluteY()); mouse.setX(mouse.getX() - window.getX()); mouse.setY(mouse.getY() - window.getY()); + + if (window.mouseWouldHit(mouse)) { + dispatchToDesktop = false; + } + } else if (event instanceof TKeypressEvent) { + dispatchToDesktop = false; } + if (debugEvents) { System.err.printf("TApplication dispatch event: %s\n", event); @@ -1094,7 +1148,14 @@ public class TApplication implements Runnable { break; } } + 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 @@ -1285,12 +1346,24 @@ public class TApplication implements Runnable { * @param window new window to add */ public final void addWindow(final TWindow window) { + + // Do not add menu windows to the window list. + if (window instanceof TMenu) { + 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 // too. if (modalWindowActive()) { window.flags |= TWindow.MODAL; + window.flags |= TWindow.CENTERED; } for (TWindow w: windows) { if (w.isActive()) { @@ -1303,6 +1376,12 @@ public class TApplication implements Runnable { window.setZ(0); window.setActive(true); window.onFocus(); + + if (((window.flags & TWindow.CENTERED) == 0) + && smartWindowPlacement) { + + doSmartPlacement(window); + } } } @@ -1424,6 +1503,131 @@ public class TApplication implements Runnable { } } + /** + * Place a window to minimize its overlap with other windows. + * + * @param window the window to place + */ + public final void doSmartPlacement(final TWindow window) { + // This is a pretty dumb algorithm, but seems to work. The hardest + // part is computing these "overlap" values seeking a minimum average + // overlap. + int xMin = 0; + int yMin = desktopTop; + int xMax = getScreen().getWidth() - window.getWidth() + 1; + int yMax = desktopBottom - window.getHeight() + 1; + if (xMax < xMin) { + xMax = xMin; + } + if (yMax < yMin) { + yMax = yMin; + } + + if ((xMin == xMax) && (yMin == yMax)) { + // No work to do, bail out. + return; + } + + // Compute the overlap matrix without the new window. + int width = getScreen().getWidth(); + int height = getScreen().getHeight(); + int overlapMatrix[][] = new int[width][height]; + for (TWindow w: windows) { + if (window == w) { + continue; + } + for (int x = w.getX(); x < w.getX() + w.getWidth(); x++) { + if (x == width) { + continue; + } + for (int y = w.getY(); y < w.getY() + w.getHeight(); y++) { + if (y == height) { + continue; + } + overlapMatrix[x][y]++; + } + } + } + + long oldOverlapTotal = 0; + long oldOverlapN = 0; + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + oldOverlapTotal += overlapMatrix[x][y]; + if (overlapMatrix[x][y] > 0) { + oldOverlapN++; + } + } + } + + + double oldOverlapAvg = (double) oldOverlapTotal / (double) oldOverlapN; + boolean first = true; + int windowX = window.getX(); + int windowY = window.getY(); + + // For each possible (x, y) position for the new window, compute a + // new overlap matrix. + for (int x = xMin; x < xMax; x++) { + for (int y = yMin; y < yMax; y++) { + + // Start with the matrix minus this window. + int newMatrix[][] = new int[width][height]; + for (int mx = 0; mx < width; mx++) { + for (int my = 0; my < height; my++) { + newMatrix[mx][my] = overlapMatrix[mx][my]; + } + } + + // Add this window's values to the new overlap matrix. + long newOverlapTotal = 0; + long newOverlapN = 0; + // Start by adding each new cell. + for (int wx = x; wx < x + window.getWidth(); wx++) { + if (wx == width) { + continue; + } + for (int wy = y; wy < y + window.getHeight(); wy++) { + if (wy == height) { + continue; + } + newMatrix[wx][wy]++; + } + } + // Now figure out the new value for total coverage. + for (int mx = 0; mx < width; mx++) { + for (int my = 0; my < height; my++) { + newOverlapTotal += newMatrix[x][y]; + if (newMatrix[mx][my] > 0) { + newOverlapN++; + } + } + } + double newOverlapAvg = (double) newOverlapTotal / (double) newOverlapN; + + if (first) { + // First time: just record what we got. + oldOverlapAvg = newOverlapAvg; + first = false; + } else { + // All other times: pick a new best (x, y) and save the + // overlap value. + if (newOverlapAvg < oldOverlapAvg) { + windowX = x; + windowY = y; + oldOverlapAvg = newOverlapAvg; + } + } + + } // for (int x = xMin; x < xMax; x++) + + } // for (int y = yMin; y < yMax; y++) + + // Finally, set the window's new coordinates. + window.setX(windowX); + window.setY(windowY); + } + // ------------------------------------------------------------------------ // TMenu management ------------------------------------------------------- // ------------------------------------------------------------------------