update copyright step 2
[mikachu/openbox.git] / openbox / place.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    place.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003        Ben Jansens
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    See the COPYING file for a copy of the GNU General Public License.
18 */
19
20 #include "client.h"
21 #include "group.h"
22 #include "screen.h"
23 #include "frame.h"
24 #include "focus.h"
25 #include "config.h"
26
27 static Rect* pick_head(ObClient *c)
28 {
29     /* try direct parent first */
30     if (c->transient_for && c->transient_for != OB_TRAN_GROUP) {
31         return screen_area_monitor(c->desktop,
32                                    client_monitor(c->transient_for));
33     }
34
35     /* more than one guy in his group (more than just him) */
36     if (client_has_group_siblings(c)) {
37         GSList *it;
38
39         /* try on the client's desktop */
40         for (it = c->group->members; it; it = g_slist_next(it)) {
41             ObClient *itc = it->data;            
42             if (itc != c &&
43                 (itc->desktop == c->desktop ||
44                  itc->desktop == DESKTOP_ALL || c->desktop == DESKTOP_ALL))
45                 return screen_area_monitor(c->desktop,
46                                            client_monitor(it->data));
47         }
48
49         /* try on all desktops */
50         for (it = c->group->members; it; it = g_slist_next(it)) {
51             ObClient *itc = it->data;            
52             if (itc != c)
53                 return screen_area_monitor(c->desktop,
54                                            client_monitor(it->data));
55         }
56     }
57
58     return NULL;
59 }
60
61 static gboolean place_random(ObClient *client, gint *x, gint *y)
62 {
63     gint l, r, t, b;
64     Rect *area;
65
66     area = pick_head(client);
67     if (!area)
68         area = screen_area_monitor(client->desktop,
69                                    g_random_int_range(0, screen_num_monitors));
70
71     l = area->x;
72     t = area->y;
73     r = area->x + area->width - client->frame->area.width;
74     b = area->y + area->height - client->frame->area.height;
75
76     if (r > l) *x = g_random_int_range(l, r + 1);
77     else       *x = 0;
78     if (b > t) *y = g_random_int_range(t, b + 1);
79     else       *y = 0;
80
81     return TRUE;
82 }
83
84 static GSList* area_add(GSList *list, Rect *a)
85 {
86     Rect *r = g_new(Rect, 1);
87     *r = *a;
88     return g_slist_prepend(list, r);
89 }
90
91 static GSList* area_remove(GSList *list, Rect *a)
92 {
93     GSList *sit;
94     GSList *result = NULL;
95
96     for (sit = list; sit; sit = g_slist_next(sit)) {
97         Rect *r = sit->data;
98
99         if (!RECT_INTERSECTS_RECT(*r, *a)) {
100             result = g_slist_prepend(result, r);
101             r = NULL; /* dont free it */
102         } else {
103             Rect isect, extra;
104
105             /* Use an intersection of a and r to determine the space
106                around r that we can use.
107
108                NOTE: the spaces calculated can overlap.
109             */
110
111             RECT_SET_INTERSECTION(isect, *r, *a);
112
113             if (RECT_LEFT(isect) > RECT_LEFT(*r)) {
114                 RECT_SET(extra, r->x, r->y,
115                          RECT_LEFT(isect) - r->x, r->height);
116                 result = area_add(result, &extra);
117             }
118
119             if (RECT_TOP(isect) > RECT_TOP(*r)) {
120                 RECT_SET(extra, r->x, r->y,
121                          r->width, RECT_TOP(isect) - r->y + 1);
122                 result = area_add(result, &extra);
123             }
124
125             if (RECT_RIGHT(isect) < RECT_RIGHT(*r)) {
126                 RECT_SET(extra, RECT_RIGHT(isect) + 1, r->y,
127                          RECT_RIGHT(*r) - RECT_RIGHT(isect), r->height);
128                 result = area_add(result, &extra);
129             }
130
131             if (RECT_BOTTOM(isect) < RECT_BOTTOM(*r)) {
132                 RECT_SET(extra, r->x, RECT_BOTTOM(isect) + 1,
133                          r->width, RECT_BOTTOM(*r) - RECT_BOTTOM(isect));
134                 result = area_add(result, &extra);
135             }
136         }
137
138         g_free(r);
139     }
140     g_slist_free(list);
141     return result;
142 }
143
144 static gint area_cmp(gconstpointer p1, gconstpointer p2, gpointer data)
145 {
146     ObClient *c = data;
147     Rect *carea = &c->frame->area;
148     const Rect *a1 = p1, *a2 = p2;
149     gboolean diffhead = FALSE;
150     guint i;
151     Rect *a;
152
153     for (i = 0; i < screen_num_monitors; ++i) {
154         a = screen_physical_area_monitor(i);
155         if (RECT_CONTAINS(*a, a1->x, a1->y) &&
156             !RECT_CONTAINS(*a, a2->x, a2->y))
157         {
158             diffhead = TRUE;
159             break;
160         }
161     }
162
163     /* has to be more than me in the group */
164     if (diffhead && client_has_group_siblings(c)) {
165         guint *num, most;
166         GSList *it;
167
168         /* find how many clients in the group are on each monitor, use the
169            monitor with the most in it */
170         num = g_new0(guint, screen_num_monitors);
171         for (it = c->group->members; it; it = g_slist_next(it))
172             if (it->data != c)
173                 ++num[client_monitor(it->data)];
174         most = 0;
175         for (i = 1; i < screen_num_monitors; ++i)
176             if (num[i] > num[most])
177                 most = i;
178
179         g_free(num);
180
181         a = screen_physical_area_monitor(most);
182         if (RECT_CONTAINS(*a, a1->x, a1->y))
183             return -1;
184         if (RECT_CONTAINS(*a, a2->x, a2->y))
185             return 1;
186     }
187
188     return MIN((a1->width - carea->width), (a1->height - carea->height)) -
189         MIN((a2->width - carea->width), (a2->height - carea->height));
190 }
191
192 typedef enum
193 {
194     SMART_FULL,
195     SMART_GROUP,
196     SMART_FOCUSED
197 } ObSmartType;
198
199 #define SMART_IGNORE(placer, c) \
200     (placer == c || !c->frame->visible || c->shaded || !client_normal(c) || \
201      (c->desktop != DESKTOP_ALL && \
202       c->desktop != (placer->desktop == DESKTOP_ALL ? \
203                      screen_desktop : placer->desktop)))
204
205 static gboolean place_smart(ObClient *client, gint *x, gint *y,
206                             ObSmartType type)
207 {
208     guint i;
209     gboolean ret = FALSE;
210     GSList *spaces = NULL, *sit;
211     GList *it;
212
213     for (i = 0; i < screen_num_monitors; ++i)
214         spaces = area_add(spaces, screen_area_monitor(client->desktop, i));
215
216     /* stay out from under windows in higher layers */
217     for (it = stacking_list; it; it = g_list_next(it)) {
218         ObClient *c;
219
220         if (WINDOW_IS_CLIENT(it->data)) {
221             c = it->data;
222             if (c->fullscreen)
223                 continue;
224         } else
225             continue;
226
227         if (c->layer > client->layer) {
228             if (!SMART_IGNORE(client, c))
229                 spaces = area_remove(spaces, &c->frame->area);
230         } else
231             break;
232     }
233
234     if (client->type == OB_CLIENT_TYPE_NORMAL) {
235         if (type == SMART_FULL || type == SMART_FOCUSED) {
236             gboolean found_foc = FALSE, stop = FALSE;
237             ObClient *foc;
238             GList *list;
239
240             list = focus_order[client->desktop == DESKTOP_ALL ?
241                                screen_desktop : client->desktop];
242             foc = list ? list->data : NULL;
243
244             for (; it && !stop; it = g_list_next(it)) {
245                 ObClient *c;
246
247                 if (WINDOW_IS_CLIENT(it->data)) {
248                     c = it->data;
249                     if (c->fullscreen)
250                         continue;
251                 } else
252                     continue;
253
254                 if (!SMART_IGNORE(client, c)) {
255                     if (type == SMART_FOCUSED)
256                         if (found_foc)
257                             stop = TRUE;
258                     if (!stop)
259                         spaces = area_remove(spaces, &c->frame->area);
260                 }
261
262                 if (c == foc)
263                     found_foc = TRUE;
264             }
265         } else if (type == SMART_GROUP) {
266             /* has to be more than me in the group */
267             if (!client_has_group_siblings(client))
268                 return FALSE;
269
270             for (sit = client->group->members; sit; sit = g_slist_next(sit)) {
271                 ObClient *c = sit->data;
272                 if (!SMART_IGNORE(client, c))
273                     spaces = area_remove(spaces, &c->frame->area);
274             }
275         } else
276             g_assert_not_reached();
277     }
278
279     spaces = g_slist_sort_with_data(spaces, area_cmp, client);
280
281     for (sit = spaces; sit; sit = g_slist_next(sit)) {
282         Rect *r = sit->data;
283
284         if (!ret) {
285             if (r->width >= client->frame->area.width &&
286                 r->height >= client->frame->area.height) {
287                 ret = TRUE;
288                 if (client->type == OB_CLIENT_TYPE_DIALOG ||
289                     type != SMART_FULL)
290                 {
291                     *x = r->x + (r->width - client->frame->area.width) / 2;
292                     *y = r->y + (r->height - client->frame->area.height) / 2;
293                 } else {
294                     *x = r->x;
295                     *y = r->y;
296                 }
297             }
298         }
299
300         g_free(r);
301     }
302     g_slist_free(spaces);
303
304     return ret;
305 }
306
307 static gboolean place_under_mouse(ObClient *client, gint *x, gint *y)
308 {
309     guint i;
310     gint l, r, t, b;
311     gint px, py;
312     Rect *area;
313
314     screen_pointer_pos(&px, &py);
315
316     for (i = 0; i < screen_num_monitors; ++i) {
317         area = screen_area_monitor(client->desktop, i);
318         if (RECT_CONTAINS(*area, px, py))
319             break;
320     }
321     if (i == screen_num_monitors)
322         area = screen_area_monitor(client->desktop, 0);
323
324     l = area->x;
325     t = area->y;
326     r = area->x + area->width - client->frame->area.width;
327     b = area->y + area->height - client->frame->area.height;
328
329     *x = px - client->area.width / 2 - client->frame->size.left;
330     *x = MIN(MAX(*x, l), r);
331     *y = py - client->area.height / 2 - client->frame->size.top;
332     *y = MIN(MAX(*y, t), b);
333
334     return TRUE;
335 }
336
337 static gboolean place_per_app_setting(ObClient *client, gint *x, gint *y, ObAppSettings *settings)
338 {
339     gint px, py, i;
340     Rect *screen;
341
342     if (!settings || (settings && !settings->pos_given))
343         return FALSE;
344
345     /* Find which head the pointer is on */
346     if (settings->head == -1 && screen_num_monitors > 1) {
347         screen_pointer_pos(&px, &py);
348
349         for (i = 0; i < screen_num_monitors; i++) {
350             screen = screen_area_monitor(client->desktop, i);
351             if (RECT_CONTAINS(*screen, px, py))
352                 break;
353         }
354
355         if (i == screen_num_monitors)
356             screen = screen_area_monitor(client->desktop, 0);
357     }
358     else
359         screen = screen_area_monitor(client->desktop, settings->head);
360
361     if (settings->center_x)
362         *x = screen->x + screen->width / 2 - client->area.width / 2;
363     else
364         *x = screen->x + settings->position.x;
365
366     if (settings->center_y)
367         *y = screen->y + screen->height / 2 - client->area.height / 2;
368     else
369         *y = screen->y + settings->position.y;
370
371     return TRUE;
372 }
373
374 static gboolean place_transient(ObClient *client, gint *x, gint *y)
375 {
376     if (client->transient_for) {
377         if (client->transient_for != OB_TRAN_GROUP) {
378             ObClient *c = client;
379             ObClient *p = client->transient_for;
380             *x = (p->frame->area.width - c->frame->area.width) / 2 +
381                 p->frame->area.x;
382             *y = (p->frame->area.height - c->frame->area.height) / 2 +
383                 p->frame->area.y;
384             return TRUE;
385         } else {
386             GSList *it;
387             gboolean first = TRUE;
388             gint l, r, t, b;
389             for (it = client->group->members; it; it = g_slist_next(it)) {
390                 ObClient *m = it->data;
391                 if (!(m == client || m->transient_for)) {
392                     if (first) {
393                         l = RECT_LEFT(m->frame->area);
394                         t = RECT_TOP(m->frame->area);
395                         r = RECT_RIGHT(m->frame->area);
396                         b = RECT_BOTTOM(m->frame->area);
397                         first = FALSE;
398                     } else {
399                         l = MIN(l, RECT_LEFT(m->frame->area));
400                         t = MIN(t, RECT_TOP(m->frame->area));
401                         r = MAX(r, RECT_RIGHT(m->frame->area));
402                         b = MAX(b, RECT_BOTTOM(m->frame->area));
403                     }
404                 }
405             }
406             if (!first) {
407                 *x = ((r + 1 - l) - client->frame->area.width) / 2 + l; 
408                 *y = ((b + 1 - t) - client->frame->area.height) / 2 + t;
409                 return TRUE;
410             }
411         }
412     }
413     return FALSE;
414 }
415
416 /* Return TRUE if we want client.c to enforce on-screen-keeping */
417 gboolean place_client(ObClient *client, gint *x, gint *y, ObAppSettings *settings)
418 {
419     gboolean ret = FALSE;
420     if (client->positioned)
421         return FALSE;
422     if (place_transient(client, x, y))
423         ret = TRUE;
424     else if (!(
425         place_per_app_setting(client, x, y, settings) ||
426         ((config_place_policy == OB_PLACE_POLICY_MOUSE) ?
427          place_under_mouse(client, x, y) :
428          place_smart(client, x, y, SMART_FULL)    ||
429          place_smart(client, x, y, SMART_GROUP)   ||
430          place_smart(client, x, y, SMART_FOCUSED) ||
431          place_random(client, x, y))))
432         g_assert_not_reached(); /* the last one better succeed */
433     /* get where the client should be */
434     frame_frame_gravity(client->frame, x, y);
435     return ret;
436 }