2008-12-12 Claudio Saavedra <csaavedra@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         hildon_stackable_window_set_stack (win, NULL, -1);
176         stack->priv->list = g_list_remove (stack->priv->list, win);
177         gtk_window_set_transient_for (GTK_WINDOW (win), NULL);
178         if (GTK_WIDGET (win)->window) {
179             gdk_window_set_group (GTK_WIDGET (win)->window, NULL);
180         }
181         g_signal_handlers_disconnect_by_func (win, hildon_window_stack_window_realized, stack);
182     }
183 }
184
185 /**
186  * hildon_window_stack_peek:
187  * @stack: A %HildonWindowStack
188  *
189  * Returns the window on top of @stack. The stack is never modified.
190  *
191  * Return value: the window on top of the stack, or %NULL if the stack
192  * is empty.
193  *
194  * Since: 2.2
195  **/
196 GtkWidget *
197 hildon_window_stack_peek                        (HildonWindowStack *stack)
198 {
199     GtkWidget *win = NULL;
200
201     g_return_val_if_fail (HILDON_IS_WINDOW_STACK (stack), NULL);
202
203     if (stack->priv->list != NULL) {
204         win = GTK_WIDGET (stack->priv->list->data);
205     }
206
207     return win;
208 }
209
210 static gboolean
211 _hildon_window_stack_do_push                    (HildonWindowStack     *stack,
212                                                  HildonStackableWindow *win)
213 {
214     HildonWindowStack *current_stack;
215
216     g_return_val_if_fail (HILDON_IS_WINDOW_STACK (stack), FALSE);
217     g_return_val_if_fail (HILDON_IS_STACKABLE_WINDOW (win), FALSE);
218
219     current_stack = hildon_stackable_window_get_stack (win);
220
221     if (current_stack == NULL) {
222         GtkWidget *parent = hildon_window_stack_peek (stack);
223
224         /* Push the window */
225         hildon_stackable_window_set_stack (win, stack, g_list_length (stack->priv->list));
226         stack->priv->list = g_list_prepend (stack->priv->list, win);
227
228         /* Make the window part of the same group as its parent */
229         if (parent) {
230             gtk_window_set_transient_for (GTK_WINDOW (win), GTK_WINDOW (parent));
231         } else {
232             gtk_window_group_add_window (stack->priv->group, GTK_WINDOW (win));
233         }
234
235         /* Set win's group after it's been realized. */
236         g_signal_connect (win, "realize",
237                           G_CALLBACK (hildon_window_stack_window_realized),
238                           stack);
239
240         return TRUE;
241     } else {
242         g_warning ("Trying to push a window that is already on a stack");
243         return FALSE;
244     }
245 }
246
247 static GtkWidget *
248 _hildon_window_stack_do_pop                     (HildonWindowStack *stack)
249 {
250     GtkWidget *win = hildon_window_stack_peek (stack);
251
252     if (win)
253         hildon_window_stack_remove (HILDON_STACKABLE_WINDOW (win));
254
255     return win;
256 }
257
258 /**
259  * hildon_window_stack_push_1:
260  * @stack: A %HildonWindowStack
261  * @win: A %HildonStackableWindow
262  *
263  * Adds @win to the top of @stack, and shows it. The window must not
264  * be already stacked.
265  *
266  * Since: 2.2
267  **/
268 void
269 hildon_window_stack_push_1                      (HildonWindowStack     *stack,
270                                                  HildonStackableWindow *win)
271 {
272     if (_hildon_window_stack_do_push (stack, win))
273         gtk_widget_show (GTK_WIDGET (win));
274 }
275
276 /**
277  * hildon_window_stack_pop_1:
278  * @stack: A %HildonWindowStack
279  *
280  * Removes the window on top of @stack, and hides it. If the stack is
281  * empty nothing happens.
282  *
283  * Return value: the window on top of the stack, or %NULL if the stack
284  * is empty.
285  *
286  * Since: 2.2
287  **/
288 GtkWidget *
289 hildon_window_stack_pop_1                       (HildonWindowStack *stack)
290 {
291     GtkWidget *win = _hildon_window_stack_do_pop (stack);
292     if (win)
293         gtk_widget_hide (win);
294     return win;
295 }
296
297 /**
298  * hildon_window_stack_push_list:
299  * @stack: A %HildonWindowStack
300  * @list: A list of %HildonStackableWindow<!-- -->s to push
301  *
302  * Pushes all windows in @list to the top of @stack, and shows
303  * them. Everything is done in a single transition, so the user will
304  * only see the last window in @list during this operation. None of
305  * the windows must be already stacked.
306  *
307  * Since: 2.2
308  **/
309 void
310 hildon_window_stack_push_list                   (HildonWindowStack *stack,
311                                                  GList             *list)
312 {
313     HildonStackableWindow *win;
314     GList *l;
315     GList *pushed = NULL;
316
317     g_return_if_fail (HILDON_IS_WINDOW_STACK (stack));
318
319     /* Stack all windows */
320     for (l = list; l != NULL; l = g_list_next (l)) {
321         win = HILDON_STACKABLE_WINDOW (l->data);
322         if (win) {
323             _hildon_window_stack_do_push (stack, win);
324             pushed = g_list_prepend (pushed, win);
325         } else {
326             g_warning ("Trying to stack a non-stackable window!");
327         }
328     }
329
330     /* Show windows in reverse order (topmost first) */
331     g_list_foreach (pushed, (GFunc) gtk_widget_show, NULL);
332
333     g_list_free (pushed);
334 }
335
336 /**
337  * hildon_window_stack_push:
338  * @stack: A %HildonWindowStack
339  * @win1: The first window to push
340  * @Varargs: A %NULL-terminated list of additional #HildonStackableWindow<!-- -->s to push.
341  *
342  * Pushes all windows to the top of @stack, and shows them. Everything
343  * is done in a single transition, so the user will only see the last
344  * window. None of the windows must be already stacked.
345  *
346  * Since: 2.2
347  **/
348 void
349 hildon_window_stack_push                        (HildonWindowStack     *stack,
350                                                  HildonStackableWindow *win1,
351                                                  ...)
352 {
353     HildonStackableWindow *win;
354     GList *list = NULL;
355     va_list args;
356
357     va_start (args, win1);
358     win = va_arg (args, HildonStackableWindow *);
359
360     while (win != NULL) {
361         list = g_list_prepend (list, win);
362         win = va_arg (args, HildonStackableWindow *);
363     }
364
365     va_end (args);
366
367     hildon_window_stack_push_list (stack, list);
368     g_list_free (list);
369 }
370
371 /**
372  * hildon_window_stack_pop:
373  * @stack: A %HildonWindowStack
374  * @nwindows: Number of windows to pop
375  * @popped_windows: if non-%NULL, the list of popped windows is stored here
376  *
377  * Pops @nwindows windows from @stack, and hides them. Everything is
378  * done in a single transition, so the user will not see any of the
379  * windows being popped in this operation.
380  *
381  * If @popped_windows is not %NULL, the list of popped windows is
382  * stored there (ordered bottom-up). That list must be freed by the
383  * user.
384  *
385  * Since: 2.2
386  **/
387 void
388 hildon_window_stack_pop                         (HildonWindowStack  *stack,
389                                                  gint                nwindows,
390                                                  GList             **popped_windows)
391 {
392     gint i;
393     GList *popped = NULL;
394
395     g_return_if_fail (HILDON_IS_WINDOW_STACK (stack));
396     g_return_if_fail (nwindows > 0);
397     g_return_if_fail (g_list_length (stack->priv->list) >= nwindows);
398
399     /* Pop windows */
400     for (i = 0; i < nwindows; i++) {
401         GtkWidget *win = _hildon_window_stack_do_pop (stack);
402         popped = g_list_prepend (popped, win);
403     }
404
405     /* Hide windows in reverse order (topmost last) */
406     g_list_foreach (popped, (GFunc) gtk_widget_hide, NULL);
407
408     if (popped_windows) {
409         *popped_windows = popped;
410     } else {
411         g_list_free (popped);
412     }
413 }
414
415 /**
416  * hildon_window_stack_pop_and_push_list:
417  * @stack: A %HildonWindowStack
418  * @nwindows: Number of windows to pop.
419  * @popped_windows: if non-%NULL, the list of popped windows is stored here
420  * @list: A list of %HildonStackableWindow<!-- -->s to push
421  *
422  * Pops @nwindows windows from @stack (and hides them), then pushes
423  * all windows in @list (and shows them). Everything is done in a
424  * single transition, so the user will only see the last window from
425  * @list. None of the pushed windows must be already stacked.
426  *
427  * If @popped_windows is not %NULL, the list of popped windows is
428  * stored there (ordered bottom-up). That list must be freed by the
429  * user.
430  *
431  * Since: 2.2
432  **/
433 void
434 hildon_window_stack_pop_and_push_list           (HildonWindowStack  *stack,
435                                                  gint                nwindows,
436                                                  GList             **popped_windows,
437                                                  GList              *list)
438 {
439     gint i;
440     GList *l;
441     GList *popped = NULL;
442     GList *pushed = NULL;
443
444     g_return_if_fail (HILDON_IS_WINDOW_STACK (stack));
445     g_return_if_fail (nwindows > 0);
446     g_return_if_fail (g_list_length (stack->priv->list) >= nwindows);
447
448     /* Pop windows */
449     for (i = 0; i < nwindows; i++) {
450         GtkWidget *win = _hildon_window_stack_do_pop (stack);
451         popped = g_list_prepend (popped, win);
452     }
453
454     /* Push windows */
455     for (l = list; l != NULL; l = g_list_next (l)) {
456         HildonStackableWindow *win = HILDON_STACKABLE_WINDOW (l->data);
457         if (win) {
458             _hildon_window_stack_do_push (stack, win);
459             pushed = g_list_prepend (pushed, win);
460         } else {
461             g_warning ("Trying to stack a non-stackable window!");
462         }
463     }
464
465     /* Show windows in reverse order (topmost first) */
466     g_list_foreach (pushed, (GFunc) gtk_widget_show, NULL);
467
468     /* Hide windows in reverse order (topmost last) */
469     g_list_foreach (popped, (GFunc) gtk_widget_hide, NULL);
470
471     g_list_free (pushed);
472     if (popped_windows) {
473         *popped_windows = popped;
474     } else {
475         g_list_free (popped);
476     }
477 }
478
479 /**
480  * hildon_window_stack_pop_and_push:
481  * @stack: A %HildonWindowStack
482  * @nwindows: Number of windows to pop.
483  * @popped_windows: if non-%NULL, the list of popped windows is stored here
484  * @win1: The first window to push
485  * @Varargs: A %NULL-terminated list of additional #HildonStackableWindow<!-- -->s to push.
486  *
487  * Pops @nwindows windows from @stack (and hides them), then pushes
488  * all passed windows (and shows them). Everything is done in a single
489  * transition, so the user will only see the last pushed window. None
490  * of the pushed windows must be already stacked.
491  *
492  * If @popped_windows is not %NULL, the list of popped windows is
493  * stored there (ordered bottom-up). That list must be freed by the
494  * user.
495  *
496  * Since: 2.2
497  **/
498 void
499 hildon_window_stack_pop_and_push                (HildonWindowStack      *stack,
500                                                  gint                    nwindows,
501                                                  GList                 **popped_windows,
502                                                  HildonStackableWindow  *win1,
503                                                  ...)
504 {
505     HildonStackableWindow *win;
506     GList *list = NULL;
507     va_list args;
508
509     va_start (args, win1);
510     win = va_arg (args, HildonStackableWindow *);
511
512     while (win != NULL) {
513         list = g_list_prepend (list, win);
514         win = va_arg (args, HildonStackableWindow *);
515     }
516
517     va_end (args);
518
519     hildon_window_stack_pop_and_push_list (stack, nwindows, popped_windows, list);
520     g_list_free (list);
521 }
522
523 static void
524 hildon_window_stack_finalize (GObject *object)
525 {
526     HildonWindowStack *stack = HILDON_WINDOW_STACK (object);
527
528     if (stack->priv->list)
529         hildon_window_stack_pop (stack, hildon_window_stack_size (stack), NULL);
530
531     if (stack->priv->group)
532         g_object_unref (stack->priv->group);
533
534     /* Since the default group stack shouldn't be finalized,
535      * it's safe to destroy the X Window group we created. */
536     if (stack->priv->leader)
537         gdk_window_destroy (stack->priv->leader);
538
539     G_OBJECT_CLASS (hildon_window_stack_parent_class)->finalize (object);
540 }
541
542 static void
543 hildon_window_stack_class_init (HildonWindowStackClass *klass)
544 {
545     GObjectClass *gobject_class = (GObjectClass *)klass;
546
547     gobject_class->finalize = hildon_window_stack_finalize;
548
549     g_type_class_add_private (klass, sizeof (HildonWindowStackPrivate));
550 }
551
552 static void
553 hildon_window_stack_init (HildonWindowStack *self)
554 {
555     HildonWindowStackPrivate *priv;
556
557     priv = self->priv = HILDON_WINDOW_STACK_GET_PRIVATE (self);
558
559     priv->list = NULL;
560     priv->group = NULL;
561 }