2008-07-29 Claudio Saavedra <csaavedra@igalia.com>
[hildon] / src / hildon-stackable-window.c
1 /*
2  * This file is a part of hildon
3  *
4  * Copyright (C) 2008 Nokia Corporation, all rights reserved.
5  *
6  * Contact: Karl Lattimer <karl.lattimer@nokia.com>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public License
10  * as published by the Free Software Foundation; version 2.1 of
11  * the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21  * 02110-1301 USA
22  *
23  */
24
25 /**
26  * SECTION:hildon-stackable-window
27  * @short_description: Widget representing a stackable, top-level window in the Hildon framework.
28  *
29  * The #HildonStackableWindow is a GTK+ widget which represents a
30  * top-level window in the Hildon framework. It is derived from
31  * #HildonWindow. Applications that use stackable windows are organized
32  * in a hierarchical way so users can go from any window back to the
33  * application's root window.
34  *
35  * To add a window to the stack, use hildon_program_add_window(). Once
36  * you show the new window, the previous one will be automatically
37  * hidden. When the new window is destroyed, the previous one will
38  * appear again.
39  *
40  * <example>
41  * <programlisting>
42  * void
43  * show_first_window (void)
44  * {
45  *     HildonProgram *program;
46  *     GtkWidget *win1;
47  * <!-- -->
48  *     program = hildon_program_get_instance ();
49  *     win1 = hildon_stackable_window_new ();
50  * <!-- -->
51  *     // ... configure first window
52  * <!-- -->
53  *     hildon_program_add_window (program, HILDON_WINDOW (win1));
54  *     gtk_widget_show (win1);
55  * }
56  * <!-- -->
57  * void
58  * show_second_window (void)
59  * {
60  *     HildonProgram *program;
61  *     GtkWidget *win2;
62  * <!-- -->
63  *     program = hildon_program_get_instance ();
64  *     win2 = hildon_stackable_window_new ();
65  * <!-- -->
66  *     // ... configure second window
67  * <!-- -->
68  *     hildon_program_add_window (program, HILDON_WINDOW (win2));
69  *     gtk_widget_show (win2);
70  * }
71  * </programlisting>
72  * </example>
73  */
74
75 #include                                        <X11/X.h>
76 #include                                        <X11/Xatom.h>
77 #include                                        "hildon-stackable-window.h"
78 #include                                        "hildon-stackable-window-private.h"
79 #include                                        "hildon-program.h"
80 #include                                        "hildon-window-private.h"
81 #include                                        "hildon-program-private.h"
82
83 G_DEFINE_TYPE (HildonStackableWindow, hildon_stackable_window, HILDON_TYPE_WINDOW);
84
85 void G_GNUC_INTERNAL
86 hildon_stackable_window_set_going_home          (HildonStackableWindow *self,
87                                                  gboolean going_home)
88 {
89     HildonStackableWindowPrivate *priv = HILDON_STACKABLE_WINDOW_GET_PRIVATE (self);
90     priv->going_home = going_home;
91 }
92
93 gboolean G_GNUC_INTERNAL
94 hildon_stackable_window_get_going_home          (HildonStackableWindow *self)
95 {
96     HildonStackableWindowPrivate *priv = HILDON_STACKABLE_WINDOW_GET_PRIVATE (self);
97     return priv->going_home;
98 }
99
100 static GSList*
101 get_window_list                                 (GtkWidget *widget)
102 {
103     HildonWindowPrivate *wpriv;
104     HildonProgramPrivate *ppriv;
105     GSList *retval = NULL;
106     g_return_val_if_fail (widget != NULL, NULL);
107
108     wpriv = HILDON_WINDOW_GET_PRIVATE (widget);
109     g_assert (wpriv != NULL);
110
111     if (wpriv->program)
112     {
113         ppriv = HILDON_PROGRAM_GET_PRIVATE (wpriv->program);
114         g_assert (ppriv != NULL);
115         retval = ppriv->windows;
116     }
117
118     return retval;
119 }
120
121 static GtkWidget*
122 get_previous_window_if_last                     (GtkWidget *widget)
123 {
124     GtkWidget *retval = NULL;
125     GSList *iter = get_window_list (widget);
126     GSList *previous = NULL;
127
128     /* Return NULL if the window hasn't been added to the HildonProgram */
129     if (iter == NULL)
130     {
131         return NULL;
132     }
133
134     /* Go to the end of the window list */
135     while (iter->next != NULL)
136     {
137         previous = iter;
138         iter = iter->next;
139     }
140
141     if ((iter->data == widget) && (previous != NULL))
142     {
143         retval = GTK_WIDGET (previous->data);
144     }
145
146     return retval;
147 }
148
149 static void
150 hildon_stackable_window_map                     (GtkWidget *widget)
151 {
152     GtkWidget *previous = get_previous_window_if_last (widget);
153
154     if (GTK_WIDGET_CLASS (hildon_stackable_window_parent_class)->map)
155         GTK_WIDGET_CLASS (hildon_stackable_window_parent_class)->map (widget);
156
157     if (HILDON_IS_STACKABLE_WINDOW (previous) && GTK_WIDGET_VISIBLE (previous))
158         gtk_widget_hide (previous);
159 }
160
161 static void
162 hildon_stackable_window_unmap                   (GtkWidget *widget)
163 {
164     GtkWidget *previous = get_previous_window_if_last (widget);
165
166     if (GTK_WIDGET_CLASS (hildon_stackable_window_parent_class)->unmap)
167         GTK_WIDGET_CLASS (hildon_stackable_window_parent_class)->unmap (widget);
168
169     if (HILDON_IS_STACKABLE_WINDOW (previous) && !GTK_WIDGET_VISIBLE (previous) &&
170         !hildon_stackable_window_get_going_home (HILDON_STACKABLE_WINDOW (widget)))
171     {
172         gtk_widget_show (previous);
173     }
174 }
175
176 static void
177 hildon_stackable_window_unset_program           (HildonWindow *hwin)
178 {
179     GtkWidget *previous = get_previous_window_if_last (GTK_WIDGET (hwin));
180
181     if (HILDON_WINDOW_CLASS (hildon_stackable_window_parent_class)->unset_program)
182         HILDON_WINDOW_CLASS (hildon_stackable_window_parent_class)->unset_program (hwin);
183
184     if (HILDON_IS_STACKABLE_WINDOW (previous) && !GTK_WIDGET_VISIBLE (previous) &&
185         !hildon_stackable_window_get_going_home (HILDON_STACKABLE_WINDOW (hwin)))
186     {
187         gtk_widget_show (previous);
188     }
189 }
190
191 void
192 hildon_stackable_window_set_main_menu           (HildonStackableWindow *self,
193                                                  HildonAppMenu *menu)
194 {
195     HildonStackableWindowPrivate *priv;
196
197     g_return_if_fail (HILDON_IS_STACKABLE_WINDOW (self));
198     g_return_if_fail (!menu || HILDON_IS_APP_MENU (menu));
199     priv = HILDON_STACKABLE_WINDOW_GET_PRIVATE (self);
200
201     /* Remove reference to old menu */
202     if (priv->app_menu)
203         g_object_unref (priv->app_menu);
204
205     /* Add new menu */
206     priv->app_menu = menu;
207     if (priv->app_menu)
208         g_object_ref (priv->app_menu);
209 }
210
211 static gboolean
212 hildon_stackable_window_toggle_menu             (HildonWindow *self,
213                                                  guint button,
214                                                  guint32 time)
215 {
216     HildonStackableWindowPrivate *priv;
217
218     g_return_val_if_fail (HILDON_IS_STACKABLE_WINDOW (self), FALSE);
219     priv = HILDON_STACKABLE_WINDOW_GET_PRIVATE (self);
220     g_assert (priv != NULL);
221
222     if (priv->app_menu)
223     {
224         if (GTK_WIDGET_MAPPED (GTK_WIDGET (priv->app_menu)))
225             gtk_widget_hide (GTK_WIDGET (priv->app_menu));
226         else
227             gtk_widget_show (GTK_WIDGET (priv->app_menu));
228
229         return TRUE;
230     }
231     else if (HILDON_WINDOW_CLASS (hildon_stackable_window_parent_class)->toggle_menu)
232     {
233         return HILDON_WINDOW_CLASS (hildon_stackable_window_parent_class)->toggle_menu (self, button, time);
234     }
235     else
236     {
237         return FALSE;
238     }
239 }
240
241 static void
242 hildon_stackable_window_realize                 (GtkWidget *widget)
243 {
244     GdkDisplay *display;
245     Atom atom;
246
247     GTK_WIDGET_CLASS (hildon_stackable_window_parent_class)->realize (widget);
248
249     /* Set the window type to "_HILDON_WM_WINDOW_TYPE_STACKABLE", to allow the WM to manage
250        it properly.  */
251     display = gdk_drawable_get_display (widget->window);
252     atom = gdk_x11_get_xatom_by_name_for_display (display, "_HILDON_WM_WINDOW_TYPE_STACKABLE");
253     XChangeProperty (GDK_DISPLAY_XDISPLAY (display), GDK_WINDOW_XID (widget->window),
254                      gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE"),
255                      XA_ATOM, 32, PropModeAppend,
256                      (guchar *)&atom, 1);
257 }
258
259 static gboolean
260 hildon_stackable_window_delete_event            (GtkWidget *widget,
261                                                  GdkEventAny *event)
262 {
263     GSList *list = get_window_list (widget);
264     list = g_slist_find (list, widget);
265
266     /* Ignore the delete event if this is not the topmost window */
267     if (list != NULL && list->next != NULL)
268         return TRUE;
269     else if (GTK_WIDGET_CLASS (hildon_stackable_window_parent_class)->delete_event)
270         return GTK_WIDGET_CLASS (hildon_stackable_window_parent_class)->delete_event (widget, event);
271     else
272         return FALSE;
273 }
274
275 static void
276 hildon_stackable_window_finalize                (GObject *object)
277 {
278     HildonStackableWindowPrivate *priv = HILDON_STACKABLE_WINDOW_GET_PRIVATE (object);
279
280     if (priv->app_menu) {
281         gtk_widget_destroy (GTK_WIDGET (priv->app_menu));
282     }
283 }
284
285 static void
286 hildon_stackable_window_class_init              (HildonStackableWindowClass *klass)
287 {
288     GObjectClass      *obj_class    = G_OBJECT_CLASS (klass);
289     GtkWidgetClass    *widget_class = GTK_WIDGET_CLASS (klass);
290     HildonWindowClass *window_class = HILDON_WINDOW_CLASS (klass);
291
292     obj_class->finalize             = hildon_stackable_window_finalize;
293
294     widget_class->map               = hildon_stackable_window_map;
295     widget_class->unmap             = hildon_stackable_window_unmap;
296     widget_class->realize           = hildon_stackable_window_realize;
297     widget_class->delete_event      = hildon_stackable_window_delete_event;
298
299     window_class->unset_program     = hildon_stackable_window_unset_program;
300     window_class->toggle_menu       = hildon_stackable_window_toggle_menu;
301
302     g_type_class_add_private (klass, sizeof (HildonStackableWindowPrivate));
303 }
304
305 static void
306 hildon_stackable_window_init                    (HildonStackableWindow *self)
307 {
308     HildonStackableWindowPrivate *priv = HILDON_STACKABLE_WINDOW_GET_PRIVATE (self);
309
310     priv->going_home = FALSE;
311     priv->app_menu = NULL;
312 }
313
314 /**
315  * hildon_stackable_window_new:
316  *
317  * Creates a new #HildonStackableWindow.
318  *
319  * Return value: A #HildonStackableWindow
320  **/
321 GtkWidget*
322 hildon_stackable_window_new                     (void)
323 {
324     HildonStackableWindow *newwindow = g_object_new (HILDON_TYPE_STACKABLE_WINDOW, NULL);
325
326     return GTK_WIDGET (newwindow);
327 }