[gnome-shell/wip/gdm-shell: 6/8] wip: StScrollView: add swipe support



commit 154c19d18ecc246b161a17c63648ffc58df23a63
Author: Ray Strode <rstrode redhat com>
Date:   Wed Jul 6 23:58:04 2011 -0400

    wip: StScrollView: add swipe support
    
    This adds the ability to drag around the scroll view without
    using the scrollbars and the ability to "throw" the thing being
    dragged so it keeps some momentum after release.

 src/st/st-box-layout.c  |   14 +-
 src/st/st-scroll-view.c |  580 ++++++++++++++++++++++++++++++++++++++++++++++-
 src/st/st-scroll-view.h |    4 +
 3 files changed, 592 insertions(+), 6 deletions(-)
---
diff --git a/src/st/st-box-layout.c b/src/st/st-box-layout.c
index 3dbf533..052af44 100644
--- a/src/st/st-box-layout.c
+++ b/src/st/st-box-layout.c
@@ -646,7 +646,9 @@ st_box_layout_allocate (ClutterActor          *actor,
                                 &min_height, &natural_height);
 
 
-  /* update adjustments for scrolling */
+  /* update adjustments for scrolling.
+   * We only update the value if not interpolating
+   */
   if (priv->vadjustment)
     {
       gdouble prev_value;
@@ -659,8 +661,9 @@ st_box_layout_allocate (ClutterActor          *actor,
                     "page-increment", avail_height - avail_height / 6,
                     NULL);
 
-      prev_value = st_adjustment_get_value (priv->vadjustment);
-      st_adjustment_set_value (priv->vadjustment, prev_value);
+      g_object_get (G_OBJECT (priv->vadjustment), "value", &prev_value, NULL);
+      if (prev_value == st_adjustment_get_value (priv->vadjustment))
+        st_adjustment_set_value (priv->vadjustment, prev_value);
     }
 
   if (priv->hadjustment)
@@ -675,8 +678,9 @@ st_box_layout_allocate (ClutterActor          *actor,
                     "page-increment", avail_width - avail_width / 6,
                     NULL);
 
-      prev_value = st_adjustment_get_value (priv->hadjustment);
-      st_adjustment_set_value (priv->hadjustment, prev_value);
+      g_object_get (G_OBJECT (priv->hadjustment), "value", &prev_value, NULL);
+      if (prev_value == st_adjustment_get_value (priv->hadjustment))
+        st_adjustment_set_value (priv->hadjustment, prev_value);
     }
 
   if (avail_height < min_height)
diff --git a/src/st/st-scroll-view.c b/src/st/st-scroll-view.c
index a628690..a69e9f1 100644
--- a/src/st/st-scroll-view.c
+++ b/src/st/st-scroll-view.c
@@ -64,9 +64,12 @@
 #include "st-scrollable.h"
 #include "st-scroll-view-fade.h"
 #include <clutter/clutter.h>
+#include <clutter/x11/clutter-x11.h>
+#include <gdk/gdkx.h>
 #include <math.h>
 
 static void clutter_container_iface_init (ClutterContainerIface *iface);
+static void stop_watching_for_swipes (StScrollView *self);
 
 static ClutterContainerIface *st_scroll_view_parent_iface = NULL;
 
@@ -78,6 +81,26 @@ G_DEFINE_TYPE_WITH_CODE (StScrollView, st_scroll_view, ST_TYPE_BIN,
                                                              ST_TYPE_SCROLL_VIEW, \
                                                              StScrollViewPrivate))
 
+typedef struct
+{
+  ClutterEvent *event;
+  double        x;
+  double        y;
+  double        time_since_start;
+  double        time_since_last_x_motion;
+  double        time_since_last_y_motion;
+  double        movement_since_last_x_motion;
+  double        movement_since_last_y_motion;
+  double        vertical_velocity;
+  double        horizontal_velocity;
+} StSwipeSample;
+
+typedef enum
+{
+  ST_SWIPE_DIRECTION_HORIZONTAL,
+  ST_SWIPE_DIRECTION_VERTICAL
+} StSwipeDirection;
+
 struct _StScrollViewPrivate
 {
   /* a pointer to the child; this is actually stored
@@ -91,6 +114,11 @@ struct _StScrollViewPrivate
   StAdjustment *vadjustment;
   ClutterActor *vscroll;
 
+  double        capture_start_time;
+  double        capture_start_x;
+  double        capture_start_y;
+  GQueue       *recorded_samples_queue;
+
   GtkPolicyType hscrollbar_policy;
   GtkPolicyType vscrollbar_policy;
 
@@ -99,9 +127,13 @@ struct _StScrollViewPrivate
 
   StScrollViewFade *vfade_effect;
 
+  guint         captured_event_signal_id;
+
   gboolean      row_size_set : 1;
   gboolean      column_size_set : 1;
   guint         mouse_scroll : 1;
+  guint         swipe_scroll : 1;
+  guint         drag_in_progress : 1;
   guint         hscrollbar_visible : 1;
   guint         vscrollbar_visible : 1;
 };
@@ -114,6 +146,7 @@ enum {
   PROP_HSCROLLBAR_POLICY,
   PROP_VSCROLLBAR_POLICY,
   PROP_MOUSE_SCROLL,
+  PROP_SWIPE_SCROLL,
 };
 
 static void
@@ -141,6 +174,9 @@ st_scroll_view_get_property (GObject    *object,
     case PROP_MOUSE_SCROLL:
       g_value_set_boolean (value, priv->mouse_scroll);
       break;
+    case PROP_SWIPE_SCROLL:
+      g_value_set_boolean (value, priv->swipe_scroll);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
     }
@@ -200,6 +236,10 @@ st_scroll_view_set_property (GObject      *object,
       st_scroll_view_set_mouse_scrolling (self,
                                           g_value_get_boolean (value));
       break;
+    case PROP_SWIPE_SCROLL:
+      st_scroll_view_set_swipe_scrolling (self,
+                                          g_value_get_boolean (value));
+      break;
     case PROP_HSCROLLBAR_POLICY:
       st_scroll_view_set_policy (self,
                                  g_value_get_enum (value),
@@ -218,7 +258,8 @@ st_scroll_view_set_property (GObject      *object,
 static void
 st_scroll_view_dispose (GObject *object)
 {
-  StScrollViewPrivate *priv = ST_SCROLL_VIEW (object)->priv;
+  StScrollView *self = ST_SCROLL_VIEW (object);
+  StScrollViewPrivate *priv = self->priv;
 
   if (priv->vfade_effect)
     {
@@ -251,6 +292,8 @@ st_scroll_view_dispose (GObject *object)
       priv->vadjustment = NULL;
     }
 
+  stop_watching_for_swipes (self);
+
   G_OBJECT_CLASS (st_scroll_view_parent_class)->dispose (object);
 }
 
@@ -730,6 +773,432 @@ st_scroll_view_scroll_event (ClutterActor       *self,
   return TRUE;
 }
 
+static StSwipeSample *
+st_swipe_sample_new (ClutterEvent *event,
+                     double        time_since_start,
+                     double        x,
+                     double        y,
+                     double        movement_since_last_x_motion,
+                     double        movement_since_last_y_motion)
+{
+  StSwipeSample *swipe_sample;
+
+  swipe_sample = g_slice_new0 (StSwipeSample);
+
+  swipe_sample->event = clutter_event_copy (event);
+  swipe_sample->time_since_start = time_since_start;
+  swipe_sample->x = x;
+  swipe_sample->y = y;
+  swipe_sample->movement_since_last_x_motion = movement_since_last_x_motion;
+  swipe_sample->movement_since_last_y_motion = movement_since_last_y_motion;
+
+  return swipe_sample;
+}
+
+static StSwipeSample *
+record_sample_from_event (StScrollView *self,
+                          ClutterEvent *event)
+{
+  StScrollViewPrivate *priv = self->priv;
+  StSwipeSample *swipe_sample;
+  float x, y, older_x, older_y;
+
+  clutter_event_get_coords (event, &x, &y);
+
+  if (g_queue_get_length (priv->recorded_samples_queue) == 0)
+    {
+      self->priv->capture_start_time = g_get_monotonic_time() / (1.0 * G_USEC_PER_SEC);
+      self->priv->capture_start_x = x;
+      self->priv->capture_start_y = y;
+      older_x = 0;
+      older_y = 0;
+    }
+  else
+    {
+      StSwipeSample *older_sample;
+
+      older_sample = g_queue_peek_tail (priv->recorded_samples_queue);
+      clutter_event_get_coords (older_sample->event, &older_x, &older_y);
+    }
+
+  swipe_sample = st_swipe_sample_new (event,
+                                      g_get_monotonic_time() / (1.0 * G_USEC_PER_SEC) - self->priv->capture_start_time,
+                                      x - self->priv->capture_start_x,
+                                      y - self->priv->capture_start_y,
+                                      x - older_x,
+                                      y - older_y);
+
+  g_queue_push_tail (priv->recorded_samples_queue,
+                     swipe_sample);
+
+  return swipe_sample;
+}
+
+static void
+st_swipe_sample_free (StSwipeSample *swipe_sample)
+{
+    clutter_event_free (swipe_sample->event);
+    g_slice_free (StSwipeSample, swipe_sample);
+}
+
+static void
+replay_events_from_recorded_samples (StScrollView *self)
+{
+  StScrollViewPrivate *priv = self->priv;
+  StSwipeSample *swipe_sample;
+
+  while ((swipe_sample = g_queue_pop_head (priv->recorded_samples_queue)) != NULL)
+    {
+      ClutterEvent *event;
+      ClutterActor *actor;
+      float x, y;
+
+      event = swipe_sample->event;
+      clutter_event_get_coords (event, &x, &y);
+
+      actor = clutter_stage_get_actor_at_pos (clutter_event_get_stage (event),
+                                              CLUTTER_PICK_REACTIVE,
+                                              x, y);
+      if (actor != NULL)
+        clutter_actor_event (actor, event, FALSE);
+    }
+}
+
+static void
+analyze_recorded_samples (StScrollView     *self,
+                          StSwipeDirection  direction,
+                          double           *stroke_length,
+                          double           *average_velocity)
+{
+  StScrollViewPrivate *priv = self->priv;
+  StSwipeSample *sample;
+  gboolean first_time, done;
+  float position;
+  GList *node;
+
+  node = g_queue_peek_tail_link (priv->recorded_samples_queue);
+  sample = (StSwipeSample *) node->data;
+
+  if (direction == ST_SWIPE_DIRECTION_HORIZONTAL)
+    position = sample->x;
+  else
+    position = sample->y;
+
+  *average_velocity = 0;
+  node = node->prev;
+  first_time = TRUE;
+  done = FALSE;
+  while (node != NULL && !done)
+    {
+       float older_position;
+       float dt;
+       float du;
+       float velocity;
+
+       StSwipeSample *older_sample = (StSwipeSample *) node->data;
+
+       dt = (float) (sample->time_since_start - older_sample->time_since_start);
+
+       /* Compress nearby events together
+        */
+       if (ABS (dt) < (1 / 60.) && node->prev != NULL)
+         {
+           node = node->prev;
+           continue;
+         }
+
+       if (direction == ST_SWIPE_DIRECTION_HORIZONTAL)
+         {
+           older_position = older_sample->x;
+           sample->time_since_last_x_motion = dt;
+         }
+       else
+         {
+           older_position = older_sample->y;
+           sample->time_since_last_y_motion = dt;
+         }
+
+       du = position - older_position;
+
+       *stroke_length += ABS (du);
+
+       velocity = du / dt;
+
+       /* Only read samples up until the user switches directions
+        * or there are no samples left
+        */
+       if (first_time)
+         {
+           *average_velocity = velocity;
+         }
+       else if (*average_velocity * velocity < 0 || node->prev == NULL)
+         {
+           velocity = 0;
+           done = TRUE;
+         }
+       else
+         {
+           *average_velocity = (*average_velocity + velocity) / 2;
+         }
+
+       if (direction == ST_SWIPE_DIRECTION_HORIZONTAL)
+         sample->horizontal_velocity = velocity;
+       else
+         sample->vertical_velocity = velocity;
+
+       first_time = FALSE;
+       sample = older_sample;
+       position = older_position;
+       node = node->prev;
+    }
+}
+
+static void
+discard_recorded_samples (StScrollView *self)
+{
+  StScrollViewPrivate *priv = self->priv;
+
+  g_queue_foreach (priv->recorded_samples_queue,
+                   (GFunc) st_swipe_sample_free,
+                   NULL);
+  g_queue_clear (priv->recorded_samples_queue);
+}
+
+static gboolean
+handle_captured_button_press_event (StScrollView *self,
+                                    ClutterEvent *event)
+{
+  StScrollViewPrivate *priv = self->priv;
+  gfloat x, y;
+  gdouble value;
+  ClutterActorBox box;
+
+  if (!priv->swipe_scroll || clutter_event_get_button (event) != 1 ||
+      !(priv->hscrollbar_visible || priv->vscrollbar_visible))
+    return FALSE;
+
+  if (priv->drag_in_progress)
+    return FALSE;
+
+  clutter_event_get_coords (event, &x, &y);
+  clutter_actor_get_paint_box (CLUTTER_ACTOR (self), &box);
+
+  if (!clutter_actor_box_contains (&box, x, y))
+    return FALSE;
+
+  clutter_actor_get_paint_box (CLUTTER_ACTOR (priv->hscroll), &box);
+
+  if (clutter_actor_box_contains (&box, x, y))
+    return FALSE;
+
+  clutter_actor_get_paint_box (CLUTTER_ACTOR (priv->vscroll), &box);
+
+  if (clutter_actor_box_contains (&box, x, y))
+    return FALSE;
+
+  record_sample_from_event (self, event);
+
+  priv->drag_in_progress = TRUE;
+
+  /* catch and stop any motion from a previous swipe
+   */
+  g_object_get (priv->hadjustment, "value", &value, NULL);
+  st_adjustment_set_value (priv->hadjustment, value);
+  st_adjustment_clamp (priv->hadjustment, FALSE, 0);
+
+  g_object_get (priv->vadjustment, "value", &value, NULL);
+  st_adjustment_set_value (priv->vadjustment, value);
+  st_adjustment_clamp (priv->vadjustment, FALSE, 0);
+
+  return TRUE;
+}
+
+static void
+throw_child (StScrollView     *self,
+             StSwipeDirection  direction,
+             double            velocity,
+             double            swipe_time)
+{
+  StAdjustment *adjustment;
+  StScrollViewPrivate *priv = self->priv;
+  double value, lower, upper, step_increment, page_size;
+  double distance, air_time;
+
+  if (direction == ST_SWIPE_DIRECTION_HORIZONTAL)
+    adjustment = priv->hadjustment;
+  else
+    adjustment = priv->vadjustment;
+
+  g_object_get (adjustment,
+                "value", &value,
+                "lower", &lower,
+                "upper", &upper,
+                "step-increment", &step_increment,
+                "page-size", &page_size,
+                NULL);
+
+  /* air time sort of arbitrarily chosen
+   */
+  air_time = 1.0 + swipe_time;
+
+  /* If the user released the button while moving at a fast
+   * velocity (faster than a step a second) then assume
+   * they're throwing what they were dragging,
+   * and send it flying.
+   */
+  distance = velocity * air_time;
+  if (ABS (velocity) >= step_increment)
+    {
+      ClutterAlpha *alpha;
+      double new_value;
+
+      new_value = value - distance;
+      new_value = CLAMP (new_value, lower, MAX (lower, upper - page_size));
+
+      alpha = clutter_alpha_new ();
+      clutter_alpha_set_mode (alpha, CLUTTER_EASE_OUT_QUAD);
+      st_adjustment_interpolate (adjustment,
+                                 alpha,
+                                 new_value,
+                                 air_time * 1000);
+      g_object_unref (alpha);
+    }
+}
+
+static gboolean
+handle_captured_button_release_event (StScrollView *self,
+                                      ClutterEvent *event)
+{
+  StScrollViewPrivate *priv = self->priv;
+  StSwipeSample *sample;
+  GtkSettings *settings;
+  int threshold;
+
+  double horizontal_stroke_length, average_horizontal_velocity;
+  double vertical_stroke_length, average_vertical_velocity;
+
+  if (!priv->drag_in_progress)
+    return FALSE;
+
+  analyze_recorded_samples (self,
+                            ST_SWIPE_DIRECTION_HORIZONTAL,
+                            &horizontal_stroke_length,
+                            &average_horizontal_velocity);
+  analyze_recorded_samples (self,
+                            ST_SWIPE_DIRECTION_VERTICAL,
+                            &vertical_stroke_length,
+                            &average_vertical_velocity);
+
+  sample = record_sample_from_event (self, event);
+
+  settings = gtk_settings_get_default();
+  g_object_get (G_OBJECT (settings), "gtk-dnd-drag-threshold", &threshold, NULL);
+
+  /* See if the user is clicking or swiping
+   */
+  if (vertical_stroke_length < threshold &&
+      horizontal_stroke_length < threshold)
+    {
+      /* No motion? It's a click! */
+      replay_events_from_recorded_samples (self);
+    }
+  else
+    {
+      throw_child (self,
+                   ST_SWIPE_DIRECTION_HORIZONTAL,
+                   average_horizontal_velocity,
+                   sample->time_since_start);
+      throw_child (self,
+                   ST_SWIPE_DIRECTION_VERTICAL,
+                   average_vertical_velocity,
+                   sample->time_since_start);
+    }
+
+  priv->drag_in_progress = FALSE;
+
+  /* We've discarded all enter and leave events, so
+   * inform children that rely on those events to
+   * update their state.
+   */
+  st_widget_sync_hover (ST_WIDGET (self));
+
+  discard_recorded_samples (self);
+
+  return TRUE;
+}
+
+static void
+drag_child (StScrollView     *self,
+            StSwipeDirection  direction,
+            double            delta)
+{
+  StScrollViewPrivate *priv = self->priv;
+  StAdjustment *adjustment;
+  gdouble value;
+
+  if (direction == ST_SWIPE_DIRECTION_HORIZONTAL)
+    adjustment = priv->hadjustment;
+  else
+    adjustment = priv->vadjustment;
+
+  g_object_get (adjustment,
+                "value", &value,
+                NULL);
+
+  st_adjustment_set_value (adjustment, value + delta);
+  st_adjustment_clamp (adjustment, FALSE, 0);
+}
+
+static gboolean
+handle_captured_motion_event (StScrollView *self,
+                              ClutterEvent *event)
+{
+  StSwipeSample *sample;
+  StScrollViewPrivate *priv = self->priv;
+
+  if (!priv->drag_in_progress)
+    return FALSE;
+
+  sample = record_sample_from_event (self, event);
+
+  drag_child (self, ST_SWIPE_DIRECTION_HORIZONTAL, - sample->movement_since_last_x_motion);
+  drag_child (self, ST_SWIPE_DIRECTION_VERTICAL, - sample->movement_since_last_y_motion);
+
+  return TRUE;
+}
+
+static gboolean
+on_captured_event (ClutterActor *stage,
+                   ClutterEvent *event,
+                   gpointer      data)
+{
+  StScrollView *self = ST_SCROLL_VIEW (data);
+  StScrollViewPrivate *priv = self->priv;
+
+  switch (event->type)
+    {
+      case CLUTTER_BUTTON_PRESS:
+        return handle_captured_button_press_event (self, event);
+
+      case CLUTTER_BUTTON_RELEASE:
+        return handle_captured_button_release_event (self, event);
+
+      case CLUTTER_MOTION:
+        return handle_captured_motion_event (self, event);
+
+      /* Block enter/leave events to avoid prelights
+         during swipe scrolling */
+      case CLUTTER_ENTER:
+      case CLUTTER_LEAVE:
+        return priv->drag_in_progress;
+
+      default:
+        break;
+    }
+
+  return FALSE;
+}
+
 static void
 st_scroll_view_class_init (StScrollViewClass *klass)
 {
@@ -795,6 +1264,69 @@ st_scroll_view_class_init (StScrollViewClass *klass)
                                    PROP_MOUSE_SCROLL,
                                    pspec);
 
+  pspec = g_param_spec_boolean ("enable-swipe-scrolling",
+                                "Enable Swipe Scrolling",
+                                "Enable scrolling when dragging the mouse",
+                                TRUE,
+                                G_PARAM_READWRITE);
+  g_object_class_install_property (object_class,
+                                   PROP_SWIPE_SCROLL,
+                                   pspec);
+
+}
+
+static void
+watch_for_swipes (StScrollView *self)
+{
+  ClutterActor *actor = CLUTTER_ACTOR (self);
+  StScrollViewPrivate *priv = self->priv;
+
+ if (priv->captured_event_signal_id <= 0)
+    priv->captured_event_signal_id = g_signal_connect (clutter_actor_get_stage (actor),
+                                                       "captured-event",
+                                                       G_CALLBACK (on_captured_event),
+                                                       self);
+
+  if (priv->recorded_samples_queue == NULL)
+    priv->recorded_samples_queue = g_queue_new ();
+}
+
+static void
+stop_watching_for_swipes (StScrollView *self)
+{
+  ClutterActor *actor = CLUTTER_ACTOR (self);
+  StScrollViewPrivate *priv = self->priv;
+
+  if (priv->captured_event_signal_id > 0)
+    {
+      g_signal_handler_disconnect (clutter_actor_get_stage (actor),
+                                   priv->captured_event_signal_id);
+      priv->captured_event_signal_id = 0;
+    }
+
+  if (priv->recorded_samples_queue != NULL)
+    {
+      discard_recorded_samples (self);
+
+      g_queue_free (priv->recorded_samples_queue);
+      priv->recorded_samples_queue = NULL;
+    }
+
+  priv->capture_start_time = 0;
+}
+
+static void
+on_notify_visible (GObject    *object,
+                   GParamSpec  param_id,
+                   gpointer    data)
+{
+  StScrollView *self = ST_SCROLL_VIEW (object);
+  StScrollViewPrivate *priv = self->priv;
+
+  if (CLUTTER_ACTOR_IS_VISIBLE (CLUTTER_ACTOR (self)) && priv->swipe_scroll)
+    watch_for_swipes (self);
+  else
+    stop_watching_for_swipes (self);
 }
 
 static void
@@ -823,6 +1355,12 @@ st_scroll_view_init (StScrollView *self)
   /* mouse scroll is enabled by default, so we also need to be reactive */
   priv->mouse_scroll = TRUE;
   g_object_set (G_OBJECT (self), "reactive", TRUE, NULL);
+
+  g_signal_connect (G_OBJECT (self), "notify::visible",
+                    G_CALLBACK (on_notify_visible),
+                    NULL);
+
+  priv->recorded_samples_queue = NULL;
 }
 
 static void
@@ -1057,6 +1595,46 @@ st_scroll_view_get_mouse_scrolling (StScrollView *scroll)
   return priv->mouse_scroll;
 }
 
+void
+st_scroll_view_set_swipe_scrolling (StScrollView *scroll,
+                                    gboolean      enabled)
+{
+  StScrollViewPrivate *priv;
+
+  g_return_if_fail (ST_IS_SCROLL_VIEW (scroll));
+
+  priv = ST_SCROLL_VIEW (scroll)->priv;
+
+  if (priv->swipe_scroll != enabled)
+    {
+      priv->swipe_scroll = enabled;
+
+      /* make sure we can receive swipe events */
+      if (enabled)
+        {
+          clutter_actor_set_reactive ((ClutterActor *) scroll, TRUE);
+          if (CLUTTER_ACTOR_IS_VISIBLE ((ClutterActor *) scroll))
+            watch_for_swipes (scroll);
+        }
+      else
+        {
+          stop_watching_for_swipes (scroll);
+        }
+    }
+}
+
+gboolean
+st_scroll_view_get_swipe_scrolling (StScrollView *scroll)
+{
+  StScrollViewPrivate *priv;
+
+  g_return_val_if_fail (ST_IS_SCROLL_VIEW (scroll), FALSE);
+
+  priv = ST_SCROLL_VIEW (scroll)->priv;
+
+  return priv->swipe_scroll;
+}
+
 /**
  * st_scroll_view_set_policy:
  * @scroll: A #StScrollView
diff --git a/src/st/st-scroll-view.h b/src/st/st-scroll-view.h
index c00d16e..ad2d203 100644
--- a/src/st/st-scroll-view.h
+++ b/src/st/st-scroll-view.h
@@ -80,6 +80,10 @@ void          st_scroll_view_set_mouse_scrolling (StScrollView *scroll,
                                                   gboolean      enabled);
 gboolean      st_scroll_view_get_mouse_scrolling (StScrollView *scroll);
 
+void          st_scroll_view_set_swipe_scrolling (StScrollView *scroll,
+                                                  gboolean      enabled);
+gboolean      st_scroll_view_get_swipe_scrolling (StScrollView *scroll);
+
 void          st_scroll_view_set_policy          (StScrollView   *scroll,
                                                   GtkPolicyType   hscroll,
                                                   GtkPolicyType   vscroll);



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