LeastOverlap placement option (Fix bug 5385)
[dana/openbox.git] / openbox / place.c
index efcec7e..579c56b 100644 (file)
@@ -25,6 +25,7 @@
 #include "config.h"
 #include "dock.h"
 #include "debug.h"
+#include "overlap.h"
 
 extern ObDock *dock;
 
@@ -37,27 +38,29 @@ static Rect *pick_pointer_head(ObClient *c)
 
    When a window is being placed in the FOREGROUND, use a monitor chosen in
    the following order:
-   1. same monitor as parent
-   2. primary monitor if placement=PRIMARY
+   1. per-app settings
+   2. same monitor as parent
+   3. primary monitor if placement=PRIMARY
       active monitor if placement=ACTIVE
       pointer monitor if placement=MOUSE
-   3. primary monitor
-   4. other monitors where the window has group members on the same desktop
-   5. other monitors where the window has group members on other desktops
-   6. other monitors
+   4. primary monitor
+   5. other monitors where the window has group members on the same desktop
+   6. other monitors where the window has group members on other desktops
+   7. other monitors
 
    When a window is being placed in the BACKGROUND, use a monitor chosen in the
    following order:
-   1. same monitor as parent
-   2. other monitors where the window has group members on the same desktop
-    2a. primary monitor in this set
-    2b. other monitors in this set
-   3. other monitors where the window has group members on other desktops
+   1. per-app settings
+   2. same monitor as parent
+   3. other monitors where the window has group members on the same desktop
     3a. primary monitor in this set
     3b. other monitors in this set
-   4. other monitors
+   4. other monitors where the window has group members on other desktops
     4a. primary monitor in this set
     4b. other monitors in this set
+   5. other monitors
+    5a. primary monitor in this set
+    5b. other monitors in this set
 */
 
 /*! One for each possible head, used to sort them in order of precedence. */
@@ -73,6 +76,7 @@ enum {
     HEAD_PRIMARY = 1 << 2, /* primary monitor */
     HEAD_GROUP_DESK = 1 << 3, /* has a group member on the same desktop */
     HEAD_GROUP = 1 << 4, /* has a group member on another desktop */
+    HEAD_PERAPP = 1 << 5, /* chosen by per-app settings */
 };
 
 gint cmp_foreground(const void *a, const void *b)
@@ -83,6 +87,10 @@ gint cmp_foreground(const void *a, const void *b)
 
     if (h1->monitor == h2->monitor) return 0;
 
+    if (h1->flags & HEAD_PERAPP) --i;
+    if (h2->flags & HEAD_PERAPP) ++i;
+    if (i) return i;
+
     if (h1->flags & HEAD_PARENT) --i;
     if (h2->flags & HEAD_PARENT) ++i;
     if (i) return i;
@@ -114,6 +122,10 @@ gint cmp_background(const void *a, const void *b)
 
     if (h1->monitor == h2->monitor) return 0;
 
+    if (h1->flags & HEAD_PERAPP) --i;
+    if (h2->flags & HEAD_PERAPP) ++i;
+    if (i) return i;
+
     if (h1->flags & HEAD_PARENT) --i;
     if (h2->flags & HEAD_PARENT) ++i;
     if (i) return i;
@@ -144,7 +156,8 @@ gint cmp_background(const void *a, const void *b)
 }
 
 /*! Pick a monitor to place a window on. */
-static Rect *pick_head(ObClient *c, gboolean foreground)
+static Rect *pick_head(ObClient *c, gboolean foreground,
+                       ObAppSettings *settings)
 {
     Rect *area;
     ObPlaceHead *choice;
@@ -159,16 +172,18 @@ static Rect *pick_head(ObClient *c, gboolean foreground)
     }
 
     /* find monitors with group members */
-    for (it = c->group->members; it; it = g_slist_next(it)) {
-        ObClient *itc = it->data;
-        if (itc != c) {
-            guint m = client_monitor(itc);
-
-            if (m < screen_num_monitors) {
-                if (screen_compare_desktops(itc->desktop, c->desktop))
-                    choice[m].flags |= HEAD_GROUP_DESK;
-                else
-                    choice[m].flags |= HEAD_GROUP;
+    if (c->group) {
+        for (it = c->group->members; it; it = g_slist_next(it)) {
+            ObClient *itc = it->data;
+            if (itc != c) {
+                guint m = client_monitor(itc);
+
+                if (m < screen_num_monitors) {
+                    if (screen_compare_desktops(itc->desktop, c->desktop))
+                        choice[m].flags |= HEAD_GROUP_DESK;
+                    else
+                        choice[m].flags |= HEAD_GROUP;
+                }
             }
         }
     }
@@ -178,6 +193,33 @@ static Rect *pick_head(ObClient *c, gboolean foreground)
         choice[i].flags |= HEAD_PRIMARY;
         if (config_place_monitor == OB_PLACE_MONITOR_PRIMARY)
             choice[i].flags |= HEAD_PLACED;
+        if (settings &&
+            settings->monitor_type == OB_PLACE_MONITOR_PRIMARY)
+            choice[i].flags |= HEAD_PERAPP;
+    }
+
+    i = screen_monitor_active();
+    if (i < screen_num_monitors) {
+        if (config_place_monitor == OB_PLACE_MONITOR_ACTIVE)
+            choice[i].flags |= HEAD_PLACED;
+        if (settings &&
+            settings->monitor_type == OB_PLACE_MONITOR_ACTIVE)
+            choice[i].flags |= HEAD_PERAPP;
+    }
+
+    i = screen_monitor_pointer();
+    if (i < screen_num_monitors) {
+        if (config_place_monitor == OB_PLACE_MONITOR_MOUSE)
+            choice[i].flags |= HEAD_PLACED;
+        if (settings &&
+            settings->monitor_type == OB_PLACE_MONITOR_MOUSE)
+            choice[i].flags |= HEAD_PERAPP;
+    }
+
+    if (settings) {
+        i = settings->monitor - 1;
+        if (i < screen_num_monitors)
+            choice[i].flags |= HEAD_PERAPP;
     }
 
     /* direct parent takes highest precedence */
@@ -438,27 +480,15 @@ static gboolean place_under_mouse(ObClient *client, gint *x, gint *y)
     return TRUE;
 }
 
-static gboolean place_per_app_setting(ObClient *client, gint *x, gint *y,
+static gboolean place_per_app_setting(ObClient *client, Rect *screen,
+                                      gint *x, gint *y,
                                       ObAppSettings *settings)
 {
-    Rect *screen = NULL;
-
     if (!settings || (settings && !settings->pos_given))
         return FALSE;
 
     ob_debug("placing by per-app settings");
 
-    /* Find which head the pointer is on */
-    if (settings->monitor == 0)
-        /* this can return NULL */
-        screen = pick_pointer_head(client);
-    else {
-        guint m = settings->monitor;
-        if (m < 1 || m > screen_num_monitors)
-            m = screen_monitor_primary(TRUE) + 1;
-        screen = screen_area(client->desktop, m - 1, NULL);
-    }
-
     if (settings->position.x.center)
         *x = screen->x + screen->width / 2 - client->area.width / 2;
     else if (settings->position.x.opposite)
@@ -479,7 +509,6 @@ static gboolean place_per_app_setting(ObClient *client, gint *x, gint *y,
     if (settings->position.y.denom)
         *y = (*y * screen->height) / settings->position.y.denom;
 
-    g_slice_free(Rect, screen);
     return TRUE;
 }
 
@@ -530,6 +559,56 @@ static gboolean place_transient_splash(ObClient *client, Rect *area,
     return FALSE;
 }
 
+static gboolean place_least_overlap(ObClient *c, gint *x, gint *y, Rect * const head)
+{
+    /* assemble the list of "interesting" windows to calculate overlap against */
+    GSList* interesting_clients = NULL;
+    int n_client_rects = 0;
+
+    /* if we're "showing desktop", ignore all existing windows */
+    if (!screen_showing_desktop) {
+        GList* it;
+        for (it = client_list; it != NULL; it = g_list_next(it)) {
+            ObClient* maybe_client = (ObClient*) it->data;
+            if (maybe_client == c)
+                continue;
+            if (maybe_client->iconic)
+                continue;
+            if (c->desktop != DESKTOP_ALL) {
+                if (maybe_client->desktop != c->desktop &&
+                    maybe_client->desktop != DESKTOP_ALL) continue;
+            } else {
+                if (maybe_client->desktop != screen_desktop &&
+                    maybe_client->desktop != DESKTOP_ALL) continue;
+            }
+            if (maybe_client->type == OB_CLIENT_TYPE_SPLASH ||
+                maybe_client->type == OB_CLIENT_TYPE_DESKTOP) continue;
+            /* it is interesting, so add it */
+            interesting_clients = g_slist_prepend(interesting_clients, maybe_client);
+            n_client_rects += 1;
+        }
+    }
+    Rect client_rects[n_client_rects];
+    GSList* it;
+    unsigned int i = 0;
+    for (it = interesting_clients; it != NULL; it = g_slist_next(it)) {
+        ObClient* interesting_client = (ObClient*) it->data;
+        client_rects[i] = interesting_client->frame->area;
+        i += 1;
+    }
+    g_slist_free(interesting_clients);
+
+    Point result;
+    Size req_size;
+    SIZE_SET(req_size, c->frame->area.width, c->frame->area.height);
+    overlap_find_least_placement(client_rects, n_client_rects, head,
+                                 &req_size, &result);
+    *x = result.x;
+    *y = result.y;
+
+    return TRUE;
+}
+
 /*! Return TRUE if openbox chose the position for the window, and FALSE if
   the application chose it */
 gboolean place_client(ObClient *client, gboolean foreground, gint *x, gint *y,
@@ -546,13 +625,15 @@ gboolean place_client(ObClient *client, gboolean foreground, gint *x, gint *y,
          !(settings && settings->pos_given)))
         return FALSE;
 
-    area = pick_head(client, foreground);
+    area = pick_head(client, foreground, settings);
 
     /* try a number of methods */
-    ret = place_per_app_setting(client, x, y, settings) ||
+    ret = place_per_app_setting(client, area, x, y, settings) ||
         place_transient_splash(client, area, x, y) ||
-        (config_place_policy == OB_PLACE_POLICY_MOUSE &&
-         place_under_mouse(client, x, y)) ||
+        (config_place_policy == OB_PLACE_POLICY_MOUSE
+            && place_under_mouse  (client, x, y)) ||
+        (config_place_policy == OB_PLACE_POLICY_LEASTOVERLAP
+            && place_least_overlap(client, x, y, area)) ||
         place_nooverlap(client, area, x, y) ||
         place_random(client, area, x, y);
     g_assert(ret);