2009-03-24 Alberto Garcia <agarcia@igalia.com>
[hildon] / src / hildon-window-stack.c
index b7ec0f0..a0b399e 100644 (file)
@@ -61,6 +61,7 @@ struct                                          _HildonWindowStackPrivate
 {
     GList *list;
     GtkWindowGroup *group;
+    GdkWindow *leader; /* X Window group hint for all windows in a group */
 };
 
 #define                                         HILDON_WINDOW_STACK_GET_PRIVATE(obj) \
@@ -69,6 +70,34 @@ struct                                          _HildonWindowStackPrivate
 
 G_DEFINE_TYPE (HildonWindowStack, hildon_window_stack, G_TYPE_OBJECT);
 
+enum {
+    PROP_GROUP = 1,
+};
+
+static void
+hildon_window_stack_set_window_group             (HildonWindowStack *stack,
+                                                  GtkWindowGroup    *group)
+{
+    g_return_if_fail (HILDON_IS_WINDOW_STACK (stack));
+    g_return_if_fail (!group || GTK_IS_WINDOW_GROUP (group));
+
+    /* The window group is only to be set once during construction */
+    g_return_if_fail (stack->priv->group == NULL);
+
+    if (!group)
+        group = gtk_window_group_new ();
+
+    stack->priv->group = group;
+}
+
+static GtkWindowGroup *
+hildon_window_stack_get_window_group             (HildonWindowStack *stack)
+{
+    g_return_val_if_fail (HILDON_IS_WINDOW_STACK (stack), NULL);
+
+    return stack->priv->group;
+}
+
 /**
  * hildon_window_stack_get_default:
  *
@@ -76,14 +105,17 @@ G_DEFINE_TYPE (HildonWindowStack, hildon_window_stack, G_TYPE_OBJECT);
  * doesn't need to be created by the application.
  *
  * Return value: the default #HildonWindowStack
+ * 
+ * Since: 2.2
  **/
 HildonWindowStack *
 hildon_window_stack_get_default                 (void)
 {
     static HildonWindowStack *stack = NULL;
     if (G_UNLIKELY (stack == NULL)) {
-        stack = g_object_new (HILDON_TYPE_WINDOW_STACK, NULL);
-        stack->priv->group = gtk_window_get_group (NULL);
+        stack = g_object_new (HILDON_TYPE_WINDOW_STACK,
+                              "window-group", gtk_window_get_group (NULL),
+                              NULL);
     }
     return stack;
 }
@@ -94,12 +126,13 @@ hildon_window_stack_get_default                 (void)
  * Creates a new #HildonWindowStack. The stack is initially empty.
  *
  * Return value: a new #HildonWindowStack
+ *
+ * Since: 2.2
  **/
 HildonWindowStack *
 hildon_window_stack_new                         (void)
 {
     HildonWindowStack *stack = g_object_new (HILDON_TYPE_WINDOW_STACK, NULL);
-    stack->priv->group = gtk_window_group_new ();
     return stack;
 }
 
@@ -110,6 +143,8 @@ hildon_window_stack_new                         (void)
  * Returns the number of windows in @stack
  *
  * Return value: Number of windows in @stack
+ *
+ * Since: 2.2
  **/
 gint
 hildon_window_stack_size                        (HildonWindowStack *stack)
@@ -119,6 +154,44 @@ hildon_window_stack_size                        (HildonWindowStack *stack)
     return g_list_length (stack->priv->list);
 }
 
+static GdkWindow *
+hildon_window_stack_get_leader_window           (HildonWindowStack *stack,
+                                                 GtkWidget         *win)
+{
+    /* Create the X Window group (leader) if we haven't. */
+    if (!stack->priv->leader) {
+        if (stack == hildon_window_stack_get_default ()) {
+            GdkDisplay *dpy;
+
+            /* We're the default stack, use the default group. */
+            dpy = gtk_widget_get_display (win);
+            stack->priv->leader = gdk_display_get_default_group (dpy);
+        } else {
+            static GdkWindowAttr attr = {
+                .window_type = GDK_WINDOW_TOPLEVEL,
+                .x = 10, .y = 10, .width = 10, .height = 10,
+                .wclass = GDK_INPUT_OUTPUT, .event_mask = 0,
+            };
+            GdkWindow *root;
+
+            /* Create a new X Window group. */
+            root = gtk_widget_get_root_window (win);
+            stack->priv->leader = gdk_window_new (root, &attr, GDK_WA_X | GDK_WA_Y);
+        }
+    }
+
+    return stack->priv->leader;
+}
+
+/* Set the X Window group of a window when it is realized. */
+static void
+hildon_window_stack_window_realized             (GtkWidget         *win,
+                                                 HildonWindowStack *stack)
+{
+    GdkWindow *leader = hildon_window_stack_get_leader_window (stack, win);
+    gdk_window_set_group (win->window, leader);
+}
+
 /* Remove a window from its stack, no matter its position */
 void G_GNUC_INTERNAL
 hildon_window_stack_remove                      (HildonStackableWindow *win)
@@ -127,9 +200,27 @@ hildon_window_stack_remove                      (HildonStackableWindow *win)
 
     /* If the window is stacked */
     if (stack) {
+        GList *pos;
+
         hildon_stackable_window_set_stack (win, NULL, -1);
-        stack->priv->list = g_list_remove (stack->priv->list, win);
         gtk_window_set_transient_for (GTK_WINDOW (win), NULL);
+        if (GTK_WIDGET (win)->window) {
+            gdk_window_set_group (GTK_WIDGET (win)->window, NULL);
+        }
+
+        /* If the window removed is in the middle of the stack, update
+         * transiency of other windows */
+        pos = g_list_find (stack->priv->list, win);
+        g_assert (pos != NULL);
+        if (pos->prev) {
+            GtkWindow *upper = GTK_WINDOW (pos->prev->data);
+            GtkWindow *lower = pos->next ? GTK_WINDOW (pos->next->data) : NULL;
+            gtk_window_set_transient_for (upper, lower);
+        }
+
+        stack->priv->list = g_list_remove (stack->priv->list, win);
+
+        g_signal_handlers_disconnect_by_func (win, hildon_window_stack_window_realized, stack);
     }
 }
 
@@ -141,6 +232,8 @@ hildon_window_stack_remove                      (HildonStackableWindow *win)
  *
  * Return value: the window on top of the stack, or %NULL if the stack
  * is empty.
+ *
+ * Since: 2.2
  **/
 GtkWidget *
 hildon_window_stack_peek                        (HildonWindowStack *stack)
@@ -156,7 +249,11 @@ hildon_window_stack_peek                        (HildonWindowStack *stack)
     return win;
 }
 
-static gboolean
+/* This function does everything to push a window to the stack _but_
+ * actually calling gtk_widget_show().
+ * It's up to each specific push function to decide the order in which
+ * to show windows. */
+gboolean G_GNUC_INTERNAL
 _hildon_window_stack_do_push                    (HildonWindowStack     *stack,
                                                  HildonStackableWindow *win)
 {
@@ -181,6 +278,11 @@ _hildon_window_stack_do_push                    (HildonWindowStack     *stack,
             gtk_window_group_add_window (stack->priv->group, GTK_WINDOW (win));
         }
 
+        /* Set win's group after it's been realized. */
+        g_signal_connect (win, "realize",
+                          G_CALLBACK (hildon_window_stack_window_realized),
+                          stack);
+
         return TRUE;
     } else {
         g_warning ("Trying to push a window that is already on a stack");
@@ -206,6 +308,8 @@ _hildon_window_stack_do_pop                     (HildonWindowStack *stack)
  *
  * Adds @win to the top of @stack, and shows it. The window must not
  * be already stacked.
+ *
+ * Since: 2.2
  **/
 void
 hildon_window_stack_push_1                      (HildonWindowStack     *stack,
@@ -224,6 +328,8 @@ hildon_window_stack_push_1                      (HildonWindowStack     *stack,
  *
  * Return value: the window on top of the stack, or %NULL if the stack
  * is empty.
+ *
+ * Since: 2.2
  **/
 GtkWidget *
 hildon_window_stack_pop_1                       (HildonWindowStack *stack)
@@ -243,6 +349,8 @@ hildon_window_stack_pop_1                       (HildonWindowStack *stack)
  * them. Everything is done in a single transition, so the user will
  * only see the last window in @list during this operation. None of
  * the windows must be already stacked.
+ *
+ * Since: 2.2
  **/
 void
 hildon_window_stack_push_list                   (HildonWindowStack *stack,
@@ -280,18 +388,19 @@ hildon_window_stack_push_list                   (HildonWindowStack *stack,
  * Pushes all windows to the top of @stack, and shows them. Everything
  * is done in a single transition, so the user will only see the last
  * window. None of the windows must be already stacked.
+ *
+ * Since: 2.2
  **/
 void
 hildon_window_stack_push                        (HildonWindowStack     *stack,
                                                  HildonStackableWindow *win1,
                                                  ...)
 {
-    HildonStackableWindow *win;
+    HildonStackableWindow *win = win1;
     GList *list = NULL;
     va_list args;
 
     va_start (args, win1);
-    win = va_arg (args, HildonStackableWindow *);
 
     while (win != NULL) {
         list = g_list_prepend (list, win);
@@ -300,6 +409,8 @@ hildon_window_stack_push                        (HildonWindowStack     *stack,
 
     va_end (args);
 
+    list = g_list_reverse (list);
+
     hildon_window_stack_push_list (stack, list);
     g_list_free (list);
 }
@@ -317,6 +428,8 @@ hildon_window_stack_push                        (HildonWindowStack     *stack,
  * If @popped_windows is not %NULL, the list of popped windows is
  * stored there (ordered bottom-up). That list must be freed by the
  * user.
+ *
+ * Since: 2.2
  **/
 void
 hildon_window_stack_pop                         (HildonWindowStack  *stack,
@@ -361,6 +474,8 @@ hildon_window_stack_pop                         (HildonWindowStack  *stack,
  * If @popped_windows is not %NULL, the list of popped windows is
  * stored there (ordered bottom-up). That list must be freed by the
  * user.
+ *
+ * Since: 2.2
  **/
 void
 hildon_window_stack_pop_and_push_list           (HildonWindowStack  *stack,
@@ -424,6 +539,8 @@ hildon_window_stack_pop_and_push_list           (HildonWindowStack  *stack,
  * If @popped_windows is not %NULL, the list of popped windows is
  * stored there (ordered bottom-up). That list must be freed by the
  * user.
+ *
+ * Since: 2.2
  **/
 void
 hildon_window_stack_pop_and_push                (HildonWindowStack      *stack,
@@ -432,12 +549,11 @@ hildon_window_stack_pop_and_push                (HildonWindowStack      *stack,
                                                  HildonStackableWindow  *win1,
                                                  ...)
 {
-    HildonStackableWindow *win;
+    HildonStackableWindow *win = win1;
     GList *list = NULL;
     va_list args;
 
     va_start (args, win1);
-    win = va_arg (args, HildonStackableWindow *);
 
     while (win != NULL) {
         list = g_list_prepend (list, win);
@@ -446,6 +562,8 @@ hildon_window_stack_pop_and_push                (HildonWindowStack      *stack,
 
     va_end (args);
 
+    list = g_list_reverse (list);
+
     hildon_window_stack_pop_and_push_list (stack, nwindows, popped_windows, list);
     g_list_free (list);
 }
@@ -461,16 +579,71 @@ hildon_window_stack_finalize (GObject *object)
     if (stack->priv->group)
         g_object_unref (stack->priv->group);
 
+    /* Since the default group stack shouldn't be finalized,
+     * it's safe to destroy the X Window group we created. */
+    if (stack->priv->leader)
+        gdk_window_destroy (stack->priv->leader);
+
     G_OBJECT_CLASS (hildon_window_stack_parent_class)->finalize (object);
 }
 
 static void
+hildon_window_stack_set_property                (GObject      *object,
+                                                 guint         prop_id,
+                                                 const GValue *value,
+                                                 GParamSpec   *pspec)
+{
+    HildonWindowStack *stack = HILDON_WINDOW_STACK (object);
+
+    switch (prop_id)
+    {
+    case PROP_GROUP:
+        hildon_window_stack_set_window_group (stack, g_value_get_object (value));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+hildon_window_stack_get_property                (GObject    *object,
+                                                 guint       prop_id,
+                                                 GValue     *value,
+                                                 GParamSpec *pspec)
+{
+    HildonWindowStack *stack = HILDON_WINDOW_STACK (object);
+
+    switch (prop_id)
+    {
+    case PROP_GROUP:
+        g_value_set_object (value, hildon_window_stack_get_window_group (stack));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
 hildon_window_stack_class_init (HildonWindowStackClass *klass)
 {
     GObjectClass *gobject_class = (GObjectClass *)klass;
 
+    gobject_class->set_property = hildon_window_stack_set_property;
+    gobject_class->get_property = hildon_window_stack_get_property;
     gobject_class->finalize = hildon_window_stack_finalize;
 
+    g_object_class_install_property (
+        gobject_class,
+        PROP_GROUP,
+        g_param_spec_object (
+            "window-group",
+            "GtkWindowGroup for this stack",
+            "GtkWindowGroup that all windows on this stack belong to",
+            GTK_TYPE_WINDOW_GROUP,
+            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
     g_type_class_add_private (klass, sizeof (HildonWindowStackPrivate));
 }