Check if a window is already realized when adding it to a stack
[hildon] / hildon / 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 enum {
74     PROP_GROUP = 1,
75 };
76
77 static void
78 hildon_window_stack_set_window_group             (HildonWindowStack *stack,
79                                                   GtkWindowGroup    *group)
80 {
81     g_return_if_fail (HILDON_IS_WINDOW_STACK (stack));
82     g_return_if_fail (!group || GTK_IS_WINDOW_GROUP (group));
83
84     /* The window group is only to be set once during construction */
85     g_return_if_fail (stack->priv->group == NULL);
86
87     if (!group)
88         group = gtk_window_group_new ();
89
90     stack->priv->group = group;
91 }
92
93 static GtkWindowGroup *
94 hildon_window_stack_get_window_group             (HildonWindowStack *stack)
95 {
96     g_return_val_if_fail (HILDON_IS_WINDOW_STACK (stack), NULL);
97
98     return stack->priv->group;
99 }
100
101 /**
102  * hildon_window_stack_get_default:
103  *
104  * Returns the default window stack. This stack always exists and
105  * doesn't need to be created by the application.
106  *
107  * Return value: the default #HildonWindowStack
108  *
109  * Since: 2.2
110  **/
111 HildonWindowStack *
112 hildon_window_stack_get_default                 (void)
113 {
114     static HildonWindowStack *stack = NULL;
115     if (G_UNLIKELY (stack == NULL)) {
116         stack = g_object_new (HILDON_TYPE_WINDOW_STACK,
117                               "window-group", gtk_window_get_group (NULL),
118                               NULL);
119     }
120     return stack;
121 }
122
123 /**
124  * hildon_window_stack_new:
125  *
126  * Creates a new #HildonWindowStack. The stack is initially empty.
127  *
128  * Return value: a new #HildonWindowStack
129  *
130  * Since: 2.2
131  **/
132 HildonWindowStack *
133 hildon_window_stack_new                         (void)
134 {
135     HildonWindowStack *stack = g_object_new (HILDON_TYPE_WINDOW_STACK, NULL);
136     return stack;
137 }
138
139 /**
140  * hildon_window_stack_size:
141  * @stack: A #HildonWindowStack
142  *
143  * Returns the number of windows in @stack
144  *
145  * Return value: Number of windows in @stack
146  *
147  * Since: 2.2
148  **/
149 gint
150 hildon_window_stack_size                        (HildonWindowStack *stack)
151 {
152     g_return_val_if_fail (HILDON_IS_WINDOW_STACK (stack), 0);
153
154     return g_list_length (stack->priv->list);
155 }
156
157 static GdkWindow *
158 hildon_window_stack_get_leader_window           (HildonWindowStack *stack,
159                                                  GtkWidget         *win)
160 {
161     /* Create the X Window group (leader) if we haven't. */
162     if (!stack->priv->leader) {
163         if (stack == hildon_window_stack_get_default ()) {
164             GdkDisplay *dpy;
165
166             /* We're the default stack, use the default group. */
167             dpy = gtk_widget_get_display (win);
168             stack->priv->leader = gdk_display_get_default_group (dpy);
169         } else {
170             static GdkWindowAttr attr = {
171                 .window_type = GDK_WINDOW_TOPLEVEL,
172                 .x = 10, .y = 10, .width = 10, .height = 10,
173                 .wclass = GDK_INPUT_OUTPUT, .event_mask = 0,
174             };
175             GdkWindow *root;
176
177             /* Create a new X Window group. */
178             root = gtk_widget_get_root_window (win);
179             stack->priv->leader = gdk_window_new (root, &attr, GDK_WA_X | GDK_WA_Y);
180         }
181     }
182
183     return stack->priv->leader;
184 }
185
186 /* Set the X Window group of a window when it is realized. */
187 static void
188 hildon_window_stack_window_realized             (GtkWidget         *win,
189                                                  HildonWindowStack *stack)
190 {
191     GdkWindow *leader = hildon_window_stack_get_leader_window (stack, win);
192     gdk_window_set_group (win->window, leader);
193 }
194
195 /* Remove a window from its stack, no matter its position */
196 void G_GNUC_INTERNAL
197 hildon_window_stack_remove                      (HildonStackableWindow *win)
198 {
199     HildonWindowStack *stack = hildon_stackable_window_get_stack (win);
200
201     /* If the window is stacked */
202     if (stack) {
203         GList *pos;
204
205         hildon_stackable_window_set_stack (win, NULL, -1);
206         gtk_window_set_transient_for (GTK_WINDOW (win), NULL);
207         if (GTK_WIDGET (win)->window) {
208             gdk_window_set_group (GTK_WIDGET (win)->window, NULL);
209         }
210
211         /* If the window removed is in the middle of the stack, update
212          * transiency of other windows */
213         pos = g_list_find (stack->priv->list, win);
214         g_assert (pos != NULL);
215         if (pos->prev) {
216             GtkWindow *upper = GTK_WINDOW (pos->prev->data);
217             GtkWindow *lower = pos->next ? GTK_WINDOW (pos->next->data) : NULL;
218             gtk_window_set_transient_for (upper, lower);
219         }
220
221         stack->priv->list = g_list_remove (stack->priv->list, win);
222
223         g_signal_handlers_disconnect_by_func (win, hildon_window_stack_window_realized, stack);
224     }
225 }
226
227 /**
228  * hildon_window_stack_get_windows:
229  * @stack: a #HildonWindowStack
230  *
231  * Returns the list of windows on this stack (topmost first). The
232  * widgets in the list are not individually referenced. Once you are
233  * done with the list you must call g_list_free().
234  *
235  * Returns: a newly-allocated list of #HildonStackableWindow<!-- -->s
236  *
237  * Since: 2.2
238  **/
239 GList *
240 hildon_window_stack_get_windows                 (HildonWindowStack *stack)
241 {
242     g_return_val_if_fail (HILDON_IS_WINDOW_STACK (stack), NULL);
243
244     return g_list_copy (stack->priv->list);
245 }
246
247 /**
248  * hildon_window_stack_peek:
249  * @stack: A %HildonWindowStack
250  *
251  * Returns the window on top of @stack. The stack is never modified.
252  *
253  * Return value: the window on top of the stack, or %NULL if the stack
254  * is empty.
255  *
256  * Since: 2.2
257  **/
258 GtkWidget *
259 hildon_window_stack_peek                        (HildonWindowStack *stack)
260 {
261     GtkWidget *win = NULL;
262
263     g_return_val_if_fail (HILDON_IS_WINDOW_STACK (stack), NULL);
264
265     if (stack->priv->list != NULL) {
266         win = GTK_WIDGET (stack->priv->list->data);
267     }
268
269     return win;
270 }
271
272 /* This function does everything to push a window to the stack _but_
273  * actually calling gtk_widget_show().
274  * It's up to each specific push function to decide the order in which
275  * to show windows. */
276 gboolean G_GNUC_INTERNAL
277 _hildon_window_stack_do_push                    (HildonWindowStack     *stack,
278                                                  HildonStackableWindow *win)
279 {
280     HildonWindowStack *current_stack;
281
282     g_return_val_if_fail (HILDON_IS_WINDOW_STACK (stack), FALSE);
283     g_return_val_if_fail (HILDON_IS_STACKABLE_WINDOW (win), FALSE);
284
285     current_stack = hildon_stackable_window_get_stack (win);
286
287     if (current_stack == NULL) {
288         GtkWidget *parent = hildon_window_stack_peek (stack);
289
290         /* Push the window */
291         hildon_stackable_window_set_stack (win, stack, g_list_length (stack->priv->list));
292         stack->priv->list = g_list_prepend (stack->priv->list, win);
293
294         /* Make the window part of the same group as its parent */
295         if (parent) {
296             gtk_window_set_transient_for (GTK_WINDOW (win), GTK_WINDOW (parent));
297         } else {
298             gtk_window_group_add_window (stack->priv->group, GTK_WINDOW (win));
299         }
300
301         /* Set window group */
302         if (GTK_WIDGET_REALIZED (win)) {
303             hildon_window_stack_window_realized (GTK_WIDGET (win), stack);
304         } else {
305             g_signal_connect (win, "realize",
306                               G_CALLBACK (hildon_window_stack_window_realized),
307                               stack);
308         }
309
310         return TRUE;
311     } else {
312         g_warning ("Trying to push a window that is already on a stack");
313         return FALSE;
314     }
315 }
316
317 static GtkWidget *
318 _hildon_window_stack_do_pop                     (HildonWindowStack *stack)
319 {
320     GtkWidget *win = hildon_window_stack_peek (stack);
321
322     if (win)
323         hildon_window_stack_remove (HILDON_STACKABLE_WINDOW (win));
324
325     return win;
326 }
327
328 /**
329  * hildon_window_stack_push_1:
330  * @stack: A %HildonWindowStack
331  * @win: A %HildonStackableWindow
332  *
333  * Adds @win to the top of @stack, and shows it. The window must not
334  * be already stacked.
335  *
336  * Since: 2.2
337  **/
338 void
339 hildon_window_stack_push_1                      (HildonWindowStack     *stack,
340                                                  HildonStackableWindow *win)
341 {
342     if (_hildon_window_stack_do_push (stack, win))
343         gtk_widget_show (GTK_WIDGET (win));
344 }
345
346 /**
347  * hildon_window_stack_pop_1:
348  * @stack: A %HildonWindowStack
349  *
350  * Removes the window on top of @stack, and hides it. If the stack is
351  * empty nothing happens.
352  *
353  * Return value: the window on top of the stack, or %NULL if the stack
354  * is empty.
355  *
356  * Since: 2.2
357  **/
358 GtkWidget *
359 hildon_window_stack_pop_1                       (HildonWindowStack *stack)
360 {
361     GtkWidget *win = _hildon_window_stack_do_pop (stack);
362     if (win)
363         gtk_widget_hide (win);
364     return win;
365 }
366
367 /**
368  * hildon_window_stack_push_list:
369  * @stack: A %HildonWindowStack
370  * @list: A list of %HildonStackableWindow<!-- -->s to push
371  *
372  * Pushes all windows in @list to the top of @stack, and shows
373  * them. Everything is done in a single transition, so the user will
374  * only see the last window in @list during this operation. None of
375  * the windows must be already stacked.
376  *
377  * Since: 2.2
378  **/
379 void
380 hildon_window_stack_push_list                   (HildonWindowStack *stack,
381                                                  GList             *list)
382 {
383     HildonStackableWindow *win;
384     GList *l;
385     GList *pushed = NULL;
386
387     g_return_if_fail (HILDON_IS_WINDOW_STACK (stack));
388
389     /* Stack all windows */
390     for (l = list; l != NULL; l = g_list_next (l)) {
391         win = HILDON_STACKABLE_WINDOW (l->data);
392         if (win) {
393             _hildon_window_stack_do_push (stack, win);
394             pushed = g_list_prepend (pushed, win);
395         } else {
396             g_warning ("Trying to stack a non-stackable window!");
397         }
398     }
399
400     /* Show windows in reverse order (topmost first) */
401     g_list_foreach (pushed, (GFunc) gtk_widget_show, NULL);
402
403     g_list_free (pushed);
404 }
405
406 /**
407  * hildon_window_stack_push:
408  * @stack: A %HildonWindowStack
409  * @win1: The first window to push
410  * @Varargs: A %NULL-terminated list of additional #HildonStackableWindow<!-- -->s to push.
411  *
412  * Pushes all windows to the top of @stack, and shows them. Everything
413  * is done in a single transition, so the user will only see the last
414  * window. None of the windows must be already stacked.
415  *
416  * Since: 2.2
417  **/
418 void
419 hildon_window_stack_push                        (HildonWindowStack     *stack,
420                                                  HildonStackableWindow *win1,
421                                                  ...)
422 {
423     HildonStackableWindow *win = win1;
424     GList *list = NULL;
425     va_list args;
426
427     va_start (args, win1);
428
429     while (win != NULL) {
430         list = g_list_prepend (list, win);
431         win = va_arg (args, HildonStackableWindow *);
432     }
433
434     va_end (args);
435
436     list = g_list_reverse (list);
437
438     hildon_window_stack_push_list (stack, list);
439     g_list_free (list);
440 }
441
442 /**
443  * hildon_window_stack_pop:
444  * @stack: A %HildonWindowStack
445  * @nwindows: Number of windows to pop
446  * @popped_windows: if non-%NULL, the list of popped windows is stored here
447  *
448  * Pops @nwindows windows from @stack, and hides them. Everything is
449  * done in a single transition, so the user will not see any of the
450  * windows being popped in this operation.
451  *
452  * If @popped_windows is not %NULL, the list of popped windows is
453  * stored there (ordered bottom-up). That list must be freed by the
454  * user.
455  *
456  * Since: 2.2
457  **/
458 void
459 hildon_window_stack_pop                         (HildonWindowStack  *stack,
460                                                  gint                nwindows,
461                                                  GList             **popped_windows)
462 {
463     gint i;
464     GList *popped = NULL;
465
466     g_return_if_fail (HILDON_IS_WINDOW_STACK (stack));
467     g_return_if_fail (nwindows > 0);
468     g_return_if_fail (g_list_length (stack->priv->list) >= nwindows);
469
470     /* Pop windows */
471     for (i = 0; i < nwindows; i++) {
472         GtkWidget *win = _hildon_window_stack_do_pop (stack);
473         popped = g_list_prepend (popped, win);
474     }
475
476     /* Hide windows in reverse order (topmost last) */
477     g_list_foreach (popped, (GFunc) gtk_widget_hide, NULL);
478
479     if (popped_windows) {
480         *popped_windows = popped;
481     } else {
482         g_list_free (popped);
483     }
484 }
485
486 /**
487  * hildon_window_stack_pop_and_push_list:
488  * @stack: A %HildonWindowStack
489  * @nwindows: Number of windows to pop.
490  * @popped_windows: if non-%NULL, the list of popped windows is stored here
491  * @list: A list of %HildonStackableWindow<!-- -->s to push
492  *
493  * Pops @nwindows windows from @stack (and hides them), then pushes
494  * all windows in @list (and shows them). Everything is done in a
495  * single transition, so the user will only see the last window from
496  * @list. None of the pushed windows must be already stacked.
497  *
498  * If @popped_windows is not %NULL, the list of popped windows is
499  * stored there (ordered bottom-up). That list must be freed by the
500  * user.
501  *
502  * Since: 2.2
503  **/
504 void
505 hildon_window_stack_pop_and_push_list           (HildonWindowStack  *stack,
506                                                  gint                nwindows,
507                                                  GList             **popped_windows,
508                                                  GList              *list)
509 {
510     gint i;
511     GList *l;
512     GList *popped = NULL;
513     GList *pushed = NULL;
514
515     g_return_if_fail (HILDON_IS_WINDOW_STACK (stack));
516     g_return_if_fail (nwindows > 0);
517     g_return_if_fail (g_list_length (stack->priv->list) >= nwindows);
518
519     /* Pop windows */
520     for (i = 0; i < nwindows; i++) {
521         GtkWidget *win = _hildon_window_stack_do_pop (stack);
522         popped = g_list_prepend (popped, win);
523     }
524
525     /* Push windows */
526     for (l = list; l != NULL; l = g_list_next (l)) {
527         HildonStackableWindow *win = HILDON_STACKABLE_WINDOW (l->data);
528         if (win) {
529             _hildon_window_stack_do_push (stack, win);
530             pushed = g_list_prepend (pushed, win);
531         } else {
532             g_warning ("Trying to stack a non-stackable window!");
533         }
534     }
535
536     /* Show windows in reverse order (topmost first) */
537     g_list_foreach (pushed, (GFunc) gtk_widget_show, NULL);
538
539     /* Hide windows in reverse order (topmost last) */
540     g_list_foreach (popped, (GFunc) gtk_widget_hide, NULL);
541
542     g_list_free (pushed);
543     if (popped_windows) {
544         *popped_windows = popped;
545     } else {
546         g_list_free (popped);
547     }
548 }
549
550 /**
551  * hildon_window_stack_pop_and_push:
552  * @stack: A %HildonWindowStack
553  * @nwindows: Number of windows to pop.
554  * @popped_windows: if non-%NULL, the list of popped windows is stored here
555  * @win1: The first window to push
556  * @Varargs: A %NULL-terminated list of additional #HildonStackableWindow<!-- -->s to push.
557  *
558  * Pops @nwindows windows from @stack (and hides them), then pushes
559  * all passed windows (and shows them). Everything is done in a single
560  * transition, so the user will only see the last pushed window. None
561  * of the pushed windows must be already stacked.
562  *
563  * If @popped_windows is not %NULL, the list of popped windows is
564  * stored there (ordered bottom-up). That list must be freed by the
565  * user.
566  *
567  * Since: 2.2
568  **/
569 void
570 hildon_window_stack_pop_and_push                (HildonWindowStack      *stack,
571                                                  gint                    nwindows,
572                                                  GList                 **popped_windows,
573                                                  HildonStackableWindow  *win1,
574                                                  ...)
575 {
576     HildonStackableWindow *win = win1;
577     GList *list = NULL;
578     va_list args;
579
580     va_start (args, win1);
581
582     while (win != NULL) {
583         list = g_list_prepend (list, win);
584         win = va_arg (args, HildonStackableWindow *);
585     }
586
587     va_end (args);
588
589     list = g_list_reverse (list);
590
591     hildon_window_stack_pop_and_push_list (stack, nwindows, popped_windows, list);
592     g_list_free (list);
593 }
594
595 static void
596 hildon_window_stack_finalize (GObject *object)
597 {
598     HildonWindowStack *stack = HILDON_WINDOW_STACK (object);
599
600     if (stack->priv->list)
601         hildon_window_stack_pop (stack, hildon_window_stack_size (stack), NULL);
602
603     if (stack->priv->group)
604         g_object_unref (stack->priv->group);
605
606     /* Since the default group stack shouldn't be finalized,
607      * it's safe to destroy the X Window group we created. */
608     if (stack->priv->leader)
609         gdk_window_destroy (stack->priv->leader);
610
611     G_OBJECT_CLASS (hildon_window_stack_parent_class)->finalize (object);
612 }
613
614 static void
615 hildon_window_stack_set_property                (GObject      *object,
616                                                  guint         prop_id,
617                                                  const GValue *value,
618                                                  GParamSpec   *pspec)
619 {
620     HildonWindowStack *stack = HILDON_WINDOW_STACK (object);
621
622     switch (prop_id)
623     {
624     case PROP_GROUP:
625         hildon_window_stack_set_window_group (stack, g_value_get_object (value));
626         break;
627     default:
628         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
629         break;
630     }
631 }
632
633 static void
634 hildon_window_stack_get_property                (GObject    *object,
635                                                  guint       prop_id,
636                                                  GValue     *value,
637                                                  GParamSpec *pspec)
638 {
639     HildonWindowStack *stack = HILDON_WINDOW_STACK (object);
640
641     switch (prop_id)
642     {
643     case PROP_GROUP:
644         g_value_set_object (value, hildon_window_stack_get_window_group (stack));
645         break;
646     default:
647         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
648         break;
649     }
650 }
651
652 static void
653 hildon_window_stack_class_init (HildonWindowStackClass *klass)
654 {
655     GObjectClass *gobject_class = (GObjectClass *)klass;
656
657     gobject_class->set_property = hildon_window_stack_set_property;
658     gobject_class->get_property = hildon_window_stack_get_property;
659     gobject_class->finalize = hildon_window_stack_finalize;
660
661     g_object_class_install_property (
662         gobject_class,
663         PROP_GROUP,
664         g_param_spec_object (
665             "window-group",
666             "GtkWindowGroup for this stack",
667             "GtkWindowGroup that all windows on this stack belong to",
668             GTK_TYPE_WINDOW_GROUP,
669             G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
670
671     g_type_class_add_private (klass, sizeof (HildonWindowStackPrivate));
672 }
673
674 static void
675 hildon_window_stack_init (HildonWindowStack *self)
676 {
677     HildonWindowStackPrivate *priv;
678
679     priv = self->priv = HILDON_WINDOW_STACK_GET_PRIVATE (self);
680
681     priv->list = NULL;
682     priv->group = NULL;
683 }