better stacking_add_nonintrusive.
[dana/openbox.git] / openbox / stacking.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    stacking.c for the Openbox window manager
4    Copyright (c) 2006        Mikael Magnusson
5    Copyright (c) 2003-2007   Dana 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 "openbox.h"
21 #include "prop.h"
22 #include "screen.h"
23 #include "focus.h"
24 #include "client.h"
25 #include "group.h"
26 #include "frame.h"
27 #include "window.h"
28
29 GList  *stacking_list = NULL;
30
31 void stacking_set_list()
32 {
33     Window *windows = NULL;
34     GList *it;
35     guint i = 0;
36
37     /* on shutdown, don't update the properties, so that we can read it back
38        in on startup and re-stack the windows as they were before we shut down
39     */
40     if (ob_state() == OB_STATE_EXITING) return;
41
42     /* create an array of the window ids (from bottom to top,
43        reverse order!) */
44     if (stacking_list) {
45         windows = g_new(Window, g_list_length(stacking_list));
46         for (it = g_list_last(stacking_list); it; it = g_list_previous(it)) {
47             if (WINDOW_IS_CLIENT(it->data))
48                 windows[i++] = WINDOW_AS_CLIENT(it->data)->window;
49         }
50     }
51
52     PROP_SETA32(RootWindow(ob_display, ob_screen),
53                 net_client_list_stacking, window, (gulong*)windows, i);
54
55     g_free(windows);
56 }
57
58 static void do_restack(GList *wins, GList *before)
59 {
60     GList *it;
61     Window *win;
62     gint i;
63
64 #ifdef DEBUG
65     GList *next;
66     /* pls only restack stuff in the same layer at a time */
67     for (it = wins; it; it = next) {
68         next = g_list_next(it);
69         if (!next) break;
70         g_assert (window_layer(it->data) == window_layer(next->data));
71     }
72     if (before)
73         g_assert(window_layer(it->data) >= window_layer(before->data));
74 #endif
75
76     win = g_new(Window, g_list_length(wins) + 1);
77
78     if (before == stacking_list)
79         win[0] = screen_support_win;
80     else if (!before)
81         win[0] = window_top(g_list_last(stacking_list)->data);
82     else
83         win[0] = window_top(g_list_previous(before)->data);
84
85     for (i = 1, it = wins; it; ++i, it = g_list_next(it)) {
86         win[i] = window_top(it->data);
87         g_assert(win[i] != None); /* better not call stacking shit before
88                                      setting your top level window value */
89         stacking_list = g_list_insert_before(stacking_list, before, it->data);
90     }
91
92 #ifdef DEBUG
93     /* some debug checking of the stacking list's order */
94     for (it = stacking_list; ; it = next) {
95         next = g_list_next(it);
96         if (!next) break;
97         g_assert(window_layer(it->data) >= window_layer(next->data));
98     }
99 #endif
100
101     XRestackWindows(ob_display, win, i);
102     g_free(win);
103
104     stacking_set_list();
105 }
106
107 static void do_raise(GList *wins)
108 {
109     GList *it;
110     GList *layer[OB_NUM_STACKING_LAYERS] = {NULL};
111     gint i;
112
113     for (it = wins; it; it = g_list_next(it)) {
114         ObStackingLayer l;
115
116         l = window_layer(it->data);
117         layer[l] = g_list_append(layer[l], it->data);
118     }
119
120     it = stacking_list;
121     for (i = OB_NUM_STACKING_LAYERS - 1; i >= 0; --i) {
122         if (layer[i]) {
123             for (; it; it = g_list_next(it)) {
124                 /* look for the top of the layer */
125                 if (window_layer(it->data) <= (ObStackingLayer) i)
126                     break;
127             }
128             do_restack(layer[i], it);
129             g_list_free(layer[i]);
130         }
131     }
132 }
133
134 static void do_lower(GList *wins)
135 {
136     GList *it;
137     GList *layer[OB_NUM_STACKING_LAYERS] = {NULL};
138     gint i;
139
140     for (it = wins; it; it = g_list_next(it)) {
141         ObStackingLayer l;
142
143         l = window_layer(it->data);
144         layer[l] = g_list_append(layer[l], it->data);
145     }
146
147     it = stacking_list;
148     for (i = OB_NUM_STACKING_LAYERS - 1; i >= 0; --i) {
149         if (layer[i]) {
150             for (; it; it = g_list_next(it)) {
151                 /* look for the top of the next layer down */
152                 if (window_layer(it->data) < (ObStackingLayer) i)
153                     break;
154             }
155             do_restack(layer[i], it);
156             g_list_free(layer[i]);
157         }
158     }
159 }
160
161 static void restack_windows(ObClient *selected, gboolean raise)
162 {
163     GList *it, *last, *below, *above, *next;
164     GList *wins = NULL;
165
166     GList *group_modals = NULL;
167     GList *group_trans = NULL;
168     GList *modals = NULL;
169     GList *trans = NULL;
170
171     if (!raise && selected->transient_for) {
172         GSList *top, *top_it;
173         GSList *top_reorder = NULL;
174         
175         /* if it's a transient lowering, lower its parents so that we can lower
176            this window, or it won't move */
177         top = client_search_all_top_parents(selected);
178
179         /* that is, if it has any parents */
180         if (!(top->data == selected && top->next == NULL)) {
181             /* go thru stacking list backwards so we can use g_slist_prepend */
182             for (it = g_list_last(stacking_list); it && top;
183                  it = g_list_previous(it))
184                 if ((top_it = g_slist_find(top, it->data))) {
185                     top_reorder = g_slist_prepend(top_reorder, top_it->data);
186                     top = g_slist_delete_link(top, top_it);
187                 }
188             g_assert(top == NULL);
189
190             /* call restack for each of these to lower them */
191             for (top_it = top_reorder; top_it; top_it = g_slist_next(top_it))
192                 restack_windows(top_it->data, raise);
193             return;
194         }
195     }
196
197     /* remove first so we can't run into ourself */
198     it = g_list_find(stacking_list, selected);
199     g_assert(it);
200     stacking_list = g_list_delete_link(stacking_list, it);
201
202     /* go from the bottom of the stacking list up */
203     for (it = g_list_last(stacking_list); it; it = next) {
204         next = g_list_previous(it);
205
206         if (WINDOW_IS_CLIENT(it->data)) {
207             ObClient *ch = it->data;
208
209             /* only move windows in the same stacking layer */
210             if (ch->layer == selected->layer &&
211                 client_search_transient(selected, ch))
212             {
213                 if (client_is_direct_child(selected, ch)) {
214                     if (ch->modal)
215                         modals = g_list_prepend(modals, ch);
216                     else
217                         trans = g_list_prepend(trans, ch);
218                 }
219                 else {
220                     if (ch->modal)
221                         group_modals = g_list_prepend(group_modals, ch);
222                     else
223                         group_trans = g_list_prepend(group_trans, ch);
224                 }
225                 stacking_list = g_list_delete_link(stacking_list, it);
226             }
227         }
228     }
229
230     /* put transients of the selected window right above it */
231     wins = g_list_concat(modals, trans);
232     wins = g_list_append(wins, selected);
233
234     /* if selected window is transient for group then raise it above others */
235     if (selected->transient_for == OB_TRAN_GROUP) {
236         /* if it's modal, raise it above those also */
237         if (selected->modal) {
238             wins = g_list_concat(wins, group_modals);
239             group_modals = NULL;
240         }
241         wins = g_list_concat(wins, group_trans);
242         group_trans = NULL;
243     }
244
245     /* find where to put the selected window, start from bottom of list,
246        this is the window below everything we are re-adding to the list */
247     last = NULL;
248     for (it = g_list_last(stacking_list); it; it = g_list_previous(it))
249     {
250         if (window_layer(it->data) < selected->layer) {
251             last = it;
252             continue;
253         }
254         /* if lowering, stop at the beginning of the layer */
255         if (!raise)
256             break;
257         /* if raising, stop at the end of the layer */
258         if (window_layer(it->data) > selected->layer)
259             break;
260
261         last = it;
262     }
263
264     /* save this position in the stacking list */
265     below = last;
266
267     /* find where to put the group transients, start from the top of list */
268     for (it = stacking_list; it; it = g_list_next(it)) {
269         /* skip past higher layers */
270         if (window_layer(it->data) > selected->layer)
271             continue;
272         /* if we reach the end of the layer (how?) then don't go further */
273         if (window_layer(it->data) < selected->layer)
274             break;
275         /* stop when we reach the first window in the group */
276         if (WINDOW_IS_CLIENT(it->data)) {
277             ObClient *c = it->data;
278             if (c->group == selected->group)
279                 break;
280         }
281         /* if we don't hit any other group members, stop here because this
282            is where we are putting the selected window (and its children) */
283         if (it == below)
284             break;
285     }
286
287     /* save this position, this is the top of the group of windows between the
288        group transient ones we're restacking and the others up above that we're
289        restacking
290
291        we actually want to save 1 position _above_ that, for for loops to work
292        nicely, so move back one position in the list while saving it
293     */
294     above = it ? g_list_previous(it) : g_list_last(stacking_list);
295
296     /* put the windows inside the gap to the other windows we're stacking
297        into the restacking list, go from the bottom up so that we can use
298        g_list_prepend */
299     if (below) it = g_list_previous(below);
300     else       it = g_list_last(stacking_list);
301     for (; it != above; it = next) {
302         next = g_list_previous(it);
303         wins = g_list_prepend(wins, it->data);
304         stacking_list = g_list_delete_link(stacking_list, it);
305     }
306
307     /* group transients go above the rest of the stuff acquired to now */
308     wins = g_list_concat(group_trans, wins);
309     /* group modals go on the very top */
310     wins = g_list_concat(group_modals, wins);
311
312     do_restack(wins, below);
313     g_list_free(wins);
314 }
315
316 void stacking_raise(ObWindow *window)
317 {
318     if (WINDOW_IS_CLIENT(window)) {
319         ObClient *selected;
320         selected = WINDOW_AS_CLIENT(window);
321         restack_windows(selected, TRUE);
322     } else {
323         GList *wins;
324         wins = g_list_append(NULL, window);
325         stacking_list = g_list_remove(stacking_list, window);
326         do_raise(wins);
327         g_list_free(wins);
328     }
329 }
330
331 void stacking_lower(ObWindow *window)
332 {
333     if (WINDOW_IS_CLIENT(window)) {
334         ObClient *selected;
335         selected = WINDOW_AS_CLIENT(window);
336         restack_windows(selected, FALSE);
337     } else {
338         GList *wins;
339         wins = g_list_append(NULL, window);
340         stacking_list = g_list_remove(stacking_list, window);
341         do_lower(wins);
342         g_list_free(wins);
343     }
344 }
345
346 void stacking_below(ObWindow *window, ObWindow *below)
347 {
348     GList *wins, *before;
349
350     if (window_layer(window) != window_layer(below))
351         return;
352
353     wins = g_list_append(NULL, window);
354     stacking_list = g_list_remove(stacking_list, window);
355     before = g_list_next(g_list_find(stacking_list, below));
356     do_restack(wins, before);
357     g_list_free(wins);
358 }
359
360 void stacking_add(ObWindow *win)
361 {
362     g_assert(screen_support_win != None); /* make sure I dont break this in the
363                                              future */
364
365     stacking_list = g_list_append(stacking_list, win);
366     stacking_raise(win);
367 }
368
369 static GList *find_highest_relative(ObClient *client)
370 {    
371     GList *ret = NULL;
372
373     if (client->transient_for) {
374         GList *it;
375         GSList *top;
376
377         /* get all top level relatives of this client */
378         top = client_search_all_top_parents(client);
379
380         /* go from the top of the stacking order down */
381         for (it = stacking_list; !ret && it; it = g_list_next(it)) {
382             if (WINDOW_IS_CLIENT(it->data)) {
383                 ObClient *c = it->data;
384                 /* only look at windows in the same layer */
385                 if (c->layer == client->layer) {
386                     GSList *sit;
387
388                     /* go through each top level parent and see it this window
389                        is related to them */
390                     for (sit = top; !ret && sit; sit = g_slist_next(sit)) {
391                         ObClient *topc = sit->data;
392
393                         /* are they related ? */
394                         if (topc == c || client_search_transient(topc, c))
395                             ret = it;
396                     }
397                 }
398             }
399         }
400     }
401     return ret;
402 }
403
404 void stacking_add_nonintrusive(ObWindow *win)
405 {
406     ObClient *client;
407     GList *it_below = NULL;
408
409     if (!WINDOW_IS_CLIENT(win)) {
410         stacking_add(win); /* no special rules for others */
411         return;
412     }
413
414     client = WINDOW_AS_CLIENT(win);
415
416     /* insert above its highest parent (or its highest child !) */
417     it_below = find_highest_relative(client);
418
419     if (!it_below) {
420         /* nothing to put it directly above, so try find the focused client to
421            put it underneath it */
422         if (focus_client && focus_client->layer == client->layer) {
423             if ((it_below = g_list_find(stacking_list, focus_client)))
424                 it_below = it_below->next;
425         }
426     }
427     if (!it_below) {
428         /* there is no window to put this directly above, so put it at the
429            bottom */
430         stacking_list = g_list_prepend(stacking_list, win);
431         stacking_lower(win);
432     } else {
433         /* make sure it's not in the wrong layer though ! */
434         for (; it_below; it_below = g_list_next(it_below))
435         {
436             /* stop when the window is not in a higher layer than the window
437                it is going above (it_below) */
438             if (client->layer >= window_layer(it_below->data))
439                 break;
440         }
441         for (; it_below != stacking_list;
442              it_below = g_list_previous(it_below))
443         {
444             /* stop when the window is not in a lower layer than the
445                window it is going under (it_above) */
446             GList *it_above = g_list_previous(it_below);
447             if (client->layer <= window_layer(it_above->data))
448                 break;
449         }
450
451         GList *wins = g_list_append(NULL, win);
452         do_restack(wins, it_below);
453         g_list_free(wins);
454     }
455 }