diff options
author | Matthias Clasen <mclasen@redhat.com> | 2013-04-21 07:51:14 -0400 |
---|---|---|
committer | Matthias Clasen <mclasen@redhat.com> | 2013-04-21 21:51:24 -0400 |
commit | 2e39c4bab8c90f8b36496cb034958738098c7099 (patch) | |
tree | 327ed53d20643264def64bdf71ebbdbf9b824cbe | |
parent | 57c4bcb369891c9035644ec4c84f30ebc241cc9f (diff) | |
download | gtk+-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.am | 4 | ||||
-rw-r--r-- | gtk/gtk.h | 2 | ||||
-rw-r--r-- | gtk/gtkstack.c | 1417 | ||||
-rw-r--r-- | gtk/gtkstack.h | 85 | ||||
-rw-r--r-- | gtk/gtkstackswitcher.c | 413 | ||||
-rw-r--r-- | gtk/gtkstackswitcher.h | 65 | ||||
-rw-r--r-- | tests/Makefile.am | 6 | ||||
-rw-r--r-- | tests/teststack.c | 249 |
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 \ @@ -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; +} |