+ }
+ }
+
+ /**
+ * Re-layout the open windows as overlapping cascaded windows.
+ */
+ private void cascadeWindows() {
+ synchronized (windows) {
+ // Don't do anything if we are in the menu
+ if (activeMenu != null) {
+ return;
+ }
+ int x = 0;
+ int y = 1;
+ List<TWindow> sorted = new LinkedList<TWindow>(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;
+ }
+ }
+ }
+ }
+
+ /**
+ * 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 -------------------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Check if a mouse event would hit either the active menu or any open
+ * sub-menus.
+ *
+ * @param mouse mouse event
+ * @return true if the mouse would hit the active menu or an open
+ * sub-menu
+ */
+ private boolean mouseOnMenu(final TMouseEvent mouse) {
+ assert (activeMenu != null);
+ List<TMenu> menus = new LinkedList<TMenu>(subMenus);
+ Collections.reverse(menus);
+ for (TMenu menu: menus) {
+ if (menu.mouseWouldHit(mouse)) {
+ return true;
+ }
+ }
+ return activeMenu.mouseWouldHit(mouse);
+ }
+
+ /**
+ * See if we need to switch window or activate the menu based on
+ * a mouse click.
+ *
+ * @param mouse mouse event
+ */
+ private void checkSwitchFocus(final TMouseEvent mouse) {
+
+ if ((mouse.getType() == TMouseEvent.Type.MOUSE_DOWN)
+ && (activeMenu != null)
+ && (mouse.getAbsoluteY() != 0)
+ && (!mouseOnMenu(mouse))
+ ) {
+ // They clicked outside the active menu, turn it off
+ activeMenu.setActive(false);
+ activeMenu = null;
+ for (TMenu menu: subMenus) {
+ menu.setActive(false);
+ }
+ subMenus.clear();
+ // Continue checks
+ }
+
+ // See if they hit the menu bar
+ if ((mouse.getType() == TMouseEvent.Type.MOUSE_DOWN)
+ && (mouse.isMouse1())
+ && (!modalWindowActive())
+ && (mouse.getAbsoluteY() == 0)
+ ) {
+
+ for (TMenu menu: subMenus) {
+ menu.setActive(false);
+ }
+ subMenus.clear();
+
+ // They selected the menu, go activate it
+ for (TMenu menu: menus) {
+ if ((mouse.getAbsoluteX() >= menu.getX())
+ && (mouse.getAbsoluteX() < menu.getX()
+ + menu.getTitle().length() + 2)
+ ) {
+ menu.setActive(true);
+ activeMenu = menu;
+ } else {
+ menu.setActive(false);
+ }
+ }
+ return;