aaf840a05c5f7e8e105877e35eb6be63237f9ce4
[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
36 #include                                        <X11/X.h>
37 #include                                        <X11/Xatom.h>
38 #include                                        "hildon-stackable-window.h"
39 #include                                        "hildon-program.h"
40 #include                                        "hildon-window-private.h"
41 #include                                        "hildon-program-private.h"
42
43 typedef struct                                  _HildonStackableWindowPrivate HildonStackableWindowPrivate;
44
45 struct                                          _HildonStackableWindowPrivate
46 {
47     gboolean going_home;
48 };
49
50 #define                                         HILDON_STACKABLE_WINDOW_GET_PRIVATE(obj) \
51                                                 (G_TYPE_INSTANCE_GET_PRIVATE ((obj),\
52                                                 HILDON_TYPE_STACKABLE_WINDOW, HildonStackableWindowPrivate))
53
54 G_DEFINE_TYPE (HildonStackableWindow, hildon_stackable_window, HILDON_TYPE_WINDOW);
55
56 static void
57 hildon_stackable_window_set_going_home          (HildonStackableWindow *self,
58                                                  gboolean going_home)
59 {
60     HildonStackableWindowPrivate *priv = HILDON_STACKABLE_WINDOW_GET_PRIVATE (self);
61     priv->going_home = going_home;
62 }
63
64 static gboolean
65 hildon_stackable_window_get_going_home          (HildonStackableWindow *self)
66 {
67     HildonStackableWindowPrivate *priv = HILDON_STACKABLE_WINDOW_GET_PRIVATE (self);
68     return priv->going_home;
69 }
70
71 static GSList*
72 get_window_list                                 (GtkWidget *widget)
73 {
74     HildonWindowPrivate *wpriv;
75     HildonProgramPrivate *ppriv;
76     GSList *retval = NULL;
77     g_return_val_if_fail (widget != NULL, NULL);
78
79     wpriv = HILDON_WINDOW_GET_PRIVATE (widget);
80     g_assert (wpriv != NULL);
81
82     if (wpriv->program)
83     {
84         ppriv = HILDON_PROGRAM_GET_PRIVATE (wpriv->program);
85         g_assert (ppriv != NULL);
86         retval = ppriv->windows;
87     }
88
89     return retval;
90 }
91
92 static GtkWidget*
93 get_last_window                                 (GtkWidget *widget)
94 {
95     GtkWidget *retval = NULL;
96     GSList *windows = get_window_list (widget);
97     GSList *last = NULL;
98
99     g_return_val_if_fail (windows != NULL, NULL);
100
101     /* Go to the end of the window list */
102     while (windows->next != NULL)
103     {
104         last = windows;
105         windows = windows->next;
106     }
107
108     if ((windows->data == widget) && (last != NULL))
109     {
110         retval = GTK_WIDGET (last->data);
111     }
112
113     return retval;
114 }
115
116 static void
117 hildon_stackable_window_map                     (GtkWidget *widget)
118 {
119     GtkWidget *lastwin;
120
121     if (GTK_WIDGET_CLASS (hildon_stackable_window_parent_class)->map)
122         GTK_WIDGET_CLASS (hildon_stackable_window_parent_class)->map (widget);
123
124     lastwin = get_last_window (widget);
125
126     if (HILDON_IS_STACKABLE_WINDOW (lastwin) && GTK_WIDGET_VISIBLE (lastwin))
127         gtk_widget_hide (lastwin);
128 }
129
130 static void
131 hildon_stackable_window_unmap                   (GtkWidget *widget)
132 {
133     GtkWidget *lastwin;
134
135     if (GTK_WIDGET_CLASS (hildon_stackable_window_parent_class)->unmap)
136         GTK_WIDGET_CLASS (hildon_stackable_window_parent_class)->unmap (widget);
137
138     lastwin = get_last_window (widget);
139
140     if (HILDON_IS_STACKABLE_WINDOW (lastwin) && !GTK_WIDGET_VISIBLE (lastwin) &&
141         !hildon_stackable_window_get_going_home (HILDON_STACKABLE_WINDOW (widget)))
142     {
143         gtk_widget_show (lastwin);
144     }
145 }
146
147 static void
148 hildon_stackable_window_unset_program           (HildonWindow *hwin)
149 {
150     GSList *windows = get_window_list (GTK_WIDGET (hwin));
151     gint l = g_slist_length (windows);
152     GtkWidget *nextwin = GTK_WIDGET (g_slist_nth_data (windows, l - 2));
153
154     if (HILDON_WINDOW_CLASS (hildon_stackable_window_parent_class)->unset_program)
155         HILDON_WINDOW_CLASS (hildon_stackable_window_parent_class)->unset_program (hwin);
156
157     if (HILDON_IS_STACKABLE_WINDOW (nextwin) && !GTK_WIDGET_VISIBLE (nextwin) &&
158         !hildon_stackable_window_get_going_home (HILDON_STACKABLE_WINDOW (hwin)))
159     {
160         gtk_widget_show (nextwin);
161     }
162 }
163
164 static void
165 hildon_stackable_window_realize                 (GtkWidget *widget)
166 {
167     GdkDisplay *display;
168     Atom atom;
169
170     GTK_WIDGET_CLASS (hildon_stackable_window_parent_class)->realize (widget);
171
172     /* Set the window type to "_HILDON_WM_WINDOW_TYPE_STACKABLE", to allow the WM to manage
173        it properly.  */
174     display = gdk_drawable_get_display (widget->window);
175     atom = gdk_x11_get_xatom_by_name_for_display (display, "_HILDON_WM_WINDOW_TYPE_STACKABLE");
176     XChangeProperty (GDK_DISPLAY_XDISPLAY (display), GDK_WINDOW_XID (widget->window),
177                      gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_WINDOW_TYPE"),
178                      XA_ATOM, 32, PropModeAppend,
179                      (guchar *)&atom, 1);
180 }
181
182 static gboolean
183 hildon_stackable_window_delete_event            (GtkWidget *widget,
184                                                  GdkEventAny *event)
185 {
186     GSList *list = get_window_list (widget);
187     list = g_slist_find (list, widget);
188
189     /* Ignore the delete event if this is not the topmost window */
190     if (list != NULL && list->next != NULL)
191         return TRUE;
192     else if (GTK_WIDGET_CLASS (hildon_stackable_window_parent_class)->delete_event)
193         return GTK_WIDGET_CLASS (hildon_stackable_window_parent_class)->delete_event (widget, event);
194     else
195         return FALSE;
196 }
197
198 static void
199 hildon_stackable_window_class_init              (HildonStackableWindowClass *klass)
200 {
201     GtkWidgetClass    *widget_class = GTK_WIDGET_CLASS (klass);
202     HildonWindowClass *window_class = HILDON_WINDOW_CLASS (klass);
203
204     widget_class->map               = hildon_stackable_window_map;
205     widget_class->unmap             = hildon_stackable_window_unmap;
206     widget_class->realize           = hildon_stackable_window_realize;
207     widget_class->delete_event      = hildon_stackable_window_delete_event;
208
209     window_class->unset_program     = hildon_stackable_window_unset_program;
210
211     g_type_class_add_private (klass, sizeof (HildonStackableWindowPrivate));
212 }
213
214 static void
215 hildon_stackable_window_init                    (HildonStackableWindow *self)
216 {
217     hildon_stackable_window_set_going_home (self, FALSE);
218 }
219
220 /**
221  * hildon_stackable_window_new:
222  *
223  * Creates a new #HildonStackableWindow.
224  *
225  * Return value: A #HildonStackableWindow
226  **/
227 GtkWidget*
228 hildon_stackable_window_new                     (void)
229 {
230     HildonStackableWindow *newwindow = g_object_new (HILDON_TYPE_STACKABLE_WINDOW, NULL);
231
232     return GTK_WIDGET (newwindow);
233 }
234
235 /**
236  * hildon_stackable_window_go_to_root_window:
237  * @self: A #HildonStackableWindow
238  *
239  * Will close all the stackable windows in the @HildonProgram but the
240  * first one (the root window) by sending them a delete event. If any
241  * of the windows refuses to close (by handling it) no further events
242  * will be sent.
243  */
244 void
245 hildon_stackable_window_go_to_root_window       (HildonStackableWindow *self)
246 {
247     GSList *windows, *iter;
248     gboolean windows_left;
249
250     g_return_if_fail (HILDON_IS_STACKABLE_WINDOW (self));
251
252     /* List of windows in reverse order (starting from the topmost one) */
253     windows = g_slist_reverse (g_slist_copy (get_window_list (GTK_WIDGET (self))));
254     iter = windows;
255
256     /* Destroy all the windows but the last one (which is the root
257      * window, as the list is reversed) */
258     windows_left = (iter != NULL && iter->next != NULL);
259     while (windows_left)
260     {
261         if (HILDON_IS_STACKABLE_WINDOW (iter->data))
262         {
263             GdkEvent *event;
264             HildonStackableWindow *win;
265
266             /* Mark the window as "going home" */
267             win = HILDON_STACKABLE_WINDOW (iter->data);
268             hildon_stackable_window_set_going_home (win, TRUE);
269
270             /* Set win pointer to NULL if the window is destroyed */
271             g_object_add_weak_pointer (G_OBJECT (win), (gpointer) &win);
272
273             /* Send a delete event */
274             event = gdk_event_new (GDK_DELETE);
275             event->any.window = g_object_ref (GTK_WIDGET (win)->window);
276             gtk_main_do_event (event);
277             gdk_event_free (event);
278
279             /* Continue sending delete events if the window has been destroyed */
280             if (win == NULL)
281             {
282                 iter = iter->next;
283                 windows_left = (iter != NULL && iter->next != NULL);
284             }
285             else
286             {
287                 g_object_remove_weak_pointer (G_OBJECT (win), (gpointer) &win);
288                 hildon_stackable_window_set_going_home (win, FALSE);
289                 windows_left = FALSE;
290             }
291         }
292         else
293         {
294             g_warning ("Window list contains a non-stackable window");
295             windows_left = FALSE;
296         }
297     }
298
299     /* Show the last window that hasn't been destroyed */
300     if (iter != NULL && GTK_IS_WIDGET (iter->data))
301     {
302         gtk_widget_show (GTK_WIDGET (iter->data));
303     }
304
305     g_slist_free (windows);
306 }