Rewrite the stacking code. It's a lot faster now, I should think. It's def a more...
[mikachu/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        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 "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, gboolean group)
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         /* go thru stacking list backwards so we can use g_slist_prepend */
180         for (it = g_list_last(stacking_list); it && top;
181              it = g_list_previous(it))
182             if ((top_it = g_slist_find(top, it->data))) {
183                 top_reorder = g_slist_prepend(top_reorder, top_it->data);
184                 top = g_slist_delete_link(top, top_it);
185             }
186         g_assert(top == NULL);
187
188         /* call restack for each of these to lower them */
189         for (top_it = top_reorder; top_it; top_it = g_slist_next(top_it))
190             restack_windows(top_it->data, raise, group);
191         return;
192     }
193
194     /* remove first so we can't run into ourself */
195     it = g_list_find(stacking_list, selected);
196     g_assert(it);
197     stacking_list = g_list_delete_link(stacking_list, it);
198
199     /* go from the bottom of the stacking list up */
200     for (it = g_list_last(stacking_list); it; it = g_list_previous(it)) {
201         if (WINDOW_IS_CLIENT(it->data)) {
202             ObClient *ch = it->data;
203
204             /* only move windows in the same stacking layer */
205             if (ch->layer == selected->layer &&
206                 client_search_transient(selected, ch))
207             {
208                 if (client_is_direct_child(selected, ch)) {
209                     if (ch->modal)
210                         modals = g_list_prepend(modals, ch);
211                     else
212                         trans = g_list_prepend(trans, ch);
213                 }
214                 else {
215                     if (ch->modal)
216                         group_modals = g_list_prepend(group_modals, ch);
217                     else
218                         group_trans = g_list_prepend(group_trans, ch);
219                 }
220                 stacking_list = g_list_delete_link(stacking_list, it);
221             }
222         }
223     }
224
225     /* put transients of the selected window right above it */
226     wins = g_list_concat(modals, trans);
227     wins = g_list_append(wins, selected);
228
229     /* if selected window is transient for group then raise it above others */
230     if (selected->transient_for == OB_TRAN_GROUP) {
231         /* if it's modal, raise it above those also */
232         if (selected->modal) {
233             wins = g_list_concat(wins, group_modals);
234             group_modals = NULL;
235         }
236         wins = g_list_concat(wins, group_trans);
237         group_trans = NULL;
238     }
239
240     /* find where to put the selected window, start from bottom of list,
241        this is the window below everything we are re-adding to the list */
242     last = NULL;
243     for (it = g_list_last(stacking_list); it; it = g_list_previous(it))
244     {
245         if (window_layer(it->data) < selected->layer)
246             continue;
247         /* if lowering, stop at the beginning of the layer */
248         if (!raise)
249             break;
250         /* if raising, stop at the end of the layer */
251         if (window_layer(it->data) > selected->layer)
252             break;
253
254         last = it;
255     }
256
257     /* save this position in the stacking list */
258     below = last;
259
260     /* find where to put the group transients, start from the top of list */
261     for (it = stacking_list; it; it = g_list_next(it)) {
262         /* skip past higher layers */
263         if (window_layer(it->data) > selected->layer)
264             continue;
265         /* if we reach the end of the layer (how?) then don't go further */
266         if (window_layer(it->data) < selected->layer)
267             break;
268         /* stop when we reach the first window in the group */
269         if (WINDOW_IS_CLIENT(it->data)) {
270             ObClient *c = it->data;
271             if (c->group == selected->group)
272                 break;
273         }
274         /* if we don't hit any other group members, stop here because this
275            is where we are putting the selected window (and its children) */
276         if (it == below)
277             break;
278     }
279
280     /* save this position, this is the top of the group of windows between the
281        group transient ones we're restacking and the others up above that we're
282        restacking
283
284        we actually want to save 1 position _above_ that, for for loops to work
285        nicely, so move back one position in the list while saving it
286     */
287     above = it ? g_list_previous(it) : g_list_last(stacking_list);
288
289     /* put the windows inside the gap to the other windows we're stacking
290        into the restacking list, go from the bottom up so that we can use
291        g_list_prepend */
292     if (below) it = g_list_previous(below);
293     else       it = g_list_last(stacking_list);
294     for (; it != above; it = next) {
295         next = g_list_previous(it);
296         wins = g_list_prepend(wins, it->data);
297         stacking_list = g_list_delete_link(stacking_list, it);
298     }
299
300     /* group transients go above the rest of the stuff acquired to now */
301     wins = g_list_concat(group_trans, wins);
302     /* group modals go on the very top */
303     wins = g_list_concat(group_modals, wins);
304
305     do_restack(wins, below);
306     g_list_free(wins);
307 }
308
309 void stacking_raise(ObWindow *window, gboolean group)
310 {
311     if (WINDOW_IS_CLIENT(window)) {
312         ObClient *selected;
313         selected = WINDOW_AS_CLIENT(window);
314         restack_windows(selected, TRUE, group);
315     } else {
316         GList *wins;
317         wins = g_list_append(NULL, window);
318         stacking_list = g_list_remove(stacking_list, window);
319         do_raise(wins);
320         g_list_free(wins);
321     }
322 }
323
324 void stacking_lower(ObWindow *window, gboolean group)
325 {
326     if (WINDOW_IS_CLIENT(window)) {
327         ObClient *selected;
328         selected = WINDOW_AS_CLIENT(window);
329         restack_windows(selected, FALSE, group);
330     } else {
331         GList *wins;
332         wins = g_list_append(NULL, window);
333         stacking_list = g_list_remove(stacking_list, window);
334         do_lower(wins);
335         g_list_free(wins);
336     }
337 }
338
339 void stacking_below(ObWindow *window, ObWindow *below)
340 {
341     GList *wins, *before;
342
343     if (window_layer(window) != window_layer(below))
344         return;
345
346     wins = g_list_append(NULL, window);
347     stacking_list = g_list_remove(stacking_list, window);
348     before = g_list_next(g_list_find(stacking_list, below));
349     do_restack(wins, before);
350     g_list_free(wins);
351 }
352
353 void stacking_add(ObWindow *win)
354 {
355     g_assert(screen_support_win != None); /* make sure I dont break this in the
356                                              future */
357
358     stacking_list = g_list_append(stacking_list, win);
359     stacking_raise(win, FALSE);
360 }
361
362 void stacking_add_nonintrusive(ObWindow *win)
363 {
364     ObClient *client;
365     ObClient *parent = NULL;
366     GList *it_below = NULL;
367
368     if (!WINDOW_IS_CLIENT(win)) {
369         stacking_add(win); /* no special rules for others */
370         return;
371     }
372
373     client = WINDOW_AS_CLIENT(win);
374
375     /* insert above its highest parent */
376     if (client->transient_for) {
377         if (client->transient_for != OB_TRAN_GROUP) {
378             parent = client->transient_for;
379         } else {
380             GSList *sit;
381             GList *it;
382
383             if (client->group)
384                 for (it = stacking_list; !parent && it; it = g_list_next(it)) {
385                     if ((sit = g_slist_find(client->group->members, it->data)))
386                 for (sit = client->group->members; !parent && sit;
387                      sit = g_slist_next(sit))
388                 {
389                     ObClient *c = sit->data;
390                     /* checking transient_for prevents infinate loops! */
391                     if (sit->data == it->data && !c->transient_for)
392                         parent = it->data;
393                 }
394             }
395         }
396     }
397
398     if (!(it_below = g_list_find(stacking_list, parent))) {
399         /* no parent to put above, try find the focused client to go
400            under */
401         if (focus_client && focus_client->layer == client->layer) {
402             if ((it_below = g_list_find(stacking_list, focus_client)))
403                 it_below = it_below->next;
404         }
405     }
406     if (!it_below) {
407         /* out of ideas, just add it normally... */
408         stacking_add(win);
409     } else {
410         /* make sure it's not in the wrong layer though ! */
411         for (; it_below; it_below = g_list_next(it_below))
412         {
413             /* stop when the window is not in a higher layer than the window
414                it is going above (it_below) */
415             if (client->layer >= window_layer(it_below->data))
416                 break;
417         }
418         for (; it_below != stacking_list;
419              it_below = g_list_previous(it_below))
420         {
421             /* stop when the window is not in a lower layer than the
422                window it is going under (it_above) */
423             GList *it_above = g_list_previous(it_below);
424             if (client->layer <= window_layer(it_above->data))
425                 break;
426         }
427
428         GList *wins = g_list_append(NULL, win);
429         do_restack(wins, it_below);
430         g_list_free(wins);
431     }
432 }