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