[gtk+/wip/matthiasc/tab-strip: 7/10] tab strip: Add scrolling



commit 76da4b4860379d32b85ad18275f40004f6a7821b
Author: Matthias Clasen <mclasen redhat com>
Date:   Sat May 21 01:15:45 2016 -0400

    tab strip: Add scrolling
    
    Implement scrolling arrows. This is currently using a scrolled window.

 gtk/gtktabstrip.c    |  352 ++++++++++++++++++++++++++------------------------
 gtk/gtktabstrip.h    |    5 +
 tests/testtabstrip.c |    1 +
 3 files changed, 187 insertions(+), 171 deletions(-)
---
diff --git a/gtk/gtktabstrip.c b/gtk/gtktabstrip.c
index 253f7e5..12546d3 100644
--- a/gtk/gtktabstrip.c
+++ b/gtk/gtktabstrip.c
@@ -25,11 +25,13 @@
 #include "gtkintl.h"
 #include "gtkprivate.h"
 #include "gtkorientable.h"
+#include "gtkscrolledwindow.h"
+#include "gtkbutton.h"
+#include "gtkadjustmentprivate.h"
 
 /*
  * TODO:
  * - custom tabs
- * - scrolling
  * - reordering
  * - dnd
  * - other edges
@@ -39,8 +41,14 @@ typedef struct
 {
   GtkStack        *stack;
   gboolean         closable;
+  gboolean         scrollable;
   gboolean         in_child_changed;
-  GdkWindow       *event_window;
+  GtkWidget       *scrolledwindow;
+  GtkWidget       *tabs;
+  GtkWidget       *start_scroll;
+  GtkWidget       *end_scroll;
+  GtkScrollType    autoscroll_mode;
+  guint            autoscroll_id;
 } GtkTabStripPrivate;
 
 G_DEFINE_TYPE_WITH_PRIVATE (GtkTabStrip, gtk_tab_strip, GTK_TYPE_BOX)
@@ -49,6 +57,7 @@ enum {
   PROP_0,
   PROP_STACK,
   PROP_CLOSABLE,
+  PROP_SCROLLABLE,
   N_PROPS
 };
 
@@ -92,6 +101,10 @@ gtk_tab_strip_get_property (GObject    *object,
       g_value_set_boolean (value, gtk_tab_strip_get_closable (self));
       break;
 
+    case PROP_SCROLLABLE:
+      g_value_set_boolean (value, gtk_tab_strip_get_scrollable (self));
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
@@ -115,232 +128,198 @@ gtk_tab_strip_set_property (GObject      *object,
       gtk_tab_strip_set_closable (self, g_value_get_boolean (value));
       break;
 
+    case PROP_SCROLLABLE:
+      gtk_tab_strip_set_scrollable (self, g_value_get_boolean (value));
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
 }
 
-static gboolean
-get_widget_coordinates (GtkWidget *widget,
-                        GdkEvent  *event,
-                        gdouble   *x,
-                        gdouble   *y)
+static void
+gtk_tab_strip_class_init (GtkTabStripClass *klass)
 {
-  GdkWindow *window = ((GdkEventAny *)event)->window;
-  gdouble tx, ty;
-
-  if (!gdk_event_get_coords (event, &tx, &ty))
-    return FALSE;
-
-  while (window && window != gtk_widget_get_window (widget))
-    {
-      gint window_x, window_y;
-
-      gdk_window_get_position (window, &window_x, &window_y);
-      tx += window_x;
-      ty += window_y;
-
-      window = gdk_window_get_parent (window);
-    }
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
 
-  if (window)
-    {
-      *x = tx;
-      *y = ty;
+  object_class->get_property = gtk_tab_strip_get_property;
+  object_class->set_property = gtk_tab_strip_set_property;
 
-      return TRUE;
-    }
-  else
-    return FALSE;
-}
+  widget_class->destroy = gtk_tab_strip_destroy;
 
-static GtkTab *
-get_tab_at_pos (GtkTabStrip *self,
-                gdouble      x,
-                gdouble      y)
-{
-  GtkAllocation allocation;
-  GList *children, *l;
-  GtkTab *tab;
+  container_class->add = gtk_tab_strip_add;
 
-  children = gtk_container_get_children (GTK_CONTAINER (self));
+  properties[PROP_STACK] =
+    g_param_spec_object ("stack", P_("Stack"), P_("The stack of items to manage"),
+                         GTK_TYPE_STACK,
+                         GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
 
-  tab = NULL;
-  for (l = children; l; l = l->next)
-    {
-      gtk_widget_get_allocation (GTK_WIDGET (l->data), &allocation);
-      if ((x >= allocation.x) &&
-          (y >= allocation.y) &&
-          (x <= (allocation.x + allocation.width)) &&
-          (y <= (allocation.y + allocation.height)))
-        {
-          tab = l->data;
-          break;
-        }
-    }
+  properties[PROP_CLOSABLE] =
+    g_param_spec_boolean ("closable", P_("Closable"), P_("Whether tabs can be closed"),
+                          FALSE,
+                          GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
 
-  g_list_free (children);
+  properties[PROP_SCROLLABLE] =
+    g_param_spec_boolean ("scrollable", P_("Scrollable"), P_("Whether tabs can be scrolled"),
+                          FALSE,
+                          GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+  g_object_class_install_properties (object_class, N_PROPS, properties);
 
-  return tab;
+  gtk_widget_class_set_css_name (widget_class, "tabs");
 }
 
 static gboolean
-gtk_tab_strip_button_press (GtkWidget      *widget,
-                            GdkEventButton *event)
+autoscroll_cb (GtkWidget     *widget,
+               GdkFrameClock *frame_clock,
+               gpointer       data)
 {
-  GtkTabStrip *self = GTK_TAB_STRIP (widget);
+  GtkTabStrip *self = data;
   GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
-  GtkTab *tab;
-  GtkWidget *child;
-  gdouble x, y;
-
-  if (!get_widget_coordinates (widget, (GdkEvent *)event, &x, &y))
-    return FALSE;
+  GtkAdjustment *adj;
+  gdouble value;
 
-  if (event->button != GDK_BUTTON_PRIMARY)
-    return FALSE;
+  adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (priv->scrolledwindow));
 
-  tab = get_tab_at_pos (self, x, y);
-
-  if (tab == NULL)
-    return FALSE;
-
-  child = gtk_tab_get_widget (tab);
-  if (child == NULL)
-    return FALSE;
+  value = gtk_adjustment_get_value (adj);
+  if (priv->autoscroll_mode == GTK_SCROLL_STEP_FORWARD)
+    value += 5;
+  else
+    value -= 5;
 
-  gtk_stack_set_visible_child (priv->stack, child);
+  gtk_adjustment_set_value (adj, value);
 
-  return TRUE;
+  return G_SOURCE_CONTINUE;
 }
 
 static void
-gtk_tab_strip_map (GtkWidget *widget)
+add_autoscroll (GtkTabStrip *self,
+                GtkWidget   *button)
 {
-  GtkTabStrip *self = GTK_TAB_STRIP (widget);
   GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
 
-  GTK_WIDGET_CLASS (gtk_tab_strip_parent_class)->map (widget);
-
-  gdk_window_show_unraised (priv->event_window);
-}
-
-static void
-gtk_tab_strip_unmap (GtkWidget *widget)
-{
-  GtkTabStrip *self = GTK_TAB_STRIP (widget);
-  GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
+  if (priv->autoscroll_id != 0)
+    return;
 
-  gdk_window_hide (priv->event_window);
+  if (button == priv->start_scroll)
+    priv->autoscroll_mode = GTK_SCROLL_STEP_BACKWARD;
+  else
+    priv->autoscroll_mode = GTK_SCROLL_STEP_FORWARD;
 
-  GTK_WIDGET_CLASS (gtk_tab_strip_parent_class)->unmap (widget);
+  priv->autoscroll_id = gtk_widget_add_tick_callback (GTK_WIDGET (self),
+                                                      autoscroll_cb, self, NULL);
 }
 
 static void
-gtk_tab_strip_realize (GtkWidget *widget)
+remove_autoscroll (GtkTabStrip *self)
 {
-  GtkTabStrip *self = GTK_TAB_STRIP (widget);
   GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
-  GdkWindow *window;
-  GdkWindowAttr attributes;
-  gint attributes_mask;
-  GtkAllocation allocation;
-
-  gtk_widget_set_realized (widget, TRUE);
-
-  gtk_widget_get_allocation (widget, &allocation);
-
-  window = gtk_widget_get_parent_window (widget);
-  gtk_widget_set_window (widget, window);
-  g_object_ref (window);
-
-  attributes.window_type = GDK_WINDOW_CHILD;
-  attributes.x = allocation.x;
-  attributes.y = allocation.y;
-  attributes.width = allocation.width;
-  attributes.height = allocation.height;
-  attributes.wclass = GDK_INPUT_ONLY;
-  attributes.event_mask = gtk_widget_get_events (widget);
-  attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
-                            GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK |
-                            GDK_POINTER_MOTION_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK);
-  attributes_mask = GDK_WA_X | GDK_WA_Y;
-
-  priv->event_window = gdk_window_new (gtk_widget_get_parent_window (widget),
-                                       &attributes, attributes_mask);
-  gtk_widget_register_window (widget, priv->event_window);
+
+  if (priv->autoscroll_id)
+    {
+      gtk_widget_remove_tick_callback (GTK_WIDGET (self), priv->autoscroll_id);
+      priv->autoscroll_id = 0;
+    }
 }
 
-static void
-gtk_tab_strip_unrealize (GtkWidget *widget)
+static gboolean
+scroll_button_event (GtkWidget      *button,
+                     GdkEventButton *event,
+                     GtkTabStrip    *self)
 {
-  GtkTabStrip *self = GTK_TAB_STRIP (widget);
-  GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
+  remove_autoscroll (self);
 
-  gtk_widget_unregister_window (widget, priv->event_window);
-  gdk_window_destroy (priv->event_window);
-  priv->event_window = NULL;
+  if (event->type == GDK_BUTTON_PRESS)
+    add_autoscroll (self, button);
 
-  GTK_WIDGET_CLASS (gtk_tab_strip_parent_class)->unrealize (widget);
+  return FALSE;
 }
 
 static void
-gtk_tab_strip_size_allocate (GtkWidget     *widget,
-                             GtkAllocation *allocation)
+scroll_button_activate (GtkWidget   *button,
+                        GtkTabStrip *self)
 {
-  GtkTabStrip *self = GTK_TAB_STRIP (widget);
   GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
+  GtkAdjustment *adj;
+  gdouble value;
 
-  GTK_WIDGET_CLASS (gtk_tab_strip_parent_class)->size_allocate (widget, allocation);
+  adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (priv->scrolledwindow));
+  value = gtk_adjustment_get_value (adj);
 
-  if (gtk_widget_get_realized (widget))
-    {
-      gdk_window_move_resize (priv->event_window,
-                              allocation->x, allocation->y,
-                              allocation->width, allocation->height);
-      if (gtk_widget_get_mapped (widget))
-        gdk_window_show_unraised (priv->event_window);
-    }
+  if (priv->start_scroll == button)
+    value -= 20;
+  else
+    value += 20;
+
+  gtk_adjustment_animate_to_value (adj, value);
 }
 
 static void
-gtk_tab_strip_class_init (GtkTabStripClass *klass)
+adjustment_changed (GtkAdjustment *adj,
+                    GtkTabStrip   *self)
 {
-  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_tab_strip_get_property;
-  object_class->set_property = gtk_tab_strip_set_property;
-
-  widget_class->destroy = gtk_tab_strip_destroy;
-  widget_class->map = gtk_tab_strip_map;
-  widget_class->unmap = gtk_tab_strip_unmap;
-  widget_class->realize = gtk_tab_strip_realize;
-  widget_class->unrealize = gtk_tab_strip_unrealize;
-  widget_class->size_allocate = gtk_tab_strip_size_allocate;
-  widget_class->button_press_event = gtk_tab_strip_button_press;
+  GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
+  gdouble value;
+  gboolean at_lower, at_upper;
 
-  container_class->add = gtk_tab_strip_add;
+  value = gtk_adjustment_get_value (adj);
 
-  properties[PROP_STACK] =
-    g_param_spec_object ("stack", P_("Stack"), P_("The stack of items to manage"),
-                         GTK_TYPE_STACK,
-                         GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+  at_lower = value <= gtk_adjustment_get_lower (adj);
+  at_upper = value >= gtk_adjustment_get_upper (adj) - gtk_adjustment_get_page_size (adj);
 
-  properties[PROP_CLOSABLE] =
-    g_param_spec_boolean ("closable", P_("Closable"), P_("Whether tabs can be closed"),
-                          FALSE,
-                          GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
-
-  g_object_class_install_properties (object_class, N_PROPS, properties);
-
-  gtk_widget_class_set_css_name (widget_class, "tabs");
+  gtk_widget_set_visible (priv->start_scroll, !(at_lower && at_upper));
+  gtk_widget_set_visible (priv->end_scroll, !(at_lower && at_upper));
+  gtk_widget_set_sensitive (priv->start_scroll, !at_lower);
+  gtk_widget_set_sensitive (priv->end_scroll, !at_upper);
 }
 
 static void
 gtk_tab_strip_init (GtkTabStrip *self)
 {
+  GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
+  GtkAdjustment *adj;
+
+  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);
+  gtk_widget_set_no_show_all (priv->start_scroll, TRUE);
+  gtk_widget_set_focus_on_click (priv->start_scroll, FALSE);
+  gtk_box_pack_start (GTK_BOX (self), priv->start_scroll, FALSE, FALSE, 0);
+  g_signal_connect (priv->start_scroll, "button-press-event",
+                    G_CALLBACK (scroll_button_event), self);
+  g_signal_connect (priv->start_scroll, "button-release-event",
+                    G_CALLBACK (scroll_button_event), self);
+  g_signal_connect (priv->start_scroll, "activate",
+                    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);
+
+  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);
+  gtk_widget_set_no_show_all (priv->end_scroll, TRUE);
+  gtk_widget_set_focus_on_click (priv->end_scroll, FALSE);
+  gtk_box_pack_start (GTK_BOX (self), priv->end_scroll, FALSE, FALSE, 0);
+  g_signal_connect (priv->end_scroll, "button-press-event",
+                    G_CALLBACK (scroll_button_event), self);
+  g_signal_connect (priv->end_scroll, "button-release-event",
+                    G_CALLBACK (scroll_button_event), self);
+  g_signal_connect (priv->end_scroll, "activate",
+                    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);
+
+  priv->tabs = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+  gtk_widget_show (priv->tabs);
+  gtk_container_add (GTK_CONTAINER (priv->scrolledwindow), priv->tabs);
 }
 
 static void
@@ -348,6 +327,7 @@ gtk_tab_strip_child_position_changed (GtkTabStrip *self,
                                       GParamSpec  *pspec,
                                       GtkWidget   *child)
 {
+  GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
   GtkWidget *parent;
   GtkTab *tab;
   guint position;
@@ -363,7 +343,7 @@ gtk_tab_strip_child_position_changed (GtkTabStrip *self,
                            "position", &position,
                            NULL);
 
-  gtk_container_child_set (GTK_CONTAINER (self), GTK_WIDGET (tab),
+  gtk_container_child_set (GTK_CONTAINER (priv->tabs), GTK_WIDGET (tab),
                            "position", position,
                            NULL);
 }
@@ -463,7 +443,7 @@ gtk_tab_strip_stack_add (GtkTabStrip *self,
                            G_CALLBACK (gtk_tab_strip_child_title_changed), self,
                            G_CONNECT_SWAPPED);
 
-  gtk_box_pack_start (GTK_BOX (self), GTK_WIDGET (tab), TRUE, TRUE, 0);
+  gtk_box_pack_start (GTK_BOX (priv->tabs), GTK_WIDGET (tab), TRUE, TRUE, 0);
 
   g_object_bind_property (widget, "visible", tab, "visible", G_BINDING_SYNC_CREATE);
 
@@ -596,3 +576,33 @@ gtk_tab_strip_get_closable (GtkTabStrip *self)
 
   return priv->closable;
 }
+
+void
+gtk_tab_strip_set_scrollable (GtkTabStrip *self,
+                              gboolean     scrollable)
+{
+  GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
+
+  g_return_if_fail (GTK_IS_TAB_STRIP (self));
+
+  if (priv->scrollable == scrollable)
+    return;
+
+  priv->scrollable = scrollable;
+
+  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolledwindow),
+                                  scrollable ? GTK_POLICY_EXTERNAL : GTK_POLICY_NEVER,
+                                  GTK_POLICY_NEVER);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SCROLLABLE]);
+}
+
+gboolean
+gtk_tab_strip_get_scrollable (GtkTabStrip *self)
+{
+  GtkTabStripPrivate *priv = gtk_tab_strip_get_instance_private (self);
+
+  g_return_val_if_fail (GTK_IS_TAB_STRIP (self), FALSE);
+
+  return priv->scrollable;
+}
diff --git a/gtk/gtktabstrip.h b/gtk/gtktabstrip.h
index 6f41f18..ec63cc8 100644
--- a/gtk/gtktabstrip.h
+++ b/gtk/gtktabstrip.h
@@ -64,6 +64,11 @@ gboolean         gtk_tab_strip_get_closable    (GtkTabStrip     *self);
 GDK_AVAILABLE_IN_3_22
 void             gtk_tab_strip_set_closable    (GtkTabStrip     *self,
                                                 gboolean         closable);
+GDK_AVAILABLE_IN_3_22
+gboolean         gtk_tab_strip_get_scrollable  (GtkTabStrip     *self);
+GDK_AVAILABLE_IN_3_22
+void             gtk_tab_strip_set_scrollable  (GtkTabStrip     *self,
+                                                gboolean         scrollable);
 
 G_END_DECLS
 
diff --git a/tests/testtabstrip.c b/tests/testtabstrip.c
index f54a64a..a4b6c31 100644
--- a/tests/testtabstrip.c
+++ b/tests/testtabstrip.c
@@ -49,6 +49,7 @@ main (int argc, char *argv[])
 
   tabs = gtk_tab_strip_new ();
   gtk_tab_strip_set_closable (GTK_TAB_STRIP (tabs), TRUE);
+  gtk_tab_strip_set_scrollable (GTK_TAB_STRIP (tabs), TRUE);
   gtk_tab_strip_set_stack (GTK_TAB_STRIP (tabs), GTK_STACK (stack));
 
   box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);


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