2008-11-25 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 };
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 /**
73  * hildon_window_stack_get_default:
74  *
75  * Returns the default window stack. This stack always exists and
76  * doesn't need to be created by the application.
77  *
78  * Return value: the default #HildonWindowStack
79  **/
80 HildonWindowStack *
81 hildon_window_stack_get_default                 (void)
82 {
83     static HildonWindowStack *stack = NULL;
84     if (G_UNLIKELY (stack == NULL)) {
85         stack = g_object_new (HILDON_TYPE_WINDOW_STACK, NULL);
86         stack->priv->group = gtk_window_get_group (NULL);
87     }
88     return stack;
89 }
90
91 /**
92  * hildon_window_stack_new:
93  *
94  * Creates a new #HildonWindowStack. The stack is initially empty.
95  *
96  * Return value: a new #HildonWindowStack
97  **/
98 HildonWindowStack *
99 hildon_window_stack_new                         (void)
100 {
101     HildonWindowStack *stack = g_object_new (HILDON_TYPE_WINDOW_STACK, NULL);
102     stack->priv->group = gtk_window_group_new ();
103     return stack;
104 }
105
106 /**
107  * hildon_window_stack_size:
108  * @stack: A #HildonWindowStack
109  *
110  * Returns the number of windows in @stack
111  *
112  * Return value: Number of windows in @stack
113  **/
114 gint
115 hildon_window_stack_size                        (HildonWindowStack *stack)
116 {
117     g_return_val_if_fail (HILDON_IS_WINDOW_STACK (stack), 0);
118
119     return g_list_length (stack->priv->list);
120 }
121
122 /* Remove a window from its stack, no matter its position */
123 void G_GNUC_INTERNAL
124 hildon_window_stack_remove                      (HildonStackableWindow *win)
125 {
126     HildonWindowStack *stack = hildon_stackable_window_get_stack (win);
127
128     /* If the window is stacked */
129     if (stack) {
130         hildon_stackable_window_set_stack (win, NULL, -1);
131         stack->priv->list = g_list_remove (stack->priv->list, win);
132         gtk_window_set_transient_for (GTK_WINDOW (win), NULL);
133     }
134 }
135
136 /**
137  * hildon_window_stack_peek:
138  * @stack: A %HildonWindowStack
139  *
140  * Returns the window on top of @stack. The stack is never modified.
141  *
142  * Return value: the window on top of the stack, or %NULL if the stack
143  * is empty.
144  **/
145 GtkWidget *
146 hildon_window_stack_peek                        (HildonWindowStack *stack)
147 {
148     GtkWidget *win = NULL;
149
150     g_return_val_if_fail (HILDON_IS_WINDOW_STACK (stack), NULL);
151
152     if (stack->priv->list != NULL) {
153         win = GTK_WIDGET (stack->priv->list->data);
154     }
155
156     return win;
157 }
158
159 static gboolean
160 _hildon_window_stack_do_push                    (HildonWindowStack     *stack,
161                                                  HildonStackableWindow *win)
162 {
163     HildonWindowStack *current_stack;
164
165     g_return_val_if_fail (HILDON_IS_WINDOW_STACK (stack), FALSE);
166     g_return_val_if_fail (HILDON_IS_STACKABLE_WINDOW (win), FALSE);
167
168     current_stack = hildon_stackable_window_get_stack (win);
169
170     if (current_stack == NULL) {
171         GtkWidget *parent = hildon_window_stack_peek (stack);
172
173         /* Push the window */
174         hildon_stackable_window_set_stack (win, stack, g_list_length (stack->priv->list));
175         stack->priv->list = g_list_prepend (stack->priv->list, win);
176
177         /* Make the window part of the same group as its parent */
178         if (parent) {
179             gtk_window_set_transient_for (GTK_WINDOW (win), GTK_WINDOW (parent));
180         } else {
181             gtk_window_group_add_window (stack->priv->group, GTK_WINDOW (win));
182         }
183
184         return TRUE;
185     } else {
186         g_warning ("Trying to push a window that is already on a stack");
187         return FALSE;
188     }
189 }
190
191 static GtkWidget *
192 _hildon_window_stack_do_pop                     (HildonWindowStack *stack)
193 {
194     GtkWidget *win = hildon_window_stack_peek (stack);
195
196     if (win)
197         hildon_window_stack_remove (HILDON_STACKABLE_WINDOW (win));
198
199     return win;
200 }
201
202 /**
203  * hildon_window_stack_push_1:
204  * @stack: A %HildonWindowStack
205  * @win: A %HildonStackableWindow
206  *
207  * Adds @win to the top of @stack, and shows it. The window must not
208  * be already stacked.
209  **/
210 void
211 hildon_window_stack_push_1                      (HildonWindowStack     *stack,
212                                                  HildonStackableWindow *win)
213 {
214     if (_hildon_window_stack_do_push (stack, win))
215         gtk_widget_show (GTK_WIDGET (win));
216 }
217
218 /**
219  * hildon_window_stack_pop_1:
220  * @stack: A %HildonWindowStack
221  *
222  * Removes the window on top of @stack, and hides it. If the stack is
223  * empty nothing happens.
224  *
225  * Return value: the window on top of the stack, or %NULL if the stack
226  * is empty.
227  **/
228 GtkWidget *
229 hildon_window_stack_pop_1                       (HildonWindowStack *stack)
230 {
231     GtkWidget *win = _hildon_window_stack_do_pop (stack);
232     if (win)
233         gtk_widget_hide (win);
234     return win;
235 }
236
237 /**
238  * hildon_window_stack_push_list:
239  * @stack: A %HildonWindowStack
240  * @list: A list of %HildonStackableWindow<!-- -->s to push
241  *
242  * Pushes all windows in @list to the top of @stack, and shows
243  * them. Everything is done in a single transition, so the user will
244  * only see the last window in @list during this operation. None of
245  * the windows must be already stacked.
246  **/
247 void
248 hildon_window_stack_push_list                   (HildonWindowStack *stack,
249                                                  GList             *list)
250 {
251     HildonStackableWindow *win;
252     GList *l;
253     GList *pushed = NULL;
254
255     g_return_if_fail (HILDON_IS_WINDOW_STACK (stack));
256
257     /* Stack all windows */
258     for (l = list; l != NULL; l = g_list_next (l)) {
259         win = HILDON_STACKABLE_WINDOW (l->data);
260         if (win) {
261             _hildon_window_stack_do_push (stack, win);
262             pushed = g_list_prepend (pushed, win);
263         } else {
264             g_warning ("Trying to stack a non-stackable window!");
265         }
266     }
267
268     /* Show windows in reverse order (topmost first) */
269     g_list_foreach (pushed, (GFunc) gtk_widget_show, NULL);
270
271     g_list_free (pushed);
272 }
273
274 /**
275  * hildon_window_stack_push:
276  * @stack: A %HildonWindowStack
277  * @win1: The first window to push
278  * @Varargs: A %NULL-terminated list of additional #HildonStackableWindow<!-- -->s to push.
279  *
280  * Pushes all windows to the top of @stack, and shows them. Everything
281  * is done in a single transition, so the user will only see the last
282  * window. None of the windows must be already stacked.
283  **/
284 void
285 hildon_window_stack_push                        (HildonWindowStack     *stack,
286                                                  HildonStackableWindow *win1,
287                                                  ...)
288 {
289     HildonStackableWindow *win;
290     GList *list = NULL;
291     va_list args;
292
293     va_start (args, win1);
294     win = va_arg (args, HildonStackableWindow *);
295
296     while (win != NULL) {
297         list = g_list_prepend (list, win);
298         win = va_arg (args, HildonStackableWindow *);
299     }
300
301     va_end (args);
302
303     hildon_window_stack_push_list (stack, list);
304     g_list_free (list);
305 }
306
307 /**
308  * hildon_window_stack_pop:
309  * @stack: A %HildonWindowStack
310  * @nwindows: Number of windows to pop
311  * @popped_windows: if non-%NULL, the list of popped windows is stored here
312  *
313  * Pops @nwindows windows from @stack, and hides them. Everything is
314  * done in a single transition, so the user will not see any of the
315  * windows being popped in this operation.
316  *
317  * If @popped_windows is not %NULL, the list of popped windows is
318  * stored there (ordered bottom-up). That list must be freed by the
319  * user.
320  **/
321 void
322 hildon_window_stack_pop                         (HildonWindowStack  *stack,
323                                                  gint                nwindows,
324                                                  GList             **popped_windows)
325 {
326     gint i;
327     GList *popped = NULL;
328
329     g_return_if_fail (HILDON_IS_WINDOW_STACK (stack));
330     g_return_if_fail (nwindows > 0);
331     g_return_if_fail (g_list_length (stack->priv->list) >= nwindows);
332
333     /* Pop windows */
334     for (i = 0; i < nwindows; i++) {
335         GtkWidget *win = _hildon_window_stack_do_pop (stack);
336         popped = g_list_prepend (popped, win);
337     }
338
339     /* Hide windows in reverse order (topmost last) */
340     g_list_foreach (popped, (GFunc) gtk_widget_hide, NULL);
341
342     if (popped_windows) {
343         *popped_windows = popped;
344     } else {
345         g_list_free (popped);
346     }
347 }
348
349 /**
350  * hildon_window_stack_pop_and_push_list:
351  * @stack: A %HildonWindowStack
352  * @nwindows: Number of windows to pop.
353  * @popped_windows: if non-%NULL, the list of popped windows is stored here
354  * @list: A list of %HildonStackableWindow<!-- -->s to push
355  *
356  * Pops @nwindows windows from @stack (and hides them), then pushes
357  * all windows in @list (and shows them). Everything is done in a
358  * single transition, so the user will only see the last window from
359  * @list. None of the pushed windows must be already stacked.
360  *
361  * If @popped_windows is not %NULL, the list of popped windows is
362  * stored there (ordered bottom-up). That list must be freed by the
363  * user.
364  **/
365 void
366 hildon_window_stack_pop_and_push_list           (HildonWindowStack  *stack,
367                                                  gint                nwindows,
368                                                  GList             **popped_windows,
369                                                  GList              *list)
370 {
371     gint i;
372     GList *l;
373     GList *popped = NULL;
374     GList *pushed = NULL;
375
376     g_return_if_fail (HILDON_IS_WINDOW_STACK (stack));
377     g_return_if_fail (nwindows > 0);
378     g_return_if_fail (g_list_length (stack->priv->list) >= nwindows);
379
380     /* Pop windows */
381     for (i = 0; i < nwindows; i++) {
382         GtkWidget *win = _hildon_window_stack_do_pop (stack);
383         popped = g_list_prepend (popped, win);
384     }
385
386     /* Push windows */
387     for (l = list; l != NULL; l = g_list_next (l)) {
388         HildonStackableWindow *win = HILDON_STACKABLE_WINDOW (l->data);
389         if (win) {
390             _hildon_window_stack_do_push (stack, win);
391             pushed = g_list_prepend (pushed, win);
392         } else {
393             g_warning ("Trying to stack a non-stackable window!");
394         }
395     }
396
397     /* Show windows in reverse order (topmost first) */
398     g_list_foreach (pushed, (GFunc) gtk_widget_show, NULL);
399
400     /* Hide windows in reverse order (topmost last) */
401     g_list_foreach (popped, (GFunc) gtk_widget_hide, NULL);
402
403     g_list_free (pushed);
404     if (popped_windows) {
405         *popped_windows = popped;
406     } else {
407         g_list_free (popped);
408     }
409 }
410
411 /**
412  * hildon_window_stack_pop_and_push:
413  * @stack: A %HildonWindowStack
414  * @nwindows: Number of windows to pop.
415  * @popped_windows: if non-%NULL, the list of popped windows is stored here
416  * @win1: The first window to push
417  * @Varargs: A %NULL-terminated list of additional #HildonStackableWindow<!-- -->s to push.
418  *
419  * Pops @nwindows windows from @stack (and hides them), then pushes
420  * all passed windows (and shows them). Everything is done in a single
421  * transition, so the user will only see the last pushed window. None
422  * of the pushed windows must be already stacked.
423  *
424  * If @popped_windows is not %NULL, the list of popped windows is
425  * stored there (ordered bottom-up). That list must be freed by the
426  * user.
427  **/
428 void
429 hildon_window_stack_pop_and_push                (HildonWindowStack      *stack,
430                                                  gint                    nwindows,
431                                                  GList                 **popped_windows,
432                                                  HildonStackableWindow  *win1,
433                                                  ...)
434 {
435     HildonStackableWindow *win;
436     GList *list = NULL;
437     va_list args;
438
439     va_start (args, win1);
440     win = va_arg (args, HildonStackableWindow *);
441
442     while (win != NULL) {
443         list = g_list_prepend (list, win);
444         win = va_arg (args, HildonStackableWindow *);
445     }
446
447     va_end (args);
448
449     hildon_window_stack_pop_and_push_list (stack, nwindows, popped_windows, list);
450     g_list_free (list);
451 }
452
453 static void
454 hildon_window_stack_finalize (GObject *object)
455 {
456     HildonWindowStack *stack = HILDON_WINDOW_STACK (object);
457
458     if (stack->priv->list)
459         hildon_window_stack_pop (stack, hildon_window_stack_size (stack), NULL);
460
461     if (stack->priv->group)
462         g_object_unref (stack->priv->group);
463
464     G_OBJECT_CLASS (hildon_window_stack_parent_class)->finalize (object);
465 }
466
467 static void
468 hildon_window_stack_class_init (HildonWindowStackClass *klass)
469 {
470     GObjectClass *gobject_class = (GObjectClass *)klass;
471
472     gobject_class->finalize = hildon_window_stack_finalize;
473
474     g_type_class_add_private (klass, sizeof (HildonWindowStackPrivate));
475 }
476
477 static void
478 hildon_window_stack_init (HildonWindowStack *self)
479 {
480     HildonWindowStackPrivate *priv;
481
482     priv = self->priv = HILDON_WINDOW_STACK_GET_PRIVATE (self);
483
484     priv->list = NULL;
485     priv->group = NULL;
486 }