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