From 1525f4b2e7ed1fda36837edab3cca0c4c39251f1 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 21 May 2016 16:13:34 -0400 Subject: [PATCH] tab strip: Support different edges Add edge properties on both GtkTabStrip and GtkTab, and make GtkTabStrip update its orientation and scrolling direction when the edge changes. --- gtk/gtktab.c | 43 +++++++++++++ gtk/gtktab.h | 5 ++ gtk/gtktabstrip.c | 159 ++++++++++++++++++++++++++++++++++++++++++---- gtk/gtktabstrip.h | 5 ++ 4 files changed, 199 insertions(+), 13 deletions(-) diff --git a/gtk/gtktab.c b/gtk/gtktab.c index 6a0bf20797..36946f1435 100644 --- a/gtk/gtktab.c +++ b/gtk/gtktab.c @@ -32,6 +32,7 @@ struct _GtkTabPrivate { gchar *title; GtkWidget *widget; + GtkPositionType edge; GtkWidget *child; GtkCssGadget *gadget; @@ -44,6 +45,7 @@ enum { PROP_0, PROP_TITLE, PROP_WIDGET, + PROP_EDGE, N_PROPS }; @@ -74,6 +76,10 @@ gtk_tab_get_property (GObject *object, g_value_set_object (value, gtk_tab_get_widget (self)); break; + case PROP_EDGE: + g_value_set_enum (value, gtk_tab_get_edge (self)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -97,6 +103,10 @@ gtk_tab_set_property (GObject *object, gtk_tab_set_widget (self, g_value_get_object (value)); break; + case PROP_EDGE: + gtk_tab_set_edge (self, g_value_get_enum (value)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -423,6 +433,12 @@ gtk_tab_class_init (GtkTabClass *klass) GTK_TYPE_WIDGET, GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + properties [PROP_EDGE] = + g_param_spec_enum ("edge", P_("Edge"), P_("The edge for the tab-strip"), + GTK_TYPE_POSITION_TYPE, + GTK_POS_TOP, + GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + g_object_class_install_properties (object_class, N_PROPS, properties); signals[ACTIVATE] = @@ -450,6 +466,8 @@ gtk_tab_init (GtkTab *self) widget_node = gtk_widget_get_css_node (GTK_WIDGET (self)); priv->gadget = gtk_box_gadget_new_for_node (widget_node, GTK_WIDGET (self)); gtk_box_gadget_set_draw_focus (GTK_BOX_GADGET (priv->gadget), TRUE); + + priv->edge = GTK_POS_TOP; } const gchar * @@ -517,3 +535,28 @@ gtk_tab_set_child (GtkTab *self, gtk_tab_add (GTK_CONTAINER (self), child); } + +GtkPositionType +gtk_tab_get_edge (GtkTab *self) +{ + GtkTabPrivate *priv = gtk_tab_get_instance_private (self); + + g_return_val_if_fail (GTK_IS_TAB (self), GTK_POS_TOP); + + return priv->edge; +} + +void +gtk_tab_set_edge (GtkTab *self, + GtkPositionType edge) +{ + GtkTabPrivate *priv = gtk_tab_get_instance_private (self); + + g_return_if_fail (GTK_IS_TAB (self)); + + if (priv->edge == edge) + return; + + priv->edge = edge; + g_object_notify (G_OBJECT (self), "edge"); +} diff --git a/gtk/gtktab.h b/gtk/gtktab.h index af5741e329..1c1d18a32e 100644 --- a/gtk/gtktab.h +++ b/gtk/gtktab.h @@ -62,6 +62,11 @@ GDK_AVAILABLE_IN_3_22 void gtk_tab_set_title (GtkTab *self, const gchar *title); GDK_AVAILABLE_IN_3_22 +GtkPositionType gtk_tab_get_edge (GtkTab *self); +GDK_AVAILABLE_IN_3_22 +void gtk_tab_set_edge (GtkTab *self, + GtkPositionType edge); +GDK_AVAILABLE_IN_3_22 GtkWidget *gtk_tab_get_widget (GtkTab *self); GDK_AVAILABLE_IN_3_22 void gtk_tab_set_widget (GtkTab *self, diff --git a/gtk/gtktabstrip.c b/gtk/gtktabstrip.c index 67c16b6ef5..25030f904a 100644 --- a/gtk/gtktabstrip.c +++ b/gtk/gtktabstrip.c @@ -33,12 +33,12 @@ * TODO: * - reordering * - dnd - * - other edges */ typedef struct { GtkStack *stack; + GtkPositionType edge; gboolean closable; gboolean scrollable; gboolean in_child_changed; @@ -55,6 +55,7 @@ G_DEFINE_TYPE_WITH_PRIVATE (GtkTabStrip, gtk_tab_strip, GTK_TYPE_BOX) enum { PROP_0, PROP_STACK, + PROP_EDGE, PROP_CLOSABLE, PROP_SCROLLABLE, N_PROPS @@ -103,6 +104,10 @@ gtk_tab_strip_get_property (GObject *object, g_value_set_object (value, gtk_tab_strip_get_stack (self)); break; + case PROP_EDGE: + g_value_set_enum (value, gtk_tab_strip_get_edge (self)); + break; + case PROP_CLOSABLE: g_value_set_boolean (value, gtk_tab_strip_get_closable (self)); break; @@ -130,6 +135,10 @@ gtk_tab_strip_set_property (GObject *object, gtk_tab_strip_set_stack (self, g_value_get_object (value)); break; + case PROP_EDGE: + gtk_tab_strip_set_edge (self, g_value_get_enum (value)); + break; + case PROP_CLOSABLE: gtk_tab_strip_set_closable (self, g_value_get_boolean (value)); break; @@ -177,6 +186,12 @@ gtk_tab_strip_class_init (GtkTabStripClass *klass) GTK_TYPE_STACK, GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + properties[PROP_EDGE] = + g_param_spec_enum ("edge", P_("Edge"), P_("The edge for the tab-strip"), + GTK_TYPE_POSITION_TYPE, + GTK_POS_TOP, + GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + properties[PROP_CLOSABLE] = g_param_spec_boolean ("closable", P_("Closable"), P_("Whether tabs can be closed"), FALSE, @@ -191,6 +206,25 @@ gtk_tab_strip_class_init (GtkTabStripClass *klass) gtk_widget_class_set_css_name (widget_class, "tabs"); } +static void +update_scrolling (GtkTabStrip *self) +{ + GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self); + GtkPolicyType hscroll = GTK_POLICY_NEVER; + GtkPolicyType vscroll = GTK_POLICY_NEVER; + + if (priv->scrollable) + { + if (gtk_orientable_get_orientation (GTK_ORIENTABLE (self)) == GTK_ORIENTATION_HORIZONTAL) + hscroll = GTK_POLICY_EXTERNAL; + else + vscroll = GTK_POLICY_EXTERNAL; + } + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolledwindow), + hscroll, vscroll); +} + static gboolean autoscroll_cb (GtkWidget *widget, GdkFrameClock *frame_clock, @@ -201,7 +235,10 @@ autoscroll_cb (GtkWidget *widget, GtkAdjustment *adj; gdouble value; - adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (priv->scrolledwindow)); + if (gtk_orientable_get_orientation (GTK_ORIENTABLE (self)) == GTK_ORIENTATION_HORIZONTAL) + adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (priv->scrolledwindow)); + else + adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (priv->scrolledwindow)); value = gtk_adjustment_get_value (adj); if (priv->autoscroll_mode == GTK_SCROLL_STEP_FORWARD) @@ -265,7 +302,10 @@ scroll_button_activate (GtkWidget *button, GtkAdjustment *adj; gdouble value; - adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (priv->scrolledwindow)); + if (gtk_orientable_get_orientation (GTK_ORIENTABLE (self)) == GTK_ORIENTATION_HORIZONTAL) + adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (priv->scrolledwindow)); + else + adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (priv->scrolledwindow)); value = gtk_adjustment_get_value (adj); if (priv->start_scroll == button) @@ -277,13 +317,18 @@ scroll_button_activate (GtkWidget *button, } static void -adjustment_changed (GtkAdjustment *adj, - GtkTabStrip *self) +adjustment_changed (GtkTabStrip *self) { GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self); + GtkAdjustment *adj; gdouble value; gboolean at_lower, at_upper; + if (gtk_orientable_get_orientation (GTK_ORIENTABLE (self)) == GTK_ORIENTATION_HORIZONTAL) + adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (priv->scrolledwindow)); + else + adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (priv->scrolledwindow)); + value = gtk_adjustment_get_value (adj); at_lower = value <= gtk_adjustment_get_lower (adj); @@ -301,6 +346,10 @@ gtk_tab_strip_init (GtkTabStrip *self) GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self); GtkAdjustment *adj; + priv->edge = GTK_POS_TOP; + priv->scrollable = FALSE; + priv->closable = FALSE; + priv->start_scroll = gtk_button_new_from_icon_name ("pan-start-symbolic", GTK_ICON_SIZE_MENU); gtk_button_set_relief (GTK_BUTTON (priv->start_scroll), GTK_RELIEF_NONE); gtk_widget_show (priv->start_scroll); @@ -315,12 +364,11 @@ gtk_tab_strip_init (GtkTabStrip *self) G_CALLBACK (scroll_button_activate), self); priv->scrolledwindow = gtk_scrolled_window_new (NULL, NULL); - gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolledwindow), - GTK_POLICY_NEVER, - GTK_POLICY_NEVER); gtk_widget_show (priv->scrolledwindow); gtk_box_pack_start (GTK_BOX (self), priv->scrolledwindow, TRUE, TRUE, 0); + update_scrolling (self); + priv->end_scroll = gtk_button_new_from_icon_name ("pan-end-symbolic", GTK_ICON_SIZE_MENU); gtk_button_set_relief (GTK_BUTTON (priv->end_scroll), GTK_RELIEF_NONE); gtk_widget_show (priv->end_scroll); @@ -335,8 +383,13 @@ gtk_tab_strip_init (GtkTabStrip *self) G_CALLBACK (scroll_button_activate), self); adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (priv->scrolledwindow)); - g_signal_connect (adj, "changed", G_CALLBACK (adjustment_changed), self); - g_signal_connect (adj, "value-changed", G_CALLBACK (adjustment_changed), self); + g_signal_connect_swapped (adj, "changed", G_CALLBACK (adjustment_changed), self); + g_signal_connect_swapped (adj, "value-changed", G_CALLBACK (adjustment_changed), self); + + adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (priv->scrolledwindow)); + g_signal_connect_swapped (adj, "changed", G_CALLBACK (adjustment_changed), self); + g_signal_connect_swapped (adj, "value-changed", G_CALLBACK (adjustment_changed), self); + priv->tabs = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_widget_show (priv->tabs); @@ -460,6 +513,8 @@ gtk_tab_strip_stack_add (GtkTabStrip *self, g_signal_emit (self, signals[CREATE_TAB], 0, widget, &tab); + gtk_tab_set_edge (tab, priv->edge); + g_object_set_data (G_OBJECT (widget), "GTK_TAB", tab); g_signal_connect (tab, "activate", @@ -581,6 +636,86 @@ gtk_tab_strip_set_stack (GtkTabStrip *self, g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STACK]); } +GtkPositionType +gtk_tab_strip_get_edge (GtkTabStrip *self) +{ + GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self); + + g_return_val_if_fail (GTK_IS_TAB_STRIP (self), GTK_POS_TOP); + + return priv->edge; +} + +static void +update_edge (GtkWidget *widget, + gpointer data) +{ + GtkTabStrip *self = data; + GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self); + + if (GTK_IS_TAB (widget)) + gtk_tab_set_edge (GTK_TAB (widget), priv->edge); +} + +void +gtk_tab_strip_set_edge (GtkTabStrip *self, + GtkPositionType edge) +{ + GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self); + GtkStyleContext *context; + GtkOrientation orientation; + GtkWidget *image; + const char *classes[] = { + "left", + "right", + "top", + "bottom" + }; + GtkOrientation orientations[] = { + GTK_ORIENTATION_VERTICAL, + GTK_ORIENTATION_VERTICAL, + GTK_ORIENTATION_HORIZONTAL, + GTK_ORIENTATION_HORIZONTAL + }; + const char *start_icon[] = { + "pan-start-symbolic", + "pan-up-symbolic" + }; + const char *end_icon[] = { + "pan-end-symbolic", + "pan-down-symbolic" + }; + + g_return_if_fail (GTK_IS_TAB_STRIP (self)); + + if (priv->edge == edge) + return; + + context = gtk_widget_get_style_context (GTK_WIDGET (self)); + gtk_style_context_remove_class (context, classes[priv->edge]); + + priv->edge = edge; + + gtk_style_context_add_class (context, classes[priv->edge]); + + orientation = orientations[priv->edge]; + gtk_orientable_set_orientation (GTK_ORIENTABLE (self), orientation); + gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->tabs), orientation); + + image = gtk_button_get_image (GTK_BUTTON (priv->start_scroll)); + gtk_image_set_from_icon_name (GTK_IMAGE (image), start_icon[orientation], GTK_ICON_SIZE_MENU); + + image = gtk_button_get_image (GTK_BUTTON (priv->end_scroll)); + gtk_image_set_from_icon_name (GTK_IMAGE (image), end_icon[orientation], GTK_ICON_SIZE_MENU); + + update_scrolling (self); + adjustment_changed (self); + + gtk_container_foreach (GTK_CONTAINER (priv->tabs), update_edge, self); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_EDGE]); +} + void gtk_tab_strip_set_closable (GtkTabStrip *self, gboolean closable) @@ -620,9 +755,7 @@ gtk_tab_strip_set_scrollable (GtkTabStrip *self, priv->scrollable = scrollable; - gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolledwindow), - scrollable ? GTK_POLICY_EXTERNAL : GTK_POLICY_NEVER, - GTK_POLICY_NEVER); + update_scrolling (self); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SCROLLABLE]); } diff --git a/gtk/gtktabstrip.h b/gtk/gtktabstrip.h index ccb4f7cdc0..2a38dd25d5 100644 --- a/gtk/gtktabstrip.h +++ b/gtk/gtktabstrip.h @@ -72,6 +72,11 @@ GDK_AVAILABLE_IN_3_22 void gtk_tab_strip_set_stack (GtkTabStrip *self, GtkStack *stack); GDK_AVAILABLE_IN_3_22 +GtkPositionType gtk_tab_strip_get_edge (GtkTabStrip *self); +GDK_AVAILABLE_IN_3_22 +void gtk_tab_strip_set_edge (GtkTabStrip *self, + GtkPositionType edge); +GDK_AVAILABLE_IN_3_22 gboolean gtk_tab_strip_get_closable (GtkTabStrip *self); GDK_AVAILABLE_IN_3_22 void gtk_tab_strip_set_closable (GtkTabStrip *self,