2009-02-04 Alberto Garcia <agarcia@igalia.com>
[hildon] / src / hildon-window-stack.c
1 /*
2  * This file is a part of hildon
3  *
4  * Copyright (C) 2008 Nokia Corporation, all rights reserved.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public License
8  * as published by the Free Software Foundation; version 2.1 of
9  * the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
19  * 02110-1301 USA
20  *
21  */
22
23 /**
24  * SECTION:hildon-window-stack
25  * @short_description: Object representing a stack of windows in the Hildon framework
26  * @see_also: #HildonStackableWindow
27  *
28  * The #HildonWindowStack is an object used to represent a stack of
29  * windows in the Hildon framework.
30  *
31  * Stacks contain all #HildonStackableWindow<!-- -->s that are being
32  * shown. The user can only interact with the topmost window from each
33  * stack (as it covers all the others), but all of them are mapped and
34  * visible from the Gtk point of view.
35  *
36  * Each window can only be in one stack at a time. All stacked windows
37  * are visible and all visible windows are stacked.
38  *
39  * Each application has a default stack, and windows are automatically
40  * added to it when they are shown with gtk_widget_show().
41  *
42  * Additional stacks can be created at any time using
43  * hildon_window_stack_new(). To add a window to a specific stack, use
44  * hildon_window_stack_push_1() (remember that, for the default stack,
45  * gtk_widget_show() can be used instead).
46  *
47  * To remove a window from a stack use hildon_window_stack_pop_1(), or
48  * simply gtk_widget_hide().
49  *
50  * For more complex layout changes, applications can push and/or pop
51  * several windows at the same time in a single step. See
52  * hildon_window_stack_push(), hildon_window_stack_pop() and
53  * hildon_window_stack_pop_and_push() for more details.
54  */
55
56 #include                                        "hildon-window-stack.h"
57 #include                                        "hildon-window-stack-private.h"
58 #include                                        "hildon-stackable-window-private.h"
59
60 struct                                          _HildonWindowStackPrivate
61 {
62     GList *list;
63     GtkWindowGroup *group;
64     GdkWindow *leader; /* X Window group hint for all windows in a group */
65 };
66
67 #define                                         HILDON_WINDOW_STACK_GET_PRIVATE(obj) \
68                                                 (G_TYPE_INSTANCE_GET_PRIVATE ((obj),\
69                                                 HILDON_TYPE_WINDOW_STACK, HildonWindowStackPrivate))
70
71 G_DEFINE_TYPE (HildonWindowStack, hildon_window_stack, G_TYPE_OBJECT);
72
73 /**
74  * hildon_window_stack_get_default:
75  *
76  * Returns the default window stack. This stack always exists and
77  * doesn't need to be created by the application.
78  *
79  * Return value: the default #HildonWindowStack
80  * 
81  * Since: 2.2
82  **/
83 HildonWindowStack *
84 hildon_window_stack_get_default                 (void)
85 {
86     static HildonWindowStack *stack = NULL;
87     if (G_UNLIKELY (stack == NULL)) {
88         stack = g_object_new (HILDON_TYPE_WINDOW_STACK, NULL);
89         stack->priv->group = gtk_window_get_group (NULL);
90     }
91     return stack;
92 }
93
94 /**
95  * hildon_window_stack_new:
96  *
97  * Creates a new #HildonWindowStack. The stack is initially empty.
98  *
99  * Return value: a new #HildonWindowStack
100  *
101  * Since: 2.2
102  **/
103 HildonWindowStack *
104 hildon_window_stack_new                         (void)
105 {
106     HildonWindowStack *stack = g_object_new (HILDON_TYPE_WINDOW_STACK, NULL);
107     stack->priv->group = gtk_window_group_new ();
108     return stack;
109 }
110
111 /**
112  * hildon_window_stack_size:
113  * @stack: A #HildonWindowStack
114  *
115  * Returns the number of windows in @stack
116  *
117  * Return value: Number of windows in @stack
118  *
119  * Since: 2.2
120  **/
121 gint
122 hildon_window_stack_size                        (HildonWindowStack *stack)
123 {
124     g_return_val_if_fail (HILDON_IS_WINDOW_STACK (stack), 0);
125
126     return g_list_length (stack->priv->list);
127 }
128
129 static GdkWindow *
130 hildon_window_stack_get_leader_window           (HildonWindowStack *stack,
131                                                  GtkWidget         *win)
132 {
133     /* Create the X Window group (leader) if we haven't. */
134     if (!stack->priv->leader) {
135         if (stack == hildon_window_stack_get_default ()) {
136             GdkDisplay *dpy;
137
138             /* We're the default stack, use the default group. */
139             dpy = gtk_widget_get_display (win);
140             stack->priv->leader = gdk_display_get_default_group (dpy);
141         } else {
142             static GdkWindowAttr attr = {
143                 .window_type = GDK_WINDOW_TOPLEVEL,
144                 .x = 10, .y = 10, .width = 10, .height = 10,
145                 .wclass = GDK_INPUT_OUTPUT, .event_mask = 0,
146             };
147             GdkWindow *root;
148
149             /* Create a new X Window group. */
150             root = gtk_widget_get_root_window (win);
151             stack->priv->leader = gdk_window_new (root, &attr, GDK_WA_X | GDK_WA_Y);
152         }
153     }
154
155     return stack->priv->leader;
156 }
157
158 /* Set the X Window group of a window when it is realized. */
159 static void
160 hildon_window_stack_window_realized             (GtkWidget         *win,
161                                                  HildonWindowStack *stack)
162 {
163     GdkWindow *leader = hildon_window_stack_get_leader_window (stack, win);
164     gdk_window_set_group (win->window, leader);
165 }
166
167 /* Remove a window from its stack, no matter its position */
168 void G_GNUC_INTERNAL
169 hildon_window_stack_remove                      (HildonStackableWindow *win)
170 {
171     HildonWindowStack *stack = hildon_stackable_window_get_stack (win);
172
173     /* If the window is stacked */
174     if (stack) {
175         GList *pos;
176
177         hildon_stackable_window_set_stack (win, NULL, -1);
178         gtk_window_set_transient_for (GTK_WINDOW (win), NULL);
179         if (GTK_WIDGET (win)->window) {
180             gdk_window_set_group (GTK_WIDGET (win)->window, NULL);
181         }
182
183         /* If the window removed is in the middle of the stack, update
184          * transiency of other windows */
185         pos = g_list_find (stack->priv->list, win);
186         g_assert (pos != NULL);
187         if (pos->prev) {
188             GtkWindow *upper = GTK_WINDOW (pos->prev->data);
189             GtkWindow *lower = pos->next ? GTK_WINDOW (pos->next->data) : NULL;
190             gtk_window_set_transient_for (upper, lower);
191         }
192
193         stack->priv->list = g_list_remove (stack->priv->list, win);
194
195         g_signal_handlers_disconnect_by_func (win, hildon_window_stack_window_realized, stack);
196     }
197 }
198
199 /**
200  * hildon_window_stack_peek:
201  * @stack: A %HildonWindowStack
202  *
203  * Returns the window on top of @stack. The stack is never modified.
204  *
205  * Return value: the window on top of the stack, or %NULL if the stack
206  * is empty.
207  *
208  * Since: 2.2
209  **/
210 GtkWidget *
211 hildon_window_stack_peek                        (HildonWindowStack *stack)
212 {
213     GtkWidget *win = NULL;
214
215     g_return_val_if_fail (HILDON_IS_WINDOW_STACK (stack), NULL);
216
217     if (stack->priv->list != NULL) {
218         win = GTK_WIDGET (stack->priv->list->data);
219     }
220
221     return win;
222 }
223
224 /* This function does everything to push a window to the stack _but_
225  * actually calling gtk_widget_show().
226  * It's up to each specific push function to decide the order in which
227  * to show windows. */
228 gboolean G_GNUC_INTERNAL
229 _hildon_window_stack_do_push                    (HildonWindowStack     *stack,
230                                                  HildonStackableWindow *win)
231 {
232     HildonWindowStack *current_stack;
233
234     g_return_val_if_fail (HILDON_IS_WINDOW_STACK (stack), FALSE);
235     g_return_val_if_fail (HILDON_IS_STACKABLE_WINDOW (win), FALSE);
236
237     current_stack = hildon_stackable_window_get_stack (win);
238
239     if (current_stack == NULL) {
240         GtkWidget *parent = hildon_window_stack_peek (stack);
241
242         /* Push the window */
243         hildon_stackable_window_set_stack (win, stack, g_list_length (stack->priv->list));
244         stack->priv->list = g_list_prepend (stack->priv->list, win);
245
246         /* Make the window part of the same group as its parent */
247         if (parent) {
248             gtk_window_set_transient_for (GTK_WINDOW (win), GTK_WINDOW (parent));
249         } else {
250             gtk_window_group_add_window (stack->priv->group, GTK_WINDOW (win));
251         }
252
253         /* Set win's group after it's been realized. */
254         g_signal_connect (win, "realize",
255                           G_CALLBACK (hildon_window_stack_window_realized),
256                           stack);
257
258         return TRUE;
259     } else {
260         g_warning ("Trying to push a window that is already on a stack");
261         return FALSE;
262     }
263 }
264
265 static GtkWidget *
266 _hildon_window_stack_do_pop                     (HildonWindowStack *stack)
267 {
268     GtkWidget *win = hildon_window_stack_peek (stack);
269
270     if (win)
271         hildon_window_stack_remove (HILDON_STACKABLE_WINDOW (win));
272
273     return win;
274 }
275
276 /**
277  * hildon_window_stack_push_1:
278  * @stack: A %HildonWindowStack
279  * @win: A %HildonStackableWindow
280  *
281  * Adds @win to the top of @stack, and shows it. The window must not
282  * be already stacked.
283  *
284  * Since: 2.2
285  **/
286 void
287 hildon_window_stack_push_1                      (HildonWindowStack     *stack,
288                                                  HildonStackableWindow *win)
289 {
290     if (_hildon_window_stack_do_push (stack, win))
291         gtk_widget_show (GTK_WIDGET (win));
292 }
293
294 /**
295  * hildon_window_stack_pop_1:
296  * @stack: A %HildonWindowStack
297  *
298  * Removes the window on top of @stack, and hides it. If the stack is
299  * empty nothing happens.
300  *
301  * Return value: the window on top of the stack, or %NULL if the stack
302  * is empty.
303  *
304  * Since: 2.2
305  **/
306 GtkWidget *
307 hildon_window_stack_pop_1                       (HildonWindowStack *stack)
308 {
309     GtkWidget *win = _hildon_window_stack_do_pop (stack);
310     if (win)
311         gtk_widget_hide (win);
312     return win;
313 }
314
315 /**
316  * hildon_window_stack_push_list:
317  * @stack: A %HildonWindowStack
318  * @list: A list of %HildonStackableWindow<!-- -->s to push
319  *
320  * Pushes all windows in @list to the top of @stack, and shows
321  * them. Everything is done in a single transition, so the user will
322  * only see the last window in @list during this operation. None of
323  * the windows must be already stacked.
324  *
325  * Since: 2.2
326  **/
327 void
328 hildon_window_stack_push_list                   (HildonWindowStack *stack,
329                                                  GList             *list)
330 {
331     HildonStackableWindow *win;
332     GList *l;
333     GList *pushed = NULL;
334
335     g_return_if_fail (HILDON_IS_WINDOW_STACK (stack));
336
337     /* Stack all windows */
338     for (l = list; l != NULL; l = g_list_next (l)) {
339         win = HILDON_STACKABLE_WINDOW (l->data);
340         if (win) {
341             _hildon_window_stack_do_push (stack, win);
342             pushed = g_list_prepend (pushed, win);
343         } else {
344             g_warning ("Trying to stack a non-stackable window!");
345         }
346     }
347
348     /* Show windows in reverse order (topmost first) */
349     g_list_foreach (pushed, (GFunc) gtk_widget_show, NULL);
350
351     g_list_free (pushed);
352 }
353
354 /**
355  * hildon_window_stack_push:
356  * @stack: A %HildonWindowStack
357  * @win1: The first window to push
358  * @Varargs: A %NULL-terminated list of additional #HildonStackableWindow<!-- -->s to push.
359  *
360  * Pushes all windows to the top of @stack, and shows them. Everything
361  * is done in a single transition, so the user will only see the last
362  * window. None of the windows must be already stacked.
363  *
364  * Since: 2.2
365  **/
366 void
367 hildon_window_stack_push                        (HildonWindowStack     *stack,
368                                                  HildonStackableWindow *win1,
369                                                  ...)
370 {
371     HildonStackableWindow *win;
372     GList *list = NULL;
373     va_list args;
374
375     va_start (args, win1);
376     win = va_arg (args, HildonStackableWindow *);
377
378     while (win != NULL) {
379         list = g_list_prepend (list, win);
380         win = va_arg (args, HildonStackableWindow *);
381     }
382
383     va_end (args);
384
385     hildon_window_stack_push_list (stack, list);
386     g_list_free (list);
387 }
388
389 /**
390  * hildon_window_stack_pop:
391  * @stack: A %HildonWindowStack
392  * @nwindows: Number of windows to pop
393  * @popped_windows: if non-%NULL, the list of popped windows is stored here
394  *
395  * Pops @nwindows windows from @stack, and hides them. Everything is
396  * done in a single transition, so the user will not see any of the
397  * windows being popped in this operation.
398  *
399  * If @popped_windows is not %NULL, the list of popped windows is
400  * stored there (ordered bottom-up). That list must be freed by the
401  * user.
402  *
403  * Since: 2.2
404  **/
405 void
406 hildon_window_stack_pop                         (HildonWindowStack  *stack,
407                                                  gint                nwindows,
408                                                  GList             **popped_windows)
409 {
410     gint i;
411     GList *popped = NULL;
412
413     g_return_if_fail (HILDON_IS_WINDOW_STACK (stack));
414     g_return_if_fail (nwindows > 0);
415     g_return_if_fail (g_list_length (stack->priv->list) >= nwindows);
416
417     /* Pop windows */
418     for (i = 0; i < nwindows; i++) {
419         GtkWidget *win = _hildon_window_stack_do_pop (stack);
420         popped = g_list_prepend (popped, win);
421     }
422
423     /* Hide windows in reverse order (topmost last) */
424     g_list_foreach (popped, (GFunc) gtk_widget_hide, NULL);
425
426     if (popped_windows) {
427         *popped_windows = popped;
428     } else {
429         g_list_free (popped);
430     }
431 }
432
433 /**
434  * hildon_window_stack_pop_and_push_list:
435  * @stack: A %HildonWindowStack
436  * @nwindows: Number of windows to pop.
437  * @popped_windows: if non-%NULL, the list of popped windows is stored here
438  * @list: A list of %HildonStackableWindow<!-- -->s to push
439  *
440  * Pops @nwindows windows from @stack (and hides them), then pushes
441  * all windows in @list (and shows them). Everything is done in a
442  * single transition, so the user will only see the last window from
443  * @list. None of the pushed windows must be already stacked.
444  *
445  * If @popped_windows is not %NULL, the list of popped windows is
446  * stored there (ordered bottom-up). That list must be freed by the
447  * user.
448  *
449  * Since: 2.2
450  **/
451 void
452 hildon_window_stack_pop_and_push_list           (HildonWindowStack  *stack,
453                                                  gint                nwindows,
454                                                  GList             **popped_windows,
455                                                  GList              *list)
456 {
457     gint i;
458     GList *l;
459     GList *popped = NULL;
460     GList *pushed = NULL;
461
462     g_return_if_fail (HILDON_IS_WINDOW_STACK (stack));
463     g_return_if_fail (nwindows > 0);
464     g_return_if_fail (g_list_length (stack->priv->list) >= nwindows);
465
466     /* Pop windows */
467     for (i = 0; i < nwindows; i++) {
468         GtkWidget *win = _hildon_window_stack_do_pop (stack);
469         popped = g_list_prepend (popped, win);
470     }
471
472     /* Push windows */
473     for (l = list; l != NULL; l = g_list_next (l)) {
474         HildonStackableWindow *win = HILDON_STACKABLE_WINDOW (l->data);
475         if (win) {
476             _hildon_window_stack_do_push (stack, win);
477             pushed = g_list_prepend (pushed, win);
478         } else {
479             g_warning ("Trying to stack a non-stackable window!");
480         }
481     }
482
483     /* Show windows in reverse order (topmost first) */
484     g_list_foreach (pushed, (GFunc) gtk_widget_show, NULL);
485
486     /* Hide windows in reverse order (topmost last) */
487     g_list_foreach (popped, (GFunc) gtk_widget_hide, NULL);
488
489     g_list_free (pushed);
490     if (popped_windows) {
491         *popped_windows = popped;
492     } else {
493         g_list_free (popped);
494     }
495 }
496
497 /**
498  * hildon_window_stack_pop_and_push:
499  * @stack: A %HildonWindowStack
500  * @nwindows: Number of windows to pop.
501  * @popped_windows: if non-%NULL, the list of popped windows is stored here
502  * @win1: The first window to push
503  * @Varargs: A %NULL-terminated list of additional #HildonStackableWindow<!-- -->s to push.
504  *
505  * Pops @nwindows windows from @stack (and hides them), then pushes
506  * all passed windows (and shows them). Everything is done in a single
507  * transition, so the user will only see the last pushed window. None
508  * of the pushed windows must be already stacked.
509  *
510  * If @popped_windows is not %NULL, the list of popped windows is
511  * stored there (ordered bottom-up). That list must be freed by the
512  * user.
513  *
514  * Since: 2.2
515  **/
516 void
517 hildon_window_stack_pop_and_push                (HildonWindowStack      *stack,
518                                                  gint                    nwindows,
519                                                  GList                 **popped_windows,
520                                                  HildonStackableWindow  *win1,
521                                                  ...)
522 {
523     HildonStackableWindow *win;
524     GList *list = NULL;
525     va_list args;
526
527     va_start (args, win1);
528     win = va_arg (args, HildonStackableWindow *);
529
530     while (win != NULL) {
531         list = g_list_prepend (list, win);
532         win = va_arg (args, HildonStackableWindow *);
533     }
534
535     va_end (args);
536
537     hildon_window_stack_pop_and_push_list (stack, nwindows, popped_windows, list);
538     g_list_free (list);
539 }
540
541 static void
542 hildon_window_stack_finalize (GObject *object)
543 {
544     HildonWindowStack *stack = HILDON_WINDOW_STACK (object);
545
546     if (stack->priv->list)
547         hildon_window_stack_pop (stack, hildon_window_stack_size (stack), NULL);
548
549     if (stack->priv->group)
550         g_object_unref (stack->priv->group);
551
552     /* Since the default group stack shouldn't be finalized,
553      * it's safe to destroy the X Window group we created. */
554     if (stack->priv->leader)
555         gdk_window_destroy (stack->priv->leader);
556
557     G_OBJECT_CLASS (hildon_window_stack_parent_class)->finalize (object);
558 }
559
560 static void
561 hildon_window_stack_class_init (HildonWindowStackClass *klass)
562 {
563     GObjectClass *gobject_class = (GObjectClass *)klass;
564
565     gobject_class->finalize = hildon_window_stack_finalize;
566
567     g_type_class_add_private (klass, sizeof (HildonWindowStackPrivate));
568 }
569
570 static void
571 hildon_window_stack_init (HildonWindowStack *self)
572 {
573     HildonWindowStackPrivate *priv;
574
575     priv = self->priv = HILDON_WINDOW_STACK_GET_PRIVATE (self);
576
577     priv->list = NULL;
578     priv->group = NULL;
579 }