[libgd] Add GdStackSwitcher



commit 4c440146ac9714a8f718734b6333cca715244500
Author: William Jon McCann <jmccann redhat com>
Date:   Mon Feb 18 00:57:27 2013 -0500

    Add GdStackSwitcher
    
    https://bugzilla.gnome.org/show_bug.cgi?id=694036

 Makefile.am               |    2 +
 libgd/gd-stack-switcher.c |  334 +++++++++++++++++++++++++++++++++++++++++++++
 libgd/gd-stack-switcher.h |   66 +++++++++
 libgd/gd-stack.c          |   59 +++++++--
 test-stack.c              |   11 ++-
 5 files changed, 462 insertions(+), 10 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 45a2f24..4cfcbb2 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -156,6 +156,8 @@ if LIBGD_STACK
 stack_sources =                                \
        libgd/gd-stack.c                        \
        libgd/gd-stack.h                        \
+       libgd/gd-stack-switcher.c               \
+       libgd/gd-stack-switcher.h               \
        $(NULL)
 
 nodist_libgd_la_SOURCES += $(stack_sources)
diff --git a/libgd/gd-stack-switcher.c b/libgd/gd-stack-switcher.c
new file mode 100644
index 0000000..28c5809
--- /dev/null
+++ b/libgd/gd-stack-switcher.c
@@ -0,0 +1,334 @@
+/*
+ * Copyright (c) 2013 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include "gd-stack-switcher.h"
+
+#define P_(s) s
+
+struct _GdStackSwitcherPrivate
+{
+  GdStack *stack;
+  GHashTable *buttons;
+};
+
+enum {
+  PROP_0,
+  PROP_STACK
+};
+
+G_DEFINE_TYPE (GdStackSwitcher, gd_stack_switcher, GTK_TYPE_BOX);
+
+static void
+gd_stack_switcher_init (GdStackSwitcher *switcher)
+{
+  GtkStyleContext *context;
+  GdStackSwitcherPrivate *priv;
+
+  priv = G_TYPE_INSTANCE_GET_PRIVATE (switcher, GD_TYPE_STACK_SWITCHER, GdStackSwitcherPrivate);
+  switcher->priv = priv;
+
+  priv->stack = NULL;
+  priv->buttons = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+  context = gtk_widget_get_style_context (GTK_WIDGET (switcher));
+  gtk_style_context_add_class (context, GTK_STYLE_CLASS_LINKED);
+
+  gtk_orientable_set_orientation (GTK_ORIENTABLE (switcher), GTK_ORIENTATION_HORIZONTAL);
+}
+
+static void
+clear_switcher (GdStackSwitcher *self)
+{
+  gtk_container_foreach (GTK_CONTAINER (self), (GtkCallback) gtk_widget_destroy, self);
+}
+
+static void
+on_button_clicked (GtkWidget       *widget,
+                   GdStackSwitcher *self)
+{
+  GtkWidget *child;
+
+  child = g_object_get_data (G_OBJECT (widget), "stack-child");
+  gd_stack_set_visible_child (self->priv->stack, child);
+}
+
+static void
+on_title_updated (GtkWidget       *widget,
+                  GParamSpec      *pspec,
+                  GdStackSwitcher *self)
+
+{
+  char *title;
+  GtkWidget *button;
+
+  gtk_container_child_get (GTK_CONTAINER (self->priv->stack), widget,
+                           "title", &title,
+                           NULL);
+  button = g_hash_table_lookup (self->priv->buttons, widget);
+  gtk_button_set_label (GTK_BUTTON (button), title);
+  g_free (title);
+}
+
+static void
+add_child (GdStackSwitcher *self,
+           GtkWidget       *widget)
+{
+  GtkWidget *button;
+  GList *group;
+  char *title = NULL;
+
+  gtk_container_child_get (GTK_CONTAINER (self->priv->stack), widget,
+                           "title", &title,
+                           NULL);
+
+  button = gtk_radio_button_new (NULL);
+  if (title != NULL && title[0] != '\0')
+    gtk_button_set_label (GTK_BUTTON (button), title);
+
+  g_free (title);
+
+  gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (button), FALSE);
+  gtk_widget_set_size_request (button, 100, -1);
+  gtk_widget_set_vexpand (button, TRUE);
+  gtk_style_context_add_class (gtk_widget_get_style_context (button), "raised");
+  gtk_style_context_add_class (gtk_widget_get_style_context (button), "text-button");
+
+  group = gtk_container_get_children (GTK_CONTAINER (self));
+  if (group != NULL)
+    {
+      gtk_radio_button_join_group (GTK_RADIO_BUTTON (button), GTK_RADIO_BUTTON (group->data));
+      g_list_free (group);
+    }
+
+  gtk_container_add (GTK_CONTAINER (self), button);
+  gtk_widget_show (button);
+
+  g_object_set_data (G_OBJECT (button), "stack-child", widget);
+  g_signal_connect (button, "clicked", G_CALLBACK (on_button_clicked), self);
+  g_signal_connect (widget, "child-notify::title", G_CALLBACK (on_title_updated), self);
+
+  g_hash_table_insert (self->priv->buttons, widget, button);
+}
+
+static void
+foreach_stack (GtkWidget       *widget,
+               GdStackSwitcher *self)
+{
+  add_child (self, widget);
+}
+
+static void
+populate_switcher (GdStackSwitcher *self)
+{
+  gtk_container_foreach (GTK_CONTAINER (self->priv->stack), (GtkCallback)foreach_stack, self);
+}
+
+static void
+on_child_changed (GtkWidget       *widget,
+                  GParamSpec      *pspec,
+                  GdStackSwitcher *self)
+{
+  GtkWidget *child;
+  GtkWidget *button;
+
+  child = gd_stack_get_visible_child (GD_STACK (widget));
+  button = g_hash_table_lookup (self->priv->buttons, child);
+  if (button != NULL)
+    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
+}
+
+static void
+on_stack_child_added (GtkContainer    *container,
+                      GtkWidget       *widget,
+                      GdStackSwitcher *self)
+{
+  add_child (self, widget);
+}
+
+static void
+on_stack_child_removed (GtkContainer    *container,
+                        GtkWidget       *widget,
+                        GdStackSwitcher *self)
+{
+  GtkWidget *button;
+
+  button = g_hash_table_lookup (self->priv->buttons, widget);
+  gtk_container_remove (GTK_CONTAINER (self), button);
+  g_hash_table_remove (self->priv->buttons, widget);
+}
+
+static void
+disconnect_stack_signals (GdStackSwitcher *self)
+{
+  g_signal_handlers_disconnect_by_func (self->priv->stack, G_CALLBACK (on_stack_child_added), self);
+  g_signal_handlers_disconnect_by_func (self->priv->stack, G_CALLBACK (on_stack_child_removed), self);
+  g_signal_handlers_disconnect_by_func (self->priv->stack, G_CALLBACK (on_child_changed), self);
+}
+
+static void
+connect_stack_signals (GdStackSwitcher *self)
+{
+  g_signal_connect_after (self->priv->stack, "add", G_CALLBACK (on_stack_child_added), self);
+  g_signal_connect_after (self->priv->stack, "remove", G_CALLBACK (on_stack_child_removed), self);
+  g_signal_connect (self->priv->stack, "notify::visible-child", G_CALLBACK (on_child_changed), self);
+}
+
+/**
+ * gd_stack_switcher_set_stack:
+ * @switcher: a #GdStackSwitcher
+ * @stack: (allow-none): a #GdStack
+ *
+ * Sets the stack to control.
+ *
+ **/
+void
+gd_stack_switcher_set_stack (GdStackSwitcher *switcher,
+                             GdStack         *stack)
+{
+  GdStackSwitcherPrivate *priv;
+
+  g_return_if_fail (GD_IS_STACK_SWITCHER (switcher));
+  if (stack)
+    g_return_if_fail (GD_IS_STACK (stack));
+
+  priv = switcher->priv;
+
+  if (priv->stack == stack)
+    return;
+
+  if (priv->stack)
+    {
+      disconnect_stack_signals (switcher);
+      clear_switcher (switcher);
+      g_clear_object (&priv->stack);
+    }
+
+  if (stack)
+    {
+      priv->stack = g_object_ref (stack);
+      populate_switcher (switcher);
+      connect_stack_signals (switcher);
+    }
+
+  gtk_widget_queue_resize (GTK_WIDGET (switcher));
+
+  g_object_notify (G_OBJECT (switcher), "stack");
+}
+
+/**
+ * gd_stack_switcher_get_stack:
+ * @switcher: a #GdStackSwitcher
+ *
+ * Retrieves the stack. See
+ * gd_stack_switcher_set_stack().
+ *
+ * Return value: (transfer none): the stack, or %NULL if
+ *    none has been set explicitly.
+ **/
+GdStack *
+gd_stack_switcher_get_stack (GdStackSwitcher *switcher)
+{
+  g_return_val_if_fail (GD_IS_STACK_SWITCHER (switcher), NULL);
+
+  return switcher->priv->stack;
+}
+
+static void
+gd_stack_switcher_get_property (GObject      *object,
+                                guint         prop_id,
+                                GValue       *value,
+                                GParamSpec   *pspec)
+{
+  GdStackSwitcher *switcher = GD_STACK_SWITCHER (object);
+  GdStackSwitcherPrivate *priv = switcher->priv;
+
+  switch (prop_id)
+    {
+    case PROP_STACK:
+      g_value_set_object (value, priv->stack);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gd_stack_switcher_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  GdStackSwitcher *switcher = GD_STACK_SWITCHER (object);
+  GdStackSwitcherPrivate *priv = switcher->priv;
+
+  switch (prop_id)
+    {
+    case PROP_STACK:
+      gd_stack_switcher_set_stack (switcher, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gd_stack_switcher_finalize (GObject *object)
+{
+  GdStackSwitcher *self = GD_STACK_SWITCHER (object);
+  GdStackSwitcherPrivate *priv = self->priv;
+
+  g_hash_table_destroy (priv->buttons);
+  g_clear_object (&priv->stack);
+
+  G_OBJECT_CLASS (gd_stack_switcher_parent_class)->finalize (object);
+}
+
+static void
+gd_stack_switcher_class_init (GdStackSwitcherClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class);
+
+  object_class->get_property = gd_stack_switcher_get_property;
+  object_class->set_property = gd_stack_switcher_set_property;
+  object_class->finalize = gd_stack_switcher_finalize;
+
+  g_object_class_install_property (object_class,
+                                   PROP_STACK,
+                                   g_param_spec_object ("stack",
+                                                        P_("Stack"),
+                                                        P_("Stack"),
+                                                        GD_TYPE_STACK,
+                                                        G_PARAM_READWRITE |
+                                                        G_PARAM_CONSTRUCT |
+                                                        G_PARAM_STATIC_STRINGS));
+
+  g_type_class_add_private (object_class, sizeof (GdStackSwitcherPrivate));
+}
+
+GtkWidget *
+gd_stack_switcher_new (void)
+{
+  return GTK_WIDGET (g_object_new (GD_TYPE_STACK_SWITCHER, NULL));
+}
diff --git a/libgd/gd-stack-switcher.h b/libgd/gd-stack-switcher.h
new file mode 100644
index 0000000..2cdbba5
--- /dev/null
+++ b/libgd/gd-stack-switcher.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2013 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef __GD_STACK_SWITCHER_H__
+#define __GD_STACK_SWITCHER_H__
+
+#include <gtk/gtk.h>
+#include <libgd/gd-stack.h>
+
+G_BEGIN_DECLS
+
+#define GD_TYPE_STACK_SWITCHER            (gd_stack_switcher_get_type ())
+#define GD_STACK_SWITCHER(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GD_TYPE_STACK_SWITCHER, 
GdStackSwitcher))
+#define GD_STACK_SWITCHER_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GD_TYPE_STACK_SWITCHER, 
GdStackSwitcherClass))
+#define GD_IS_STACK_SWITCHER(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GD_TYPE_STACK_SWITCHER))
+#define GD_IS_STACK_SWITCHER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GD_TYPE_STACK_SWITCHER))
+#define GD_STACK_SWITCHER_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GD_TYPE_STACK_SWITCHER, 
GdStackSwitcherClass))
+
+typedef struct _GdStackSwitcher              GdStackSwitcher;
+typedef struct _GdStackSwitcherPrivate       GdStackSwitcherPrivate;
+typedef struct _GdStackSwitcherClass         GdStackSwitcherClass;
+
+struct _GdStackSwitcher
+{
+  GtkBox widget;
+
+  /*< private >*/
+  GdStackSwitcherPrivate *priv;
+};
+
+struct _GdStackSwitcherClass
+{
+  GtkBoxClass parent_class;
+
+  /* Padding for future expansion */
+  void (*_gd_reserved1) (void);
+  void (*_gd_reserved2) (void);
+  void (*_gd_reserved3) (void);
+  void (*_gd_reserved4) (void);
+};
+
+GType        gd_stack_switcher_get_type          (void) G_GNUC_CONST;
+GtkWidget   *gd_stack_switcher_new               (void);
+void         gd_stack_switcher_set_stack         (GdStackSwitcher *switcher,
+                                                  GdStack         *stack);
+GdStack     *gd_stack_switcher_get_stack         (GdStackSwitcher *switcher);
+
+G_END_DECLS
+
+#endif /* __GD_STACK_SWITCHER_H__ */
diff --git a/libgd/gd-stack.c b/libgd/gd-stack.c
index 9f3b3e1..faf3504 100644
--- a/libgd/gd-stack.c
+++ b/libgd/gd-stack.c
@@ -42,7 +42,8 @@ enum  {
 enum
 {
   CHILD_PROP_0,
-  CHILD_PROP_NAME
+  CHILD_PROP_NAME,
+  CHILD_PROP_TITLE
 };
 
 typedef struct _GdStackChildInfo GdStackChildInfo;
@@ -50,6 +51,7 @@ typedef struct _GdStackChildInfo GdStackChildInfo;
 struct _GdStackChildInfo {
   GtkWidget *widget;
   char *name;
+  char *title;
 };
 
 struct _GdStackPrivate {
@@ -273,6 +275,13 @@ gd_stack_class_init (GdStackClass * klass)
                          NULL,
                          GTK_PARAM_READWRITE));
 
+  gtk_container_class_install_child_property (container_class, CHILD_PROP_TITLE,
+    g_param_spec_string ("title",
+                         "Title",
+                         "The title of the child page",
+                         NULL,
+                         GTK_PARAM_READWRITE));
+
   g_type_class_add_private (klass, sizeof (GdStackPrivate));
 }
 
@@ -324,6 +333,10 @@ gd_stack_get_child_property (GtkContainer *container,
       g_value_set_string (value, info->name);
       break;
 
+    case CHILD_PROP_TITLE:
+      g_value_set_string (value, info->title);
+      break;
+
     default:
       GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
       break;
@@ -351,13 +364,22 @@ gd_stack_set_child_property (GtkContainer *container,
   switch (property_id)
     {
     case CHILD_PROP_NAME:
+      g_free (info->name);
       info->name = g_value_dup_string (value);
 
+      gtk_container_child_notify (container, child, "name");
+
       if (priv->visible_child == info)
         g_object_notify (G_OBJECT (stack), "visible-child-name");
 
       break;
 
+    case CHILD_PROP_TITLE:
+      g_free (info->title);
+      info->title = g_value_dup_string (value);
+      gtk_container_child_notify (container, child, "title");
+      break;
+
     default:
       GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
       break;
@@ -564,10 +586,34 @@ stack_child_visibility_notify_cb (GObject *obj,
 }
 
 void
+gd_stack_add_titled (GdStack    *stack,
+                     GtkWidget  *child,
+                     const char *name,
+                     const char *title)
+{
+  gtk_container_add_with_properties (GTK_CONTAINER (stack),
+                                     child,
+                                     "name", name,
+                                     "title", title,
+                                     NULL);
+}
+
+void
 gd_stack_add_named (GdStack    *stack,
                     GtkWidget  *child,
                     const char *name)
 {
+  gtk_container_add_with_properties (GTK_CONTAINER (stack),
+                                     child,
+                                     "name", name,
+                                     NULL);
+}
+
+static void
+gd_stack_add (GtkContainer *container,
+             GtkWidget *child)
+{
+  GdStack *stack = GD_STACK (container);
   GdStackPrivate *priv = stack->priv;
   GdStackChildInfo *child_info;
 
@@ -575,7 +621,8 @@ gd_stack_add_named (GdStack    *stack,
 
   child_info = g_slice_new (GdStackChildInfo);
   child_info->widget = child;
-  child_info->name = g_strdup (name);
+  child_info->name = NULL;
+  child_info->title = NULL;
 
   priv->children = g_list_append (priv->children, child_info);
 
@@ -595,13 +642,6 @@ gd_stack_add_named (GdStack    *stack,
 }
 
 static void
-gd_stack_add (GtkContainer *container,
-             GtkWidget *child)
-{
-  gd_stack_add_named (GD_STACK (container), child, NULL);
-}
-
-static void
 gd_stack_remove (GtkContainer *container,
                 GtkWidget    *child)
 {
@@ -632,6 +672,7 @@ gd_stack_remove (GtkContainer *container,
 
   gtk_widget_unparent (child);
 
+  g_free (child_info->title);
   g_slice_free (GdStackChildInfo, child_info);
 
   if (priv->homogeneous && was_visible)
diff --git a/test-stack.c b/test-stack.c
index e6ecac3..9960286 100644
--- a/test-stack.c
+++ b/test-stack.c
@@ -1,7 +1,9 @@
 #include <gtk/gtk.h>
 #include <libgd/gd-stack.h>
+#include <libgd/gd-stack-switcher.h>
 
 GtkWidget *stack;
+GtkWidget *switcher;
 
 static void
 set_visible_child (GtkWidget *button, gpointer data)
@@ -38,23 +40,30 @@ main (gint argc,
   box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
   gtk_container_add (GTK_CONTAINER (window), box);
 
+  switcher = gd_stack_switcher_new ();
+  gtk_box_pack_start (GTK_BOX (box), switcher, FALSE, FALSE, 0);
+
   stack = gd_stack_new ();
   gtk_widget_set_halign (stack, GTK_ALIGN_START);
   gtk_container_add (GTK_CONTAINER (box), stack);
 
+  gd_stack_switcher_set_stack (GD_STACK_SWITCHER (switcher), GD_STACK (stack));
+
   b1 = gtk_button_new_with_label ("Blah");
   gtk_container_add_with_properties (GTK_CONTAINER (stack), b1,
                                     "name", "1",
+                                    "title", "1",
                                     NULL);
 
   b2 = gtk_button_new_with_label ("Gazoooooooooooooooonk");
   gtk_container_add (GTK_CONTAINER (stack), b2);
   gtk_container_child_set (GTK_CONTAINER (stack), b2,
                           "name", "2",
+                          "title", "2",
                           NULL);
 
   b3 = gtk_button_new_with_label ("Foo\nBar");
-  gd_stack_add_named (GD_STACK (stack), b3, "3");
+  gd_stack_add_titled (GD_STACK (stack), b3, "3", "3");
 
   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
   gtk_container_add (GTK_CONTAINER (box), hbox);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]