summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Clasen <mclasen@redhat.com>2013-04-21 07:51:14 -0400
committerMatthias Clasen <mclasen@redhat.com>2013-04-21 21:51:24 -0400
commit2e39c4bab8c90f8b36496cb034958738098c7099 (patch)
tree327ed53d20643264def64bdf71ebbdbf9b824cbe
parent57c4bcb369891c9035644ec4c84f30ebc241cc9f (diff)
downloadgtk+-2e39c4bab8c90f8b36496cb034958738098c7099.tar.gz
Add GtkStack
Add separate GtkStack and GtkStackSwitcher widgets that are an alternative to GtkNotebook. Additionally, GtkStack supports animated transitions when changing pages. These widgets were initially developed in libgd.
-rw-r--r--gtk/Makefile.am4
-rw-r--r--gtk/gtk.h2
-rw-r--r--gtk/gtkstack.c1417
-rw-r--r--gtk/gtkstack.h85
-rw-r--r--gtk/gtkstackswitcher.c413
-rw-r--r--gtk/gtkstackswitcher.h65
-rw-r--r--tests/Makefile.am6
-rw-r--r--tests/teststack.c249
8 files changed, 2240 insertions, 1 deletions
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 9df87a0675..3718d477cc 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -331,6 +331,8 @@ gtk_public_h_sources = \
gtksocket.h \
gtkspinbutton.h \
gtkspinner.h \
+ gtkstack.h \
+ gtkstackswitcher.h \
gtkstatusbar.h \
gtkstatusicon.h \
gtkstock.h \
@@ -830,6 +832,8 @@ gtk_base_c_sources = \
gtkshow.c \
gtkspinbutton.c \
gtkspinner.c \
+ gtkstack.c \
+ gtkstackswitcher.c \
gtkstatusbar.c \
gtkstatusicon.c \
gtkstock.c \
diff --git a/gtk/gtk.h b/gtk/gtk.h
index 61d4107c0c..fd8b7d32f8 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -179,6 +179,8 @@
#include <gtk/gtksizerequest.h>
#include <gtk/gtkspinbutton.h>
#include <gtk/gtkspinner.h>
+#include <gtk/gtkstack.h>
+#include <gtk/gtkstackswitcher.h>
#include <gtk/gtkstatusbar.h>
#include <gtk/gtkstatusicon.h>
#include <gtk/gtkstock.h>
diff --git a/gtk/gtkstack.c b/gtk/gtkstack.c
new file mode 100644
index 0000000000..14a4981b18
--- /dev/null
+++ b/gtk/gtkstack.c
@@ -0,0 +1,1417 @@
+/*
+ * 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
+ *
+ * Author: Alexander Larsson <alexl@redhat.com>
+ *
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+#include "gtkstack.h"
+#include "gtkprivate.h"
+#include "gtkintl.h"
+#include <math.h>
+#include <string.h>
+
+/* TODO:
+ * more transition types (slides)
+ * filter events out events to the last_child widget during transitions
+ */
+
+enum {
+ PROP_0,
+ PROP_HOMOGENEOUS,
+ PROP_VISIBLE_CHILD,
+ PROP_VISIBLE_CHILD_NAME,
+ PROP_TRANSITION_DURATION,
+ PROP_TRANSITION_TYPE
+};
+
+enum
+{
+ CHILD_PROP_0,
+ CHILD_PROP_NAME,
+ CHILD_PROP_TITLE,
+ CHILD_PROP_ICON_NAME,
+ CHILD_PROP_POSITION
+};
+
+typedef struct _GtkStackChildInfo GtkStackChildInfo;
+
+struct _GtkStackChildInfo {
+ GtkWidget *widget;
+ gchar *name;
+ gchar *title;
+ gchar *icon_name;
+};
+
+struct _GtkStackPrivate {
+ GList *children;
+
+ GdkWindow* bin_window;
+ GdkWindow* view_window;
+
+ GtkStackChildInfo *visible_child;
+
+ gboolean homogeneous;
+
+ GtkStackTransitionType transition_type;
+ gint transition_duration;
+
+ GtkStackChildInfo *last_visible_child;
+ cairo_surface_t *last_visible_surface;
+ GtkAllocation last_visible_surface_allocation;
+ gdouble transition_pos;
+
+ guint tick_id;
+ gint64 start_time;
+ gint64 end_time;
+};
+
+static void gtk_stack_add (GtkContainer *widget,
+ GtkWidget *child);
+static void gtk_stack_remove (GtkContainer *widget,
+ GtkWidget *child);
+static void gtk_stack_forall (GtkContainer *container,
+ gboolean include_internals,
+ GtkCallback callback,
+ gpointer callback_data);
+static void gtk_stack_compute_expand (GtkWidget *widget,
+ gboolean *hexpand,
+ gboolean *vexpand);
+static void gtk_stack_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation);
+static gboolean gtk_stack_draw (GtkWidget *widget,
+ cairo_t *cr);
+static void gtk_stack_get_preferred_height (GtkWidget *widget,
+ gint *minimum_height,
+ gint *natural_height);
+static void gtk_stack_get_preferred_height_for_width (GtkWidget *widget,
+ gint width,
+ gint *minimum_height,
+ gint *natural_height);
+static void gtk_stack_get_preferred_width (GtkWidget *widget,
+ gint *minimum_width,
+ gint *natural_width);
+static void gtk_stack_get_preferred_width_for_height (GtkWidget *widget,
+ gint height,
+ gint *minimum_width,
+ gint *natural_width);
+static void gtk_stack_finalize (GObject *obj);
+static void gtk_stack_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gtk_stack_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gtk_stack_get_child_property (GtkContainer *container,
+ GtkWidget *child,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gtk_stack_set_child_property (GtkContainer *container,
+ GtkWidget *child,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gtk_stack_unschedule_ticks (GtkStack *stack);
+static gint get_bin_window_x (GtkStack *stack,
+ GtkAllocation *allocation);
+
+G_DEFINE_TYPE(GtkStack, gtk_stack, GTK_TYPE_CONTAINER);
+
+static void
+gtk_stack_init (GtkStack *stack)
+{
+ stack->priv = G_TYPE_INSTANCE_GET_PRIVATE (stack, GTK_TYPE_STACK, GtkStackPrivate);
+
+ gtk_widget_set_has_window ((GtkWidget*) stack, TRUE);
+ gtk_widget_set_redraw_on_allocate ((GtkWidget*) stack, TRUE);
+}
+
+static void
+gtk_stack_finalize (GObject *obj)
+{
+ GtkStack *stack = GTK_STACK (obj);
+ GtkStackPrivate *priv = stack->priv;
+
+ gtk_stack_unschedule_ticks (stack);
+
+ if (priv->last_visible_surface != NULL)
+ cairo_surface_destroy (priv->last_visible_surface);
+
+ G_OBJECT_CLASS (gtk_stack_parent_class)->finalize (obj);
+}
+
+static void
+gtk_stack_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkStack *stack = GTK_STACK (object);
+ GtkStackPrivate *priv = stack->priv;
+
+ switch (property_id)
+ {
+ case PROP_HOMOGENEOUS:
+ g_value_set_boolean (value, priv->homogeneous);
+ break;
+ case PROP_VISIBLE_CHILD:
+ g_value_set_object (value, priv->visible_child);
+ break;
+ case PROP_VISIBLE_CHILD_NAME:
+ g_value_set_string (value, gtk_stack_get_visible_child_name (stack));
+ break;
+ case PROP_TRANSITION_DURATION:
+ g_value_set_int (value, gtk_stack_get_transition_duration (stack));
+ break;
+ case PROP_TRANSITION_TYPE:
+ g_value_set_int (value, gtk_stack_get_transition_type (stack));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_stack_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkStack *stack = GTK_STACK (object);
+
+ switch (property_id)
+ {
+ case PROP_HOMOGENEOUS:
+ gtk_stack_set_homogeneous (stack, g_value_get_boolean (value));
+ break;
+ case PROP_VISIBLE_CHILD:
+ gtk_stack_set_visible_child (stack, g_value_get_object (value));
+ break;
+ case PROP_VISIBLE_CHILD_NAME:
+ gtk_stack_set_visible_child_name (stack, g_value_get_string (value));
+ break;
+ case PROP_TRANSITION_DURATION:
+ gtk_stack_set_transition_duration (stack, g_value_get_int (value));
+ break;
+ case PROP_TRANSITION_TYPE:
+ gtk_stack_set_transition_type (stack, g_value_get_int (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_stack_realize (GtkWidget *widget)
+{
+ GtkStack *stack = GTK_STACK (widget);
+ GtkStackPrivate *priv = stack->priv;
+ GtkAllocation allocation;
+ GdkWindowAttr attributes = { 0 };
+ GdkWindowAttributesType attributes_mask;
+ GtkStackChildInfo *info;
+ GList *l;
+
+ gtk_widget_set_realized (widget, TRUE);
+
+ gtk_widget_get_allocation (widget, &allocation);
+
+ attributes.x = allocation.x;
+ attributes.y = allocation.y;
+ attributes.width = allocation.width;
+ attributes.height = allocation.height;
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.visual = gtk_widget_get_visual (widget);
+ attributes.event_mask =
+ gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK;
+ attributes_mask = (GDK_WA_X | GDK_WA_Y) | GDK_WA_VISUAL;
+
+ priv->view_window =
+ gdk_window_new (gtk_widget_get_parent_window ((GtkWidget*) stack),
+ &attributes, attributes_mask);
+ gtk_widget_set_window (widget, priv->view_window);
+ gtk_widget_register_window (widget, priv->view_window);
+
+ attributes.x = get_bin_window_x (stack, &allocation);
+ attributes.y = 0;
+ attributes.width = allocation.width;
+ attributes.height = allocation.height;
+
+ priv->bin_window =
+ gdk_window_new (priv->view_window, &attributes, attributes_mask);
+ gtk_widget_register_window (widget, priv->bin_window);
+
+ for (l = priv->children; l != NULL; l = l->next)
+ {
+ info = l->data;
+
+ gtk_widget_set_parent_window (info->widget, priv->bin_window);
+ }
+
+ gdk_window_show (priv->bin_window);
+}
+
+static void
+gtk_stack_unrealize (GtkWidget *widget)
+{
+ GtkStack *stack = GTK_STACK (widget);
+ GtkStackPrivate *priv = stack->priv;
+
+ gtk_widget_unregister_window (widget, priv->bin_window);
+ gdk_window_destroy (priv->bin_window);
+ priv->view_window = NULL;
+
+ GTK_WIDGET_CLASS (gtk_stack_parent_class)->unrealize (widget);
+}
+
+static void
+gtk_stack_class_init (GtkStackClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+
+ object_class->get_property = gtk_stack_get_property;
+ object_class->set_property = gtk_stack_set_property;
+ object_class->finalize = gtk_stack_finalize;
+
+ widget_class->size_allocate = gtk_stack_size_allocate;
+ widget_class->draw = gtk_stack_draw;
+ widget_class->realize = gtk_stack_realize;
+ widget_class->unrealize = gtk_stack_unrealize;
+ widget_class->get_preferred_height = gtk_stack_get_preferred_height;
+ widget_class->get_preferred_height_for_width = gtk_stack_get_preferred_height_for_width;
+ widget_class->get_preferred_width = gtk_stack_get_preferred_width;
+ widget_class->get_preferred_width_for_height = gtk_stack_get_preferred_width_for_height;
+ widget_class->compute_expand = gtk_stack_compute_expand;
+
+ container_class->add = gtk_stack_add;
+ container_class->remove = gtk_stack_remove;
+ container_class->forall = gtk_stack_forall;
+ container_class->set_child_property = gtk_stack_set_child_property;
+ container_class->get_child_property = gtk_stack_get_child_property;
+ /*container_class->get_path_for_child = gtk_stack_get_path_for_child; */
+ gtk_container_class_handle_border_width (container_class);
+
+ g_object_class_install_property (object_class,
+ PROP_HOMOGENEOUS,
+ g_param_spec_boolean ("homogeneous",
+ P_("Homogeneous"),
+ P_("Homogeneous sizing"),
+ TRUE,
+ GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+ g_object_class_install_property (object_class,
+ PROP_VISIBLE_CHILD,
+ g_param_spec_object ("visible-child",
+ P_("Visible child"),
+ P_("The widget currently visible in the stack"),
+ GTK_TYPE_WIDGET,
+ GTK_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_VISIBLE_CHILD_NAME,
+ g_param_spec_string ("visible-child-name",
+ P_("Name of visible child"),
+ P_("The name of the widget currently visible in the stack"),
+ NULL,
+ GTK_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_TRANSITION_DURATION,
+ g_param_spec_int ("transition-duration",
+ P_("Transition duration"),
+ P_("The animation duration, in milliseconds"),
+ G_MININT, G_MAXINT,
+ 200,
+ GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+ g_object_class_install_property (object_class,
+ PROP_TRANSITION_TYPE,
+ g_param_spec_int ("transition-type",
+ P_("Transition type"),
+ P_("The type of animation used to transition"),
+ GTK_STACK_TRANSITION_TYPE_NONE,
+ G_MAXINT,
+ GTK_STACK_TRANSITION_TYPE_NONE,
+ GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+
+ gtk_container_class_install_child_property (container_class, CHILD_PROP_NAME,
+ g_param_spec_string ("name",
+ P_("Name"),
+ P_("The name of the child page"),
+ NULL,
+ GTK_PARAM_READWRITE));
+
+ gtk_container_class_install_child_property (container_class, CHILD_PROP_TITLE,
+ g_param_spec_string ("title",
+ P_("Title"),
+ P_("The title of the child page"),
+ NULL,
+ GTK_PARAM_READWRITE));
+
+ gtk_container_class_install_child_property (container_class, CHILD_PROP_ICON_NAME,
+ g_param_spec_string ("icon-name",
+ P_("Icon name"),
+ P_("The icon name of the child page"),
+ NULL,
+ GTK_PARAM_READWRITE));
+
+ gtk_container_class_install_child_property (container_class, CHILD_PROP_POSITION,
+ g_param_spec_int ("position",
+ P_("Position"),
+ P_("The index of the child in the parent"),
+ -1, G_MAXINT, 0,
+ GTK_PARAM_READWRITE));
+
+ g_type_class_add_private (klass, sizeof (GtkStackPrivate));
+}
+
+
+GtkWidget *
+gtk_stack_new (void)
+{
+ return g_object_new (GTK_TYPE_STACK, NULL);
+}
+
+static GtkStackChildInfo *
+find_child_info_for_widget (GtkStack *stack,
+ GtkWidget *child)
+{
+ GtkStackPrivate *priv = stack->priv;
+ GtkStackChildInfo *info;
+ GList *l;
+
+ for (l = priv->children; l != NULL; l = l->next)
+ {
+ info = l->data;
+ if (info->widget == child)
+ return info;
+ }
+
+ return NULL;
+}
+
+static void
+reorder_child (GtkStack *stack,
+ GtkWidget *child,
+ gint position)
+{
+ GtkStackPrivate *priv;
+ GList *l;
+ GList *old_link = NULL;
+ GList *new_link = NULL;
+ GtkStackChildInfo *child_info = NULL;
+ gint num = 0;
+
+ priv = stack->priv;
+
+ l = priv->children;
+
+ /* Loop to find the old position and link of child, new link of child and
+ * total number of children. new_link will be NULL if the child should be
+ * moved to the end (in case of position being < 0 || >= num)
+ */
+ while (l && (new_link == NULL || old_link == NULL))
+ {
+ /* Record the new position if found */
+ if (position == num)
+ new_link = l;
+
+ if (old_link == NULL)
+ {
+ GtkStackChildInfo *info;
+ info = l->data;
+
+ /* Keep trying to find the current position and link location of the
+ child */
+ if (info->widget == child)
+ {
+ old_link = l;
+ child_info = info;
+ }
+ }
+
+ l = g_list_next (l);
+ num++;
+ }
+
+ g_return_if_fail (old_link != NULL);
+
+ if (old_link == new_link || (g_list_next (old_link) == NULL && new_link == NULL))
+ return;
+
+ priv->children = g_list_delete_link (priv->children, old_link);
+ priv->children = g_list_insert_before (priv->children, new_link, child_info);
+
+ gtk_widget_child_notify (child, "position");
+}
+
+static void
+gtk_stack_get_child_property (GtkContainer *container,
+ GtkWidget *child,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkStack *stack = GTK_STACK (container);
+ GtkStackChildInfo *info;
+ GList *list;
+ guint i;
+
+ info = find_child_info_for_widget (stack, child);
+ if (info == NULL)
+ {
+ GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
+ return;
+ }
+
+ switch (property_id)
+ {
+ case CHILD_PROP_NAME:
+ g_value_set_string (value, info->name);
+ break;
+
+ case CHILD_PROP_TITLE:
+ g_value_set_string (value, info->title);
+ break;
+
+ case CHILD_PROP_ICON_NAME:
+ g_value_set_string (value, info->icon_name);
+ break;
+
+ case CHILD_PROP_POSITION:
+ i = 0;
+ for (list = stack->priv->children; list != NULL; list = g_list_next (list))
+ {
+ if (info == list->data)
+ break;
+ ++i;
+ }
+ g_value_set_int (value, i);
+ break;
+
+ default:
+ GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_stack_set_child_property (GtkContainer *container,
+ GtkWidget *child,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkStack *stack = GTK_STACK (container);
+ GtkStackPrivate *priv = stack->priv;
+ GtkStackChildInfo *info;
+
+ info = find_child_info_for_widget (stack, child);
+ if (info == NULL)
+ {
+ GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
+ return;
+ }
+
+ 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;
+
+ case CHILD_PROP_ICON_NAME:
+ g_free (info->icon_name);
+ info->icon_name = g_value_dup_string (value);
+ gtk_container_child_notify (container, child, "icon-name");
+ break;
+
+ case CHILD_PROP_POSITION:
+ reorder_child (stack, child, g_value_get_int (value));
+ break;
+
+ default:
+ GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
+ break;
+ }
+}
+
+/* From clutter-easing.c, based on Robert Penner's
+ * infamous easing equations, MIT license.
+ */
+static double
+ease_out_cubic (double t)
+{
+ double p = t - 1;
+ return p * p * p + 1;
+}
+
+static gint
+get_bin_window_x (GtkStack *stack,
+ GtkAllocation *allocation)
+{
+ GtkStackPrivate *priv = stack->priv;
+ int x = 0;
+
+ if (priv->transition_pos < 1.0)
+ {
+ if (priv->transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT)
+ x = allocation->width * (1 - ease_out_cubic (priv->transition_pos));
+ if (priv->transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT)
+ x = -allocation->width * (1 - ease_out_cubic (priv->transition_pos));
+ }
+
+ return x;
+}
+
+static gboolean
+gtk_stack_set_transition_position (GtkStack *stack,
+ gdouble pos)
+{
+ GtkStackPrivate *priv = stack->priv;
+ gboolean done;
+
+ priv->transition_pos = pos;
+ gtk_widget_queue_draw (GTK_WIDGET (stack));
+
+ if (priv->bin_window != NULL &&
+ (priv->transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT ||
+ priv->transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT))
+ {
+ GtkAllocation allocation;
+ gtk_widget_get_allocation (GTK_WIDGET (stack), &allocation);
+ gdk_window_move (priv->bin_window,
+ get_bin_window_x (stack, &allocation), 0);
+ }
+
+ done = pos >= 1.0;
+
+ if (done || priv->last_visible_surface != NULL)
+ {
+ if (priv->last_visible_child)
+ {
+ gtk_widget_set_child_visible (priv->last_visible_child->widget, FALSE);
+ priv->last_visible_child = NULL;
+ }
+ }
+
+ if (done)
+ {
+ if (priv->last_visible_surface != NULL)
+ {
+ cairo_surface_destroy (priv->last_visible_surface);
+ priv->last_visible_surface = NULL;
+ }
+
+ gtk_widget_queue_resize (GTK_WIDGET (stack));
+ }
+
+ return done;
+}
+
+static gboolean
+gtk_stack_transition_cb (GtkStack *stack,
+ GdkFrameClock *frame_clock,
+ gpointer user_data)
+{
+ GtkStackPrivate *priv = stack->priv;
+ gint64 now;
+ gdouble t;
+
+ now = gdk_frame_clock_get_frame_time (frame_clock);
+
+ t = 1.0;
+ if (now < priv->end_time)
+ t = (now - priv->start_time) / (double) (priv->end_time - priv->start_time);
+
+ /* Finish animation early if not mapped anymore */
+ if (!gtk_widget_get_mapped (GTK_WIDGET (stack)))
+ t = 1.0;
+
+ if (gtk_stack_set_transition_position (stack, t))
+ {
+ gtk_widget_set_opacity (GTK_WIDGET (stack), 1.0);
+ priv->tick_id = 0;
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+gtk_stack_schedule_ticks (GtkStack *stack)
+{
+ GtkStackPrivate *priv = stack->priv;
+
+ if (priv->tick_id == 0)
+ {
+ priv->tick_id =
+ gtk_widget_add_tick_callback (GTK_WIDGET (stack), (GtkTickCallback)gtk_stack_transition_cb, stack, NULL);
+ }
+}
+
+static void
+gtk_stack_unschedule_ticks (GtkStack *stack)
+{
+ GtkStackPrivate *priv = stack->priv;
+
+ if (priv->tick_id != 0)
+ {
+ gtk_widget_remove_tick_callback (GTK_WIDGET (stack), priv->tick_id);
+ priv->tick_id = 0;
+ }
+}
+
+static void
+gtk_stack_start_transition (GtkStack *stack)
+{
+ GtkStackPrivate *priv = stack->priv;
+ GtkWidget *widget = GTK_WIDGET (stack);
+ gboolean animations_enabled;
+
+ g_object_get (gtk_widget_get_settings (widget),
+ "gtk-enable-animations", &animations_enabled,
+ NULL);
+
+ if (gtk_widget_get_mapped (widget) &&
+ animations_enabled &&
+ priv->transition_type != GTK_STACK_TRANSITION_TYPE_NONE &&
+ priv->last_visible_child != NULL)
+ {
+ gtk_widget_set_opacity (widget, 0.999);
+
+ priv->transition_pos = 0.0;
+ priv->start_time = gdk_frame_clock_get_frame_time (gtk_widget_get_frame_clock (widget));
+ priv->end_time = priv->start_time + (priv->transition_duration * 1000);
+ gtk_stack_schedule_ticks (stack);
+ }
+ else
+ {
+ gtk_stack_unschedule_ticks (stack);
+ gtk_stack_set_transition_position (stack, 1.0);
+ }
+}
+
+static void
+set_visible_child (GtkStack *stack,
+ GtkStackChildInfo *child_info)
+{
+ GtkStackPrivate *priv = stack->priv;
+ GtkStackChildInfo *info;
+ GtkWidget *widget = GTK_WIDGET (stack);
+ GList *l;
+
+ /* If none, pick first visible */
+ if (child_info == NULL)
+ {
+ for (l = priv->children; l != NULL; l = l->next)
+ {
+ info = l->data;
+ if (gtk_widget_get_visible (info->widget))
+ {
+ child_info = info;
+ break;
+ }
+ }
+ }
+
+ if (child_info == priv->visible_child)
+ return;
+
+ if (priv->last_visible_child)
+ gtk_widget_set_child_visible (priv->last_visible_child->widget, FALSE);
+ priv->last_visible_child = NULL;
+
+ if (priv->last_visible_surface != NULL)
+ cairo_surface_destroy (priv->last_visible_surface);
+ priv->last_visible_surface = NULL;
+
+ if (priv->visible_child && priv->visible_child->widget)
+ {
+ if (gtk_widget_is_visible (widget))
+ priv->last_visible_child = priv->visible_child;
+ else
+ gtk_widget_set_child_visible (priv->visible_child->widget, FALSE);
+ }
+
+ priv->visible_child = child_info;
+
+ if (child_info)
+ gtk_widget_set_child_visible (child_info->widget, TRUE);
+
+ gtk_widget_queue_resize (GTK_WIDGET (stack));
+ gtk_widget_queue_draw (GTK_WIDGET (stack));
+
+ g_object_notify (G_OBJECT (stack), "visible-child");
+ g_object_notify (G_OBJECT (stack), "visible-child-name");
+
+ gtk_stack_start_transition (stack);
+}
+
+static void
+stack_child_visibility_notify_cb (GObject *obj,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ GtkStack *stack = GTK_STACK (user_data);
+ GtkStackPrivate *priv = stack->priv;
+ GtkWidget *child = GTK_WIDGET (obj);
+ GtkStackChildInfo *child_info;
+
+ child_info = find_child_info_for_widget (stack, child);
+
+ if (priv->visible_child == NULL &&
+ gtk_widget_get_visible (child))
+ set_visible_child (stack, child_info);
+ else if (priv->visible_child == child_info &&
+ !gtk_widget_get_visible (child))
+ set_visible_child (stack, NULL);
+
+ if (child_info == priv->last_visible_child)
+ {
+ gtk_widget_set_child_visible (priv->last_visible_child->widget, FALSE);
+ priv->last_visible_child = NULL;
+ }
+}
+
+void
+gtk_stack_add_titled (GtkStack *stack,
+ GtkWidget *child,
+ const gchar *name,
+ const gchar *title)
+{
+ g_return_if_fail (GTK_IS_STACK (stack));
+ g_return_if_fail (GTK_IS_WIDGET (child));
+
+ gtk_container_add_with_properties (GTK_CONTAINER (stack),
+ child,
+ "name", name,
+ "title", title,
+ NULL);
+}
+
+void
+gtk_stack_add_named (GtkStack *stack,
+ GtkWidget *child,
+ const gchar *name)
+{
+ g_return_if_fail (GTK_IS_STACK (stack));
+ g_return_if_fail (GTK_IS_WIDGET (child));
+
+ gtk_container_add_with_properties (GTK_CONTAINER (stack),
+ child,
+ "name", name,
+ NULL);
+}
+
+static void
+gtk_stack_add (GtkContainer *container,
+ GtkWidget *child)
+{
+ GtkStack *stack = GTK_STACK (container);
+ GtkStackPrivate *priv = stack->priv;
+ GtkStackChildInfo *child_info;
+
+ g_return_if_fail (child != NULL);
+
+ child_info = g_slice_new (GtkStackChildInfo);
+ child_info->widget = child;
+ child_info->name = NULL;
+ child_info->title = NULL;
+ child_info->icon_name = NULL;
+
+ priv->children = g_list_append (priv->children, child_info);
+
+ gtk_widget_set_parent_window (child, priv->bin_window);
+ gtk_widget_set_parent (child, GTK_WIDGET (stack));
+
+ g_signal_connect (child, "notify::visible",
+ G_CALLBACK (stack_child_visibility_notify_cb), stack);
+
+ gtk_widget_child_notify (child, "position");
+
+ if (priv->visible_child == NULL &&
+ gtk_widget_get_visible (child))
+ set_visible_child (stack, child_info);
+ else
+ gtk_widget_set_child_visible (child, FALSE);
+
+ if (priv->homogeneous || priv->visible_child == child_info)
+ gtk_widget_queue_resize (GTK_WIDGET (stack));
+}
+
+static void
+gtk_stack_remove (GtkContainer *container,
+ GtkWidget *child)
+{
+ GtkStack *stack = GTK_STACK (container);
+ GtkStackPrivate *priv = stack->priv;
+ GtkStackChildInfo *child_info;
+ gboolean was_visible;
+
+ child_info = find_child_info_for_widget (stack, child);
+ if (child_info == NULL)
+ return;
+
+ priv->children = g_list_remove (priv->children, child_info);
+
+ g_signal_handlers_disconnect_by_func (child,
+ stack_child_visibility_notify_cb,
+ stack);
+
+ was_visible = gtk_widget_get_visible (child);
+
+ child_info->widget = NULL;
+
+ if (priv->visible_child == child_info)
+ set_visible_child (stack, NULL);
+
+ if (priv->last_visible_child == child_info)
+ priv->last_visible_child = NULL;
+
+ gtk_widget_unparent (child);
+
+ g_free (child_info->name);
+ g_free (child_info->title);
+ g_free (child_info->icon_name);
+ g_slice_free (GtkStackChildInfo, child_info);
+
+ if (priv->homogeneous && was_visible)
+ gtk_widget_queue_resize (GTK_WIDGET (stack));
+}
+
+void
+gtk_stack_set_homogeneous (GtkStack *stack,
+ gboolean homogeneous)
+{
+ GtkStackPrivate *priv;
+
+ g_return_if_fail (GTK_IS_STACK (stack));
+
+ priv = stack->priv;
+
+ homogeneous = !!homogeneous;
+
+ if (priv->homogeneous == homogeneous)
+ return;
+
+ priv->homogeneous = homogeneous;
+
+ if (gtk_widget_get_visible (GTK_WIDGET(stack)))
+ gtk_widget_queue_resize (GTK_WIDGET (stack));
+
+ g_object_notify (G_OBJECT (stack), "homogeneous");
+}
+
+gboolean
+gtk_stack_get_homogeneous (GtkStack *stack)
+{
+ g_return_val_if_fail (GTK_IS_STACK (stack), FALSE);
+
+ return stack->priv->homogeneous;
+}
+
+gint
+gtk_stack_get_transition_duration (GtkStack *stack)
+{
+ g_return_val_if_fail (GTK_IS_STACK (stack), 0);
+
+ return stack->priv->transition_duration;
+}
+
+void
+gtk_stack_set_transition_duration (GtkStack *stack,
+ gint value)
+{
+ g_return_if_fail (GTK_IS_STACK (stack));
+
+ stack->priv->transition_duration = value;
+ g_object_notify (G_OBJECT (stack), "transition-duration");
+}
+
+GtkStackTransitionType
+gtk_stack_get_transition_type (GtkStack *stack)
+{
+ g_return_val_if_fail (GTK_IS_STACK (stack), GTK_STACK_TRANSITION_TYPE_NONE);
+
+ return stack->priv->transition_type;
+}
+
+void
+gtk_stack_set_transition_type (GtkStack *stack,
+ GtkStackTransitionType value)
+{
+ g_return_if_fail (GTK_IS_STACK (stack));
+
+ stack->priv->transition_type = value;
+ g_object_notify (G_OBJECT (stack), "transition-type");
+}
+
+/**
+ * gtk_stack_get_visible_child:
+ * @stack: a #GtkStack
+ *
+ * Gets the currently visible child of the #GtkStack, or %NULL if the
+ * there are no visible children. The returned widget does not have a reference
+ * added, so you do not need to unref it.
+ *
+ * Return value: (transfer none): pointer to child of the #GtkStack
+ **/
+GtkWidget *
+gtk_stack_get_visible_child (GtkStack *stack)
+{
+ g_return_val_if_fail (GTK_IS_STACK (stack), NULL);
+
+ return stack->priv->visible_child ? stack->priv->visible_child->widget : NULL;
+}
+
+const gchar *
+gtk_stack_get_visible_child_name (GtkStack *stack)
+{
+ g_return_val_if_fail (GTK_IS_STACK (stack), NULL);
+
+ if (stack->priv->visible_child)
+ return stack->priv->visible_child->name;
+
+ return NULL;
+}
+
+void
+gtk_stack_set_visible_child (GtkStack *stack,
+ GtkWidget *child)
+{
+ GtkStackChildInfo *child_info;
+
+ g_return_if_fail (GTK_IS_STACK (stack));
+ g_return_if_fail (GTK_IS_WIDGET (child));
+
+ child_info = find_child_info_for_widget (stack, child);
+ if (child_info == NULL)
+ return;
+
+ if (gtk_widget_get_visible (child_info->widget))
+ set_visible_child (stack, child_info);
+}
+
+void
+gtk_stack_set_visible_child_name (GtkStack *stack,
+ const gchar *name)
+{
+ GtkStackPrivate *priv;
+ GtkStackChildInfo *child_info, *info;
+ GList *l;
+
+ g_return_if_fail (GTK_IS_STACK (stack));
+ g_return_if_fail (name != NULL);
+
+ priv = stack->priv;
+
+ child_info = NULL;
+ for (l = priv->children; l != NULL; l = l->next)
+ {
+ info = l->data;
+ if (info->name != NULL &&
+ strcmp (info->name, name) == 0)
+ {
+ child_info = info;
+ break;
+ }
+ }
+
+ if (child_info != NULL && gtk_widget_get_visible (child_info->widget))
+ set_visible_child (stack, child_info);
+}
+
+static void
+gtk_stack_forall (GtkContainer *container,
+ gboolean include_internals,
+ GtkCallback callback,
+ gpointer callback_data)
+{
+ GtkStack *stack = GTK_STACK (container);
+ GtkStackPrivate *priv = stack->priv;
+ GtkStackChildInfo *child_info;
+ GList *l;
+
+ l = priv->children;
+ while (l)
+ {
+ child_info = l->data;
+ l = l->next;
+
+ (* callback) (child_info->widget, callback_data);
+ }
+}
+
+static void
+gtk_stack_compute_expand (GtkWidget *widget,
+ gboolean *hexpand_p,
+ gboolean *vexpand_p)
+{
+ GtkStack *stack = GTK_STACK (widget);
+ GtkStackPrivate *priv = stack->priv;
+ gboolean hexpand, vexpand;
+ GtkStackChildInfo *child_info;
+ GtkWidget *child;
+ GList *l;
+
+ hexpand = FALSE;
+ vexpand = FALSE;
+ for (l = priv->children; l != NULL; l = l->next)
+ {
+ child_info = l->data;
+ child = child_info->widget;
+
+ if (!hexpand &&
+ gtk_widget_compute_expand (child, GTK_ORIENTATION_HORIZONTAL))
+ hexpand = TRUE;
+
+ if (!vexpand &&
+ gtk_widget_compute_expand (child, GTK_ORIENTATION_VERTICAL))
+ vexpand = TRUE;
+
+ if (hexpand && vexpand)
+ break;
+ }
+
+ *hexpand_p = hexpand;
+ *vexpand_p = vexpand;
+}
+
+static void
+gtk_stack_draw_crossfade (GtkWidget *widget,
+ cairo_t *cr)
+{
+ GtkStack *stack = GTK_STACK (widget);
+ GtkStackPrivate *priv = stack->priv;
+
+ if (priv->last_visible_surface)
+ {
+ cairo_set_source_surface (cr, priv->last_visible_surface,
+ priv->last_visible_surface_allocation.x,
+ priv->last_visible_surface_allocation.y);
+ cairo_set_operator (cr, CAIRO_OPERATOR_ADD);
+ cairo_paint_with_alpha (cr, MAX (1.0 - priv->transition_pos, 0));
+ }
+
+ cairo_push_group (cr);
+ cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+ gtk_container_propagate_draw (GTK_CONTAINER (stack),
+ priv->visible_child->widget,
+ cr);
+ cairo_pop_group_to_source (cr);
+ cairo_set_operator (cr, CAIRO_OPERATOR_ADD);
+ cairo_paint_with_alpha (cr, priv->transition_pos);
+}
+
+static void
+gtk_stack_draw_slide (GtkWidget *widget,
+ cairo_t *cr)
+{
+ GtkStack *stack = GTK_STACK (widget);
+ GtkStackPrivate *priv = stack->priv;
+ GtkAllocation allocation;
+ int x = 0;
+
+ gtk_widget_get_allocation (widget, &allocation);
+
+ x = get_bin_window_x (stack, &allocation);
+
+ if (priv->transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT)
+ x -= allocation.width;
+ if (priv->transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT)
+ x += allocation.width;
+
+ if (priv->last_visible_surface)
+ {
+ cairo_save (cr);
+ cairo_set_source_surface (cr, priv->last_visible_surface, x, 0);
+ cairo_paint (cr);
+ cairo_restore (cr);
+ }
+
+ gtk_container_propagate_draw (GTK_CONTAINER (stack),
+ priv->visible_child->widget,
+ cr);
+}
+
+static gboolean
+gtk_stack_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ GtkStack *stack = GTK_STACK (widget);
+ GtkStackPrivate *priv = stack->priv;
+ cairo_t *pattern_cr;
+
+ if (priv->visible_child)
+ {
+ if (priv->transition_pos < 1.0)
+ {
+ if (priv->last_visible_surface == NULL &&
+ priv->last_visible_child != NULL)
+ {
+ gtk_widget_get_allocation (priv->last_visible_child->widget,
+ &priv->last_visible_surface_allocation);
+ priv->last_visible_surface =
+ gdk_window_create_similar_surface (gtk_widget_get_window (widget),
+ CAIRO_CONTENT_COLOR_ALPHA,
+ priv->last_visible_surface_allocation.width,
+ priv->last_visible_surface_allocation.height);
+ pattern_cr = cairo_create (priv->last_visible_surface);
+ /* We don't use propagate_draw here, because we don't want to apply
+ the bin_window offset */
+ gtk_widget_draw (priv->last_visible_child->widget, pattern_cr);
+ cairo_destroy (pattern_cr);
+ }
+
+ switch (priv->transition_type)
+ {
+ case GTK_STACK_TRANSITION_TYPE_CROSSFADE:
+ gtk_stack_draw_crossfade (widget, cr);
+ break;
+ case GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT:
+ case GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT:
+ gtk_stack_draw_slide (widget, cr);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ }
+ else if (gtk_cairo_should_draw_window (cr, priv->bin_window))
+ gtk_container_propagate_draw (GTK_CONTAINER (stack),
+ priv->visible_child->widget,
+ cr);
+ }
+
+ return TRUE;
+}
+
+static void
+gtk_stack_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GtkStack *stack = GTK_STACK (widget);
+ GtkStackPrivate *priv = stack->priv;
+ GtkAllocation child_allocation;
+
+ g_return_if_fail (allocation != NULL);
+
+ gtk_widget_set_allocation (widget, allocation);
+
+ child_allocation = *allocation;
+ child_allocation.x = 0;
+ child_allocation.y = 0;
+
+ if (priv->last_visible_child)
+ gtk_widget_size_allocate (priv->last_visible_child->widget, &child_allocation);
+
+ if (priv->visible_child)
+ gtk_widget_size_allocate (priv->visible_child->widget, &child_allocation);
+
+ if (gtk_widget_get_realized (widget))
+ {
+ gdk_window_move_resize (priv->view_window,
+ allocation->x, allocation->y,
+ allocation->width, allocation->height);
+ gdk_window_move_resize (priv->bin_window,
+ get_bin_window_x (stack, allocation), 0,
+ allocation->width, allocation->height);
+ }
+}
+
+static void
+gtk_stack_get_preferred_height (GtkWidget *widget,
+ gint *minimum_height,
+ gint *natural_height)
+{
+ GtkStack *stack = GTK_STACK (widget);
+ GtkStackPrivate *priv = stack->priv;
+ GtkStackChildInfo *child_info;
+ GtkWidget *child;
+ gint child_min, child_nat;
+ GList *l;
+
+ *minimum_height = 0;
+ *natural_height = 0;
+
+ for (l = priv->children; l != NULL; l = l->next)
+ {
+ child_info = l->data;
+ child = child_info->widget;
+
+ if (!priv->homogeneous &&
+ (priv->visible_child != child_info &&
+ priv->last_visible_child != child_info))
+ continue;
+ if (gtk_widget_get_visible (child))
+ {
+ gtk_widget_get_preferred_height (child, &child_min, &child_nat);
+
+ *minimum_height = MAX (*minimum_height, child_min);
+ *natural_height = MAX (*natural_height, child_nat);
+ }
+ }
+
+ if (priv->last_visible_surface != NULL)
+ {
+ *minimum_height = MAX (*minimum_height, priv->last_visible_surface_allocation.height);
+ *natural_height = MAX (*natural_height, priv->last_visible_surface_allocation.height);
+ }
+}
+
+static void
+gtk_stack_get_preferred_height_for_width (GtkWidget *widget,
+ gint width,
+ gint *minimum_height,
+ gint *natural_height)
+{
+ GtkStack *stack = GTK_STACK (widget);
+ GtkStackPrivate *priv = stack->priv;
+ GtkStackChildInfo *child_info;
+ GtkWidget *child;
+ gint child_min, child_nat;
+ GList *l;
+
+ *minimum_height = 0;
+ *natural_height = 0;
+
+ for (l = priv->children; l != NULL; l = l->next)
+ {
+ child_info = l->data;
+ child = child_info->widget;
+
+ if (!priv->homogeneous &&
+ (priv->visible_child != child_info &&
+ priv->last_visible_child != child_info))
+ continue;
+ if (gtk_widget_get_visible (child))
+ {
+ gtk_widget_get_preferred_height_for_width (child, width, &child_min, &child_nat);
+
+ *minimum_height = MAX (*minimum_height, child_min);
+ *natural_height = MAX (*natural_height, child_nat);
+ }
+ }
+
+ if (priv->last_visible_surface != NULL)
+ {
+ *minimum_height = MAX (*minimum_height, priv->last_visible_surface_allocation.height);
+ *natural_height = MAX (*natural_height, priv->last_visible_surface_allocation.height);
+ }
+}
+
+static void
+gtk_stack_get_preferred_width (GtkWidget *widget,
+ gint *minimum_width,
+ gint *natural_width)
+{
+ GtkStack *stack = GTK_STACK (widget);
+ GtkStackPrivate *priv = stack->priv;
+ GtkStackChildInfo *child_info;
+ GtkWidget *child;
+ gint child_min, child_nat;
+ GList *l;
+
+ *minimum_width = 0;
+ *natural_width = 0;
+
+ for (l = priv->children; l != NULL; l = l->next)
+ {
+ child_info = l->data;
+ child = child_info->widget;
+
+ if (!priv->homogeneous &&
+ (priv->visible_child != child_info &&
+ priv->last_visible_child != child_info))
+ continue;
+ if (gtk_widget_get_visible (child))
+ {
+ gtk_widget_get_preferred_width (child, &child_min, &child_nat);
+
+ *minimum_width = MAX (*minimum_width, child_min);
+ *natural_width = MAX (*natural_width, child_nat);
+ }
+ }
+
+ if (priv->last_visible_surface != NULL)
+ {
+ *minimum_width = MAX (*minimum_width, priv->last_visible_surface_allocation.width);
+ *natural_width = MAX (*natural_width, priv->last_visible_surface_allocation.width);
+ }
+}
+
+static void
+gtk_stack_get_preferred_width_for_height (GtkWidget *widget,
+ gint height,
+ gint *minimum_width,
+ gint *natural_width)
+{
+ GtkStack *stack = GTK_STACK (widget);
+ GtkStackPrivate *priv = stack->priv;
+ GtkStackChildInfo *child_info;
+ GtkWidget *child;
+ gint child_min, child_nat;
+ GList *l;
+
+ *minimum_width = 0;
+ *natural_width = 0;
+
+ for (l = priv->children; l != NULL; l = l->next)
+ {
+ child_info = l->data;
+ child = child_info->widget;
+
+ if (!priv->homogeneous &&
+ (priv->visible_child != child_info &&
+ priv->last_visible_child != child_info))
+ continue;
+ if (gtk_widget_get_visible (child))
+ {
+ gtk_widget_get_preferred_width_for_height (child, height, &child_min, &child_nat);
+
+ *minimum_width = MAX (*minimum_width, child_min);
+ *natural_width = MAX (*natural_width, child_nat);
+ }
+ }
+
+ if (priv->last_visible_surface != NULL)
+ {
+ *minimum_width = MAX (*minimum_width, priv->last_visible_surface_allocation.width);
+ *natural_width = MAX (*natural_width, priv->last_visible_surface_allocation.width);
+ }
+}
diff --git a/gtk/gtkstack.h b/gtk/gtkstack.h
new file mode 100644
index 0000000000..86dd4f4d28
--- /dev/null
+++ b/gtk/gtkstack.h
@@ -0,0 +1,85 @@
+/*
+ * 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
+ *
+ * Author: Alexander Larsson <alexl@redhat.com>
+ *
+ */
+
+#ifndef __GTK_STACK_H__
+#define __GTK_STACK_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+
+#define GTK_TYPE_STACK (gtk_stack_get_type ())
+#define GTK_STACK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_STACK, GtkStack))
+#define GTK_STACK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_STACK, GtkStackClass))
+#define GTK_IS_STACK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_STACK))
+#define GTK_IS_STACK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_STACK))
+#define GTK_STACK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_STACK, GtkStackClass))
+
+typedef struct _GtkStack GtkStack;
+typedef struct _GtkStackClass GtkStackClass;
+typedef struct _GtkStackPrivate GtkStackPrivate;
+
+typedef enum {
+ GTK_STACK_TRANSITION_TYPE_NONE,
+ GTK_STACK_TRANSITION_TYPE_CROSSFADE,
+ GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT,
+ GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT
+} GtkStackTransitionType;
+
+struct _GtkStack {
+ GtkContainer parent_instance;
+ GtkStackPrivate *priv;
+};
+
+struct _GtkStackClass {
+ GtkContainerClass parent_class;
+};
+
+GType gtk_stack_get_type (void) G_GNUC_CONST;
+
+GtkWidget * gtk_stack_new (void);
+void gtk_stack_add_named (GtkStack *stack,
+ GtkWidget *child,
+ const gchar *name);
+void gtk_stack_add_titled (GtkStack *stack,
+ GtkWidget *child,
+ const gchar *name,
+ const gchar *title);
+void gtk_stack_set_visible_child (GtkStack *stack,
+ GtkWidget *child);
+GtkWidget * gtk_stack_get_visible_child (GtkStack *stack);
+void gtk_stack_set_visible_child_name (GtkStack *stack,
+ const gchar *name);
+const gchar * gtk_stack_get_visible_child_name (GtkStack *stack);
+void gtk_stack_set_homogeneous (GtkStack *stack,
+ gboolean homogeneous);
+gboolean gtk_stack_get_homogeneous (GtkStack *stack);
+void gtk_stack_set_transition_duration (GtkStack *stack,
+ gint transition_duration);
+gint gtk_stack_get_transition_duration (GtkStack *stack);
+void gtk_stack_set_transition_type (GtkStack *stack,
+ GtkStackTransitionType type);
+GtkStackTransitionType gtk_stack_get_transition_type (GtkStack *stack);
+
+G_END_DECLS
+
+#endif
diff --git a/gtk/gtkstackswitcher.c b/gtk/gtkstackswitcher.c
new file mode 100644
index 0000000000..3aef9ca3bb
--- /dev/null
+++ b/gtk/gtkstackswitcher.c
@@ -0,0 +1,413 @@
+/*
+ * 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 "config.h"
+#include "gtkstackswitcher.h"
+#include "gtkprivate.h"
+#include "gtkintl.h"
+
+struct _GtkStackSwitcherPrivate
+{
+ GtkStack *stack;
+ GHashTable *buttons;
+ gboolean in_child_changed;
+};
+
+enum {
+ PROP_0,
+ PROP_STACK
+};
+
+G_DEFINE_TYPE (GtkStackSwitcher, gtk_stack_switcher, GTK_TYPE_BOX);
+
+static void
+gtk_stack_switcher_init (GtkStackSwitcher *switcher)
+{
+ GtkStyleContext *context;
+ GtkStackSwitcherPrivate *priv;
+
+ priv = G_TYPE_INSTANCE_GET_PRIVATE (switcher, GTK_TYPE_STACK_SWITCHER, GtkStackSwitcherPrivate);
+ 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 (GtkStackSwitcher *self)
+{
+ gtk_container_foreach (GTK_CONTAINER (self), (GtkCallback) gtk_widget_destroy, self);
+}
+
+static void
+on_button_clicked (GtkWidget *widget,
+ GtkStackSwitcher *self)
+{
+ GtkWidget *child;
+
+ if (!self->priv->in_child_changed)
+ {
+ child = g_object_get_data (G_OBJECT (widget), "stack-child");
+ gtk_stack_set_visible_child (self->priv->stack, child);
+ }
+}
+
+static void
+rebuild_child (GtkWidget *self,
+ const gchar *icon_name,
+ const gchar *title)
+{
+ GtkStyleContext *context;
+ GtkWidget *button_child;
+
+ gtk_widget_set_valign (GTK_WIDGET (self), GTK_ALIGN_CENTER);
+
+ button_child = gtk_bin_get_child (GTK_BIN (self));
+ if (button_child != NULL)
+ gtk_widget_destroy (button_child);
+
+ button_child = NULL;
+ context = gtk_widget_get_style_context (GTK_WIDGET (self));
+
+ if (icon_name != NULL)
+ {
+ button_child = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
+ if (title != NULL)
+ gtk_widget_set_tooltip_text (GTK_WIDGET (self), title);
+
+ gtk_style_context_remove_class (context, "text-button");
+ gtk_style_context_add_class (context, "image-button");
+ }
+ else if (title != NULL)
+ {
+ button_child = gtk_label_new (title);
+
+ gtk_style_context_remove_class (context, "image-button");
+ gtk_style_context_add_class (context, "text-button");
+ }
+
+ if (button_child)
+ {
+ gtk_widget_show_all (button_child);
+ gtk_container_add (GTK_CONTAINER (self), button_child);
+ }
+}
+
+static void
+update_button (GtkStackSwitcher *self,
+ GtkWidget *widget,
+ GtkWidget *button)
+{
+ gchar *title;
+ gchar *icon_name;
+
+ gtk_container_child_get (GTK_CONTAINER (self->priv->stack), widget,
+ "title", &title,
+ "icon-name", &icon_name,
+ NULL);
+
+ rebuild_child (button, icon_name, title);
+
+ gtk_widget_set_visible (button, title != NULL || icon_name != NULL);
+
+ if (icon_name != NULL)
+ gtk_widget_set_size_request (button, -1, -1);
+ else
+ gtk_widget_set_size_request (button, 100, -1);
+
+ g_free (title);
+ g_free (icon_name);
+}
+
+static void
+on_title_icon_updated (GtkWidget *widget,
+ GParamSpec *pspec,
+ GtkStackSwitcher *self)
+{
+ GtkWidget *button;
+
+ button = g_hash_table_lookup (self->priv->buttons, widget);
+ update_button (self, widget, button);
+}
+
+static void
+on_position_updated (GtkWidget *widget,
+ GParamSpec *pspec,
+ GtkStackSwitcher *self)
+{
+ GtkWidget *button;
+ gint position;
+
+ button = g_hash_table_lookup (self->priv->buttons, widget);
+
+ gtk_container_child_get (GTK_CONTAINER (self->priv->stack), widget,
+ "position", &position,
+ NULL);
+
+ gtk_box_reorder_child (GTK_BOX (self), button, position);
+}
+
+static void
+add_child (GtkStackSwitcher *self,
+ GtkWidget *widget)
+{
+ GtkWidget *button;
+ GList *group;
+
+ button = gtk_radio_button_new (NULL);
+ gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (button), FALSE);
+
+ update_button (self, widget, 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);
+
+ 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_icon_updated), self);
+ g_signal_connect (widget, "child-notify::icon-name", G_CALLBACK (on_title_icon_updated), self);
+ g_signal_connect (widget, "child-notify::position", G_CALLBACK (on_position_updated), self);
+
+ g_hash_table_insert (self->priv->buttons, widget, button);
+}
+
+static void
+foreach_stack (GtkWidget *widget,
+ GtkStackSwitcher *self)
+{
+ add_child (self, widget);
+}
+
+static void
+populate_switcher (GtkStackSwitcher *self)
+{
+ gtk_container_foreach (GTK_CONTAINER (self->priv->stack), (GtkCallback)foreach_stack, self);
+}
+
+static void
+on_child_changed (GtkWidget *widget,
+ GParamSpec *pspec,
+ GtkStackSwitcher *self)
+{
+ GtkWidget *child;
+ GtkWidget *button;
+
+ child = gtk_stack_get_visible_child (GTK_STACK (widget));
+ button = g_hash_table_lookup (self->priv->buttons, child);
+ if (button != NULL)
+ {
+ self->priv->in_child_changed = TRUE;
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
+ self->priv->in_child_changed = FALSE;
+ }
+}
+
+static void
+on_stack_child_added (GtkContainer *container,
+ GtkWidget *widget,
+ GtkStackSwitcher *self)
+{
+ add_child (self, widget);
+}
+
+static void
+on_stack_child_removed (GtkContainer *container,
+ GtkWidget *widget,
+ GtkStackSwitcher *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 (GtkStackSwitcher *switcher)
+{
+ GtkStackSwitcherPrivate *priv = switcher->priv;
+
+ g_signal_handlers_disconnect_by_func (priv->stack, on_stack_child_added, switcher);
+ g_signal_handlers_disconnect_by_func (priv->stack, on_stack_child_removed, switcher);
+ g_signal_handlers_disconnect_by_func (priv->stack, on_child_changed, switcher);
+ g_signal_handlers_disconnect_by_func (priv->stack, disconnect_stack_signals, switcher);
+}
+
+static void
+connect_stack_signals (GtkStackSwitcher *switcher)
+{
+ GtkStackSwitcherPrivate *priv = switcher->priv;
+
+ g_signal_connect_after (priv->stack, "add",
+ G_CALLBACK (on_stack_child_added), switcher);
+ g_signal_connect_after (priv->stack, "remove",
+ G_CALLBACK (on_stack_child_removed), switcher);
+ g_signal_connect (priv->stack, "notify::visible-child",
+ G_CALLBACK (on_child_changed), switcher);
+ g_signal_connect_swapped (priv->stack, "destroy",
+ G_CALLBACK (disconnect_stack_signals), switcher);
+}
+
+/**
+ * gtk_stack_switcher_set_stack:
+ * @switcher: a #GtkStackSwitcher
+ * @stack: (allow-none): a #GtkStack
+ *
+ * Sets the stack to control.
+ */
+void
+gtk_stack_switcher_set_stack (GtkStackSwitcher *switcher,
+ GtkStack *stack)
+{
+ GtkStackSwitcherPrivate *priv;
+
+ g_return_if_fail (GTK_IS_STACK_SWITCHER (switcher));
+ if (stack)
+ g_return_if_fail (GTK_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");
+}
+
+/**
+ * gtk_stack_switcher_get_stack:
+ * @switcher: a #GtkStackSwitcher
+ *
+ * Retrieves the stack. See
+ * gtk_stack_switcher_set_stack().
+ *
+ * Return value: (transfer none): the stack, or %NULL if
+ * none has been set explicitly.
+ */
+GtkStack *
+gtk_stack_switcher_get_stack (GtkStackSwitcher *switcher)
+{
+ g_return_val_if_fail (GTK_IS_STACK_SWITCHER (switcher), NULL);
+
+ return switcher->priv->stack;
+}
+
+static void
+gtk_stack_switcher_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkStackSwitcher *switcher = GTK_STACK_SWITCHER (object);
+ GtkStackSwitcherPrivate *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
+gtk_stack_switcher_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkStackSwitcher *switcher = GTK_STACK_SWITCHER (object);
+
+ switch (prop_id)
+ {
+ case PROP_STACK:
+ gtk_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
+gtk_stack_switcher_dispose (GObject *object)
+{
+ GtkStackSwitcher *switcher = GTK_STACK_SWITCHER (object);
+
+ gtk_stack_switcher_set_stack (switcher, NULL);
+
+ G_OBJECT_CLASS (gtk_stack_switcher_parent_class)->dispose (object);
+}
+
+static void
+gtk_stack_switcher_class_init (GtkStackSwitcherClass *class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+ object_class->get_property = gtk_stack_switcher_get_property;
+ object_class->set_property = gtk_stack_switcher_set_property;
+ object_class->dispose = gtk_stack_switcher_dispose;
+
+ g_object_class_install_property (object_class,
+ PROP_STACK,
+ g_param_spec_object ("stack",
+ P_("Stack"),
+ P_("Stack"),
+ GTK_TYPE_STACK,
+ GTK_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_type_class_add_private (object_class, sizeof (GtkStackSwitcherPrivate));
+}
+
+GtkWidget *
+gtk_stack_switcher_new (void)
+{
+ return GTK_WIDGET (g_object_new (GTK_TYPE_STACK_SWITCHER, NULL));
+}
diff --git a/gtk/gtkstackswitcher.h b/gtk/gtkstackswitcher.h
new file mode 100644
index 0000000000..76d93a9f4b
--- /dev/null
+++ b/gtk/gtkstackswitcher.h
@@ -0,0 +1,65 @@
+/*
+ * 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 __GTK_STACK_SWITCHER_H__
+#define __GTK_STACK_SWITCHER_H__
+
+#include "gtkstack.h"
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_STACK_SWITCHER (gtk_stack_switcher_get_type ())
+#define GTK_STACK_SWITCHER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_STACK_SWITCHER, GtkStackSwitcher))
+#define GTK_STACK_SWITCHER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_STACK_SWITCHER, GtkStackSwitcherClass))
+#define GTK_IS_STACK_SWITCHER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_STACK_SWITCHER))
+#define GTK_IS_STACK_SWITCHER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_STACK_SWITCHER))
+#define GTK_STACK_SWITCHER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_STACK_SWITCHER, GtkStackSwitcherClass))
+
+typedef struct _GtkStackSwitcher GtkStackSwitcher;
+typedef struct _GtkStackSwitcherPrivate GtkStackSwitcherPrivate;
+typedef struct _GtkStackSwitcherClass GtkStackSwitcherClass;
+
+struct _GtkStackSwitcher
+{
+ GtkBox widget;
+
+ /*< private >*/
+ GtkStackSwitcherPrivate *priv;
+};
+
+struct _GtkStackSwitcherClass
+{
+ GtkBoxClass parent_class;
+
+ /* Padding for future expansion */
+ void (*_gtk_reserved1) (void);
+ void (*_gtk_reserved2) (void);
+ void (*_gtk_reserved3) (void);
+ void (*_gtk_reserved4) (void);
+};
+
+GType gtk_stack_switcher_get_type (void) G_GNUC_CONST;
+GtkWidget * gtk_stack_switcher_new (void);
+void gtk_stack_switcher_set_stack (GtkStackSwitcher *switcher,
+ GtkStack *stack);
+GtkStack * gtk_stack_switcher_get_stack (GtkStackSwitcher *switcher);
+
+G_END_DECLS
+
+#endif /* __GTK_STACK_SWITCHER_H__ */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 74d88eb22f..8024a98793 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -130,7 +130,8 @@ noinst_PROGRAMS = $(TEST_PROGS) \
testpixbuf-color \
testpixbuf-scale \
testgmenu \
- testlogout
+ testlogout \
+ teststack
if USE_X11
noinst_PROGRAMS += testerrors
@@ -254,6 +255,7 @@ testpixbuf_color_DEPENDENCIES = $(TEST_DEPS)
testpixbuf_scale_DEPENDENCIES = $(TEST_DEPS)
testgmenu_DEPENDENCIES = $(TEST_DEPS)
testlogout_DEPENDENCIES = $(TEST_DEPS)
+teststack_DEPENDENCIES = $(TEST_DEPS)
animated_resizing_SOURCES = \
animated-resizing.c \
@@ -452,6 +454,8 @@ testcolorchooser_SOURCES = testcolorchooser.c
testkineticscrolling_SOURCES = testkineticscrolling.c
+teststack_SOURCES = teststack.c
+
EXTRA_DIST += \
gradient1.png \
prop-editor.h \
diff --git a/tests/teststack.c b/tests/teststack.c
new file mode 100644
index 0000000000..30837c69f1
--- /dev/null
+++ b/tests/teststack.c
@@ -0,0 +1,249 @@
+#include <gtk/gtk.h>
+
+GtkWidget *stack;
+GtkWidget *switcher;
+GtkWidget *w1;
+
+static void
+set_visible_child (GtkWidget *button, gpointer data)
+{
+ gtk_stack_set_visible_child (GTK_STACK (stack), GTK_WIDGET (data));
+}
+
+static void
+set_visible_child_name (GtkWidget *button, gpointer data)
+{
+ gtk_stack_set_visible_child_name (GTK_STACK (stack), (const char *)data);
+}
+
+static void
+toggle_homogeneous (GtkWidget *button, gpointer data)
+{
+ gboolean active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
+ gtk_stack_set_homogeneous (GTK_STACK (stack), active);
+}
+
+static void
+toggle_icon_name (GtkWidget *button, gpointer data)
+{
+ gboolean active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
+ gtk_container_child_set (GTK_CONTAINER (stack), w1,
+ "icon-name", active ? "edit-find-symbolic" : NULL,
+ NULL);
+}
+
+static void
+toggle_transitions (GtkWidget *combo, gpointer data)
+{
+ int id = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
+ gtk_stack_set_transition_type (GTK_STACK (stack), id);
+}
+
+static void
+on_back_button_clicked (GtkButton *button, GtkStack *stack)
+{
+ const gchar *seq[] = { "1", "2", "3" };
+ const gchar *vis;
+ gint i;
+
+ vis = gtk_stack_get_visible_child_name (stack);
+
+ for (i = 1; i < G_N_ELEMENTS (seq); i++)
+ {
+ if (g_str_equal (vis, seq[i]))
+ {
+ gtk_stack_set_transition_type (stack, GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
+ gtk_stack_set_visible_child_name (stack, seq[i - 1]);
+ break;
+ }
+ }
+}
+
+static void
+on_forward_button_clicked (GtkButton *button, GtkStack *stack)
+{
+ const gchar *seq[] = { "1", "2", "3" };
+ const gchar *vis;
+ gint i;
+
+ vis = gtk_stack_get_visible_child_name (stack);
+
+ for (i = 0; i < G_N_ELEMENTS (seq) - 1; i++)
+ {
+ if (g_str_equal (vis, seq[i]))
+ {
+ gtk_stack_set_transition_type (stack, GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
+ gtk_stack_set_visible_child_name (stack, seq[i + 1]);
+ break;
+ }
+ }
+}
+
+static void
+update_back_button_sensitivity (GtkStack *stack, GParamSpec *pspec, GtkWidget *button)
+{
+ const gchar *vis;
+
+ vis = gtk_stack_get_visible_child_name (stack);
+ gtk_widget_set_sensitive (button, ! g_str_equal (vis, "1"));
+}
+
+static void
+update_forward_button_sensitivity (GtkStack *stack, GParamSpec *pspec, GtkWidget *button)
+{
+ const gchar *vis;
+
+ vis = gtk_stack_get_visible_child_name (stack);
+ gtk_widget_set_sensitive (button, ! g_str_equal (vis, "3"));
+}
+
+gint
+main (gint argc,
+ gchar ** argv)
+{
+ GtkWidget *window, *box, *button, *hbox, *combo;
+ GtkWidget *w2, *w3;
+ GtkListStore* store;
+ GtkWidget *tree_view;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *renderer;
+ GtkWidget *scrolled_win;
+ int i;
+ GtkTreeIter iter;
+
+ gtk_init (&argc, &argv);
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_widget_set_size_request (window, 300, 300);
+
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_container_add (GTK_CONTAINER (window), box);
+
+ switcher = gtk_stack_switcher_new ();
+ gtk_box_pack_start (GTK_BOX (box), switcher, FALSE, FALSE, 0);
+
+ stack = gtk_stack_new ();
+
+ /* Make transitions longer so we can see that they work */
+ gtk_stack_set_transition_duration (GTK_STACK (stack), 500);
+
+ gtk_widget_set_halign (stack, GTK_ALIGN_START);
+ gtk_container_add (GTK_CONTAINER (box), stack);
+
+ gtk_stack_switcher_set_stack (GTK_STACK_SWITCHER (switcher), GTK_STACK (stack));
+
+ w1 = gtk_text_view_new ();
+ gtk_text_buffer_set_text (gtk_text_view_get_buffer (GTK_TEXT_VIEW (w1)),
+ "This is a\nTest\nBalh!", -1);
+
+ gtk_container_add_with_properties (GTK_CONTAINER (stack), w1,
+ "name", "1",
+ "title", "1",
+ NULL);
+
+ w2 = gtk_button_new_with_label ("Gazoooooooooooooooonk");
+ gtk_container_add (GTK_CONTAINER (stack), w2);
+ gtk_container_child_set (GTK_CONTAINER (stack), w2,
+ "name", "2",
+ "title", "2",
+ NULL);
+
+
+ scrolled_win = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_widget_set_size_request (scrolled_win, 100, 200);
+
+
+ store = gtk_list_store_new (1, G_TYPE_STRING);
+
+ for (i = 0; i < 40; i++)
+ gtk_list_store_insert_with_values (store, &iter, i, 0, "Testvalule", -1);
+
+ tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
+
+ gtk_container_add (GTK_CONTAINER (scrolled_win), tree_view);
+ w3 = scrolled_win;
+
+ renderer = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes ("Target", renderer,
+ "text", 0, NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column);
+
+ gtk_stack_add_titled (GTK_STACK (stack), w3, "3", "3");
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_container_add (GTK_CONTAINER (box), hbox);
+
+ button = gtk_button_new_with_label ("1");
+ gtk_container_add (GTK_CONTAINER (hbox), button);
+ g_signal_connect (button, "clicked", (GCallback) set_visible_child, w1);
+
+ button = gtk_button_new_with_label ("2");
+ gtk_container_add (GTK_CONTAINER (hbox), button);
+ g_signal_connect (button, "clicked", (GCallback) set_visible_child, w2);
+
+ button = gtk_button_new_with_label ("3");
+ gtk_container_add (GTK_CONTAINER (hbox), button);
+ g_signal_connect (button, "clicked", (GCallback) set_visible_child, w3);
+
+ button = gtk_button_new_with_label ("1");
+ gtk_container_add (GTK_CONTAINER (hbox), button);
+ g_signal_connect (button, "clicked", (GCallback) set_visible_child_name, (gpointer) "1");
+
+ button = gtk_button_new_with_label ("2");
+ gtk_container_add (GTK_CONTAINER (hbox), button);
+ g_signal_connect (button, "clicked", (GCallback) set_visible_child_name, (gpointer) "2");
+
+ button = gtk_button_new_with_label ("3");
+ gtk_container_add (GTK_CONTAINER (hbox), button);
+ g_signal_connect (button, "clicked", (GCallback) set_visible_child_name, (gpointer) "3");
+
+ button = gtk_check_button_new_with_label ("homogeneous");
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button),
+ gtk_stack_get_homogeneous (GTK_STACK (stack)));
+ gtk_container_add (GTK_CONTAINER (hbox), button);
+ g_signal_connect (button, "clicked", (GCallback) toggle_homogeneous, NULL);
+
+ button = gtk_toggle_button_new_with_label ("Add icon");
+ g_signal_connect (button, "toggled", (GCallback) toggle_icon_name, NULL);
+ gtk_container_add (GTK_CONTAINER (hbox), button);
+
+ combo = gtk_combo_box_text_new ();
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo),
+ "NONE");
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo),
+ "CROSSFADE");
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo),
+ "SLIDE_RIGHT");
+ gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo),
+ "SLIDE_LEFT");
+ gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
+
+ gtk_container_add (GTK_CONTAINER (hbox), combo);
+ g_signal_connect (combo, "changed", (GCallback) toggle_transitions, NULL);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_container_add (GTK_CONTAINER (box), hbox);
+
+ button = gtk_button_new_with_label ("<");
+ g_signal_connect (button, "clicked", (GCallback) on_back_button_clicked, stack);
+ g_signal_connect (stack, "notify::visible-child-name",
+ (GCallback)update_back_button_sensitivity, button);
+ gtk_container_add (GTK_CONTAINER (hbox), button);
+
+ button = gtk_button_new_with_label (">");
+ gtk_container_add (GTK_CONTAINER (hbox), button);
+ g_signal_connect (button, "clicked", (GCallback) on_forward_button_clicked, stack);
+ g_signal_connect (stack, "notify::visible-child-name",
+ (GCallback)update_forward_button_sensitivity, button);
+
+
+ gtk_widget_show_all (window);
+ gtk_main ();
+
+ gtk_widget_destroy (window);
+
+ return 0;
+}