*
* 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"),
*/
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.
*/
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 -------------------------------------------------------
// ------------------------------------------------------------------------
timers = new LinkedList<TTimer>();
accelerators = new HashMap<TKeypress, TMenuItem>();
menuItems = new ArrayList<TMenuItem>();
+ desktop = new TDesktop(this);
// Setup the main consumer thread
primaryEventHandler = new WidgetEventHandler(this, true);
// 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<TWindow> sorted = new LinkedList<TWindow>(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();
}
// 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());
oldMouseX = 0;
oldMouseY = 0;
}
+ if (desktop != null) {
+ desktop.setDimensions(0, 0, resize.getWidth(),
+ resize.getHeight() - 1);
+ }
return;
}
}
// Dispatch events to the active window -------------------------------
+ boolean dispatchToDesktop = true;
for (TWindow window: windows) {
if (window.isActive()) {
if (event instanceof TMouseEvent) {
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);
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
* @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()) {
window.setZ(0);
window.setActive(true);
window.onFocus();
+
+ if (((window.flags & TWindow.CENTERED) == 0)
+ && smartWindowPlacement) {
+
+ doSmartPlacement(window);
+ }
}
}
}
}
+ /**
+ * 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 -------------------------------------------------------
// ------------------------------------------------------------------------