added smart placement
[dana/openbox.git] / openbox / place.c
1 #include "client.h"
2 #include "group.h"
3 #include "screen.h"
4 #include "frame.h"
5 #include "focus.h"
6
7 static Rect* pick_head(ObClient *c)
8 {
9     /* try direct parent first */
10     if (c->transient_for && c->transient_for != OB_TRAN_GROUP) {
11         return screen_area_monitor(c->desktop,
12                                    client_monitor(c->transient_for));
13     }
14
15     /* more than one guy in his group (more than just him) */
16     if (c->group && c->group->members->next) {
17         GSList *it;
18
19         /* try on the client's desktop */
20         for (it = c->group->members; it; it = g_slist_next(it)) {
21             ObClient *itc = it->data;            
22             if (itc != c &&
23                 (itc->desktop == c->desktop ||
24                  itc->desktop == DESKTOP_ALL || c->desktop == DESKTOP_ALL))
25                 return screen_area_monitor(c->desktop,
26                                            client_monitor(it->data));
27         }
28
29         /* try on all desktops */
30         for (it = c->group->members; it; it = g_slist_next(it)) {
31             ObClient *itc = it->data;            
32             if (itc != c)
33                 return screen_area_monitor(c->desktop,
34                                            client_monitor(it->data));
35         }
36     }
37
38     return NULL;
39 }
40
41 static gboolean place_random(ObClient *client, gint *x, gint *y)
42 {
43     int l, r, t, b;
44     Rect *area;
45
46     area = pick_head(client);
47     if (!area)
48         area = screen_area_monitor(client->desktop,
49                                    g_random_int_range(0, screen_num_monitors));
50
51     l = area->x;
52     t = area->y;
53     r = area->x + area->width - client->frame->area.width;
54     b = area->y + area->height - client->frame->area.height;
55
56     if (r > l) *x = g_random_int_range(l, r + 1);
57     else       *x = 0;
58     if (b > t) *y = g_random_int_range(t, b + 1);
59     else       *y = 0;
60
61     /* get where the client should be */
62     frame_frame_gravity(client->frame, x, y);
63
64     return TRUE;
65 }
66
67 static GSList* area_add(GSList *list, Rect *a)
68 {
69     Rect *r = g_new(Rect, 1);
70     *r = *a;
71     return g_slist_prepend(list, r);
72 }
73
74
75 static GSList* area_remove(GSList *list, Rect *a)
76 {
77     GSList *sit;
78     GSList *result = NULL;
79
80     for (sit = list; sit; sit = g_slist_next(sit)) {
81         Rect *r = sit->data;
82
83         if (!RECT_INTERSECTS_RECT(*r, *a)) {
84             result = g_slist_prepend(result, r);
85             r = NULL; /* dont free it */
86         } else {
87             Rect isect, extra;
88
89             /* Use an intersection of win and curr to determine the space
90                around curr that we can use.
91
92                NOTE: the spaces calculated can overlap.
93             */
94
95             RECT_SET_INTERSECTION(isect, *r, *a);
96
97             if (RECT_LEFT(isect) > RECT_LEFT(*r)) {
98                 RECT_SET(extra, r->x, r->y,
99                          RECT_LEFT(isect) - r->x, r->height);
100                 result = area_add(result, &extra);
101             }
102
103             if (RECT_TOP(isect) > RECT_TOP(*r)) {
104                 RECT_SET(extra, r->x, r->y,
105                          r->width, RECT_TOP(isect) - r->y + 1);
106                 result = area_add(result, &extra);
107             }
108
109             if (RECT_RIGHT(isect) < RECT_RIGHT(*r)) {
110                 RECT_SET(extra, RECT_RIGHT(isect) + 1, r->y,
111                          RECT_RIGHT(*r) - RECT_RIGHT(isect), r->height);
112                 result = area_add(result, &extra);
113             }
114
115             if (RECT_BOTTOM(isect) < RECT_BOTTOM(*r)) {
116                 RECT_SET(extra, r->x, RECT_BOTTOM(isect) + 1,
117                          r->width, RECT_BOTTOM(*r) - RECT_BOTTOM(isect));
118                 result = area_add(result, &extra);
119             }
120         }
121
122         g_free(r);
123     }
124     g_slist_free(list);
125     return result;
126 }
127
128 static gint area_cmp(gconstpointer p1, gconstpointer p2)
129 {
130     gint ret;
131     const Rect *a1 = p1, *a2 = p2;
132
133     ret = RECT_BOTTOM(*a1) - RECT_BOTTOM(*a2);
134     if (!ret)
135         ret = RECT_LEFT(*a1) - RECT_LEFT(*a2);
136     return ret;
137 }
138
139 static gboolean place_smart(ObClient *client, gint *x, gint *y)
140 {
141     guint i;
142     gboolean ret = FALSE;
143     GSList *spaces, *sit;
144     GList *it, *list;
145
146     list = focus_order[client->desktop == DESKTOP_ALL ?
147                        screen_desktop : client->desktop];
148
149     for (i = 0; i < screen_num_monitors; ++i)
150         spaces = area_add(spaces, screen_area_monitor(client->desktop, i));
151
152     for (it = list; it; it = g_list_next(it)) {
153         ObClient *c = it->data;
154
155         if (c == client || c->shaded)
156             continue;
157         spaces = area_remove(spaces, &c->frame->area);
158     }
159
160     spaces = g_slist_sort(spaces, area_cmp);
161
162     for (sit = spaces; sit; sit = g_slist_next(sit)) {
163         Rect *r = sit->data;
164
165         if (!ret) {
166             if (r->width >= client->frame->area.width &&
167                 r->height >= client->frame->area.height) {
168                 ret = TRUE;
169                 *x = r->x;
170                 *y = r->y;
171             }
172         }
173
174         g_free(r);
175     }
176     g_slist_free(spaces);
177
178     return ret;
179 }
180
181 static gboolean place_transient(ObClient *client, gint *x, gint *y)
182 {
183     if (client->transient_for) {
184         if (client->transient_for != OB_TRAN_GROUP) {
185             ObClient *c = client;
186             ObClient *p = client->transient_for;
187             *x = (p->frame->area.width - c->frame->area.width) / 2 +
188                 p->frame->area.x;
189             *y = (p->frame->area.height - c->frame->area.height) / 2 +
190                 p->frame->area.y;
191             return TRUE;
192         } else {
193             GSList *it;
194             gboolean first = TRUE;
195             int l, r, t, b;
196             for (it = client->group->members; it; it = it->next) {
197                 ObClient *m = it->data;
198                 if (!(m == client || m->transient_for)) {
199                     if (first) {
200                         l = RECT_LEFT(m->frame->area);
201                         t = RECT_TOP(m->frame->area);
202                         r = RECT_RIGHT(m->frame->area);
203                         b = RECT_BOTTOM(m->frame->area);
204                         first = FALSE;
205                     } else {
206                         l = MIN(l, RECT_LEFT(m->frame->area));
207                         t = MIN(t, RECT_TOP(m->frame->area));
208                         r = MAX(r, RECT_RIGHT(m->frame->area));
209                         b = MAX(b, RECT_BOTTOM(m->frame->area));
210                     }
211                 }
212             }
213             if (!first) {
214                 *x = ((r + 1 - l) - client->frame->area.width) / 2 + l; 
215                 *y = ((b + 1 - t) - client->frame->area.height) / 2 + t;
216                 return TRUE;
217             }
218         }
219     }
220     return FALSE;
221 }
222
223 static gboolean place_dialog(ObClient *client, gint *x, gint *y)
224 {
225     /* center parentless dialogs on the screen */
226     if (client->type == OB_CLIENT_TYPE_DIALOG) {
227         Rect *area;
228
229         area = pick_head(client);
230         if (!area)
231             area = screen_area_monitor(client->desktop, 0);
232
233         *x = (area->width - client->frame->area.width) / 2 + area->x;
234         *y = (area->height - client->frame->area.height) / 2 + area->y;
235         return TRUE;
236     }
237     return FALSE;
238 }
239
240 void place_client(ObClient *client, gint *x, gint *y)
241 {
242     if (client->positioned)
243         return;
244     if (place_transient(client, x, y))
245         return;
246     if (place_dialog(client, x, y))
247         return;
248     if (place_smart(client, x, y))
249         return;
250     if (place_random(client, x, y))
251         return;
252     g_assert_not_reached(); /* the last one better succeed */
253 }