[sysprof/wip/visualizers] multi-paned: add SpMultiPaned



commit f9e19131b5d92a61a38929e52ae8fa9c2e2e6ff0
Author: Christian Hergert <chergert redhat com>
Date:   Fri Sep 30 12:59:09 2016 -0700

    multi-paned: add SpMultiPaned
    
    This is copy/paste from PnlMultiPaned. The feature we want from
    it is the ability to resize paned halves based on their natural
    sizing up until the user has manually changed the paned handle
    position.

 lib/Makefile.am      |    2 +
 lib/sp-multi-paned.c | 1905 ++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/sp-multi-paned.h |   55 ++
 3 files changed, 1962 insertions(+), 0 deletions(-)
---
diff --git a/lib/Makefile.am b/lib/Makefile.am
index cea9fde..c894558 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -144,6 +144,7 @@ uiheaders_DATA = \
        sp-failed-state-view.h \
        sp-line-visualizer-row.h \
        sp-model-filter.h \
+       sp-multi-paned.h \
        sp-process-model.h \
        sp-process-model-item.h \
        sp-process-model-row.h \
@@ -168,6 +169,7 @@ libsysprof_ui_@API_VERSION@_la_SOURCES = \
        sp-failed-state-view.c \
        sp-line-visualizer-row.c \
        sp-model-filter.c \
+       sp-multi-paned.c \
        sp-process-model.c \
        sp-process-model-item.c \
        sp-process-model-row.c \
diff --git a/lib/sp-multi-paned.c b/lib/sp-multi-paned.c
new file mode 100644
index 0000000..233850e
--- /dev/null
+++ b/lib/sp-multi-paned.c
@@ -0,0 +1,1905 @@
+/* sp-multi-paned.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * 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 "sp-multi-paned.h"
+
+#define HANDLE_WIDTH  10
+#define HANDLE_HEIGHT 10
+
+#define IS_HORIZONTAL(o) (o == GTK_ORIENTATION_HORIZONTAL)
+
+/**
+ * SECTION:sp-multi-paned
+ * @title: SpMultiPaned
+ * @short_description: A widget with multiple adjustable panes
+ *
+ * This widget is similar to #GtkPaned except that it allows adding more than
+ * two children to the widget. For each additional child added to the
+ * #SpMultiPaned, an additional resize grip is added.
+ */
+
+typedef struct
+{
+  /*
+   * The child widget in question.
+   */
+  GtkWidget *widget;
+
+  /*
+   * The input only window for resize grip.
+   * Has a cursor associated with it.
+   */
+  GdkWindow *handle;
+
+  /*
+   * The position the handle has been dragged to.
+   * This is used to adjust size requests.
+   */
+  gint position;
+
+  /*
+   * Cached size requests to avoid extra sizing calls during
+   * the layout procedure.
+   */
+  GtkRequisition min_req;
+  GtkRequisition nat_req;
+
+  /*
+   * A cached size allocation used during the size_allocate()
+   * cycle. This allows us to do a first pass to allocate
+   * natural sizes, and then followup when dealing with
+   * expanding children.
+   */
+  GtkAllocation alloc;
+
+  /*
+   * If the position field has been set.
+   */
+  guint position_set : 1;
+} SpMultiPanedChild;
+
+typedef struct
+{
+  /*
+   * A GArray of SpMultiPanedChild containing everything we need to
+   * do size requests, drag operations, resize handles, and temporary
+   * space needed in such operations.
+   */
+  GArray *children;
+
+  /*
+   * The gesture used for dragging resize handles.
+   *
+   * TODO: GtkPaned now uses two gestures, one for mouse and one for touch.
+   *       We should do the same as it improved things quite a bit.
+   */
+  GtkGesturePan *gesture;
+
+  /*
+   * For GtkOrientable:orientation.
+   */
+  GtkOrientation orientation;
+
+  /*
+   * This is the child that is currently being dragged. Keep in mind that
+   * the drag handle is immediately after the child. So the final visible
+   * child has the handle input-only window hidden.
+   */
+  SpMultiPanedChild *drag_begin;
+
+  /*
+   * The position (width or height) of the child when the drag began.
+   * We use the pan delta offset to determine what the size should be
+   * by adding (or subtracting) to this value.
+   */
+  gint drag_begin_position;
+
+  /*
+   * If we are dragging a handle in a fashion that would shrink the
+   * previous widgets, we need to track how much to subtract from their
+   * target allocations. This is set during the drag operation and used
+   * in allocation_stage_drag_overflow() to adjust the neighbors.
+   */
+  gint drag_extra_offset;
+} SpMultiPanedPrivate;
+
+typedef struct
+{
+  SpMultiPanedChild **children;
+  guint                n_children;
+  GtkOrientation       orientation;
+  GtkAllocation        top_alloc;
+  gint                 avail_width;
+  gint                 avail_height;
+  gint                 handle_size;
+} AllocationState;
+
+typedef void (*AllocationStage) (SpMultiPaned   *self,
+                                 AllocationState *state);
+
+static void allocation_stage_allocate      (SpMultiPaned   *self,
+                                            AllocationState *state);
+static void allocation_stage_borders       (SpMultiPaned   *self,
+                                            AllocationState *state);
+static void allocation_stage_cache_request (SpMultiPaned   *self,
+                                            AllocationState *state);
+static void allocation_stage_drag_overflow (SpMultiPaned   *self,
+                                            AllocationState *state);
+static void allocation_stage_expand        (SpMultiPaned   *self,
+                                            AllocationState *state);
+static void allocation_stage_handles       (SpMultiPaned   *self,
+                                            AllocationState *state);
+static void allocation_stage_minimums      (SpMultiPaned   *self,
+                                            AllocationState *state);
+static void allocation_stage_naturals      (SpMultiPaned   *self,
+                                            AllocationState *state);
+static void allocation_stage_positions     (SpMultiPaned   *self,
+                                            AllocationState *state);
+
+G_DEFINE_TYPE_EXTENDED (SpMultiPaned, sp_multi_paned, GTK_TYPE_CONTAINER, 0,
+                        G_ADD_PRIVATE (SpMultiPaned)
+                        G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL))
+
+enum {
+  PROP_0,
+  PROP_ORIENTATION,
+  N_PROPS
+};
+
+enum {
+  CHILD_PROP_0,
+  CHILD_PROP_POSITION,
+  N_CHILD_PROPS
+};
+
+enum {
+  STYLE_PROP_0,
+  STYLE_PROP_HANDLE_SIZE,
+  N_STYLE_PROPS
+};
+
+enum {
+  RESIZE_DRAG_BEGIN,
+  RESIZE_DRAG_END,
+  N_SIGNALS
+};
+
+/*
+ * TODO: An obvious optimization here would be to move the constant
+ *       branches outside the loops.
+ */
+
+static GParamSpec *properties [N_PROPS];
+static GParamSpec *child_properties [N_CHILD_PROPS];
+static GParamSpec *style_properties [N_STYLE_PROPS];
+static guint signals [N_SIGNALS];
+static AllocationStage allocation_stages[] = {
+  allocation_stage_borders,
+  allocation_stage_cache_request,
+  allocation_stage_minimums,
+  allocation_stage_handles,
+  allocation_stage_positions,
+  allocation_stage_drag_overflow,
+  allocation_stage_naturals,
+  allocation_stage_expand,
+  allocation_stage_allocate,
+};
+
+static void
+sp_multi_paned_reset_positions (SpMultiPaned *self)
+{
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+  guint i;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+
+  for (i = 0; i < priv->children->len; i++)
+    {
+      SpMultiPanedChild *child = &g_array_index (priv->children, SpMultiPanedChild, i);
+
+      child->position = -1;
+      child->position_set = FALSE;
+
+      gtk_container_child_notify_by_pspec (GTK_CONTAINER (self),
+                                           child->widget,
+                                           child_properties [CHILD_PROP_POSITION]);
+    }
+
+  gtk_widget_queue_resize (GTK_WIDGET (self));
+}
+
+static SpMultiPanedChild *
+sp_multi_paned_get_next_visible_child (SpMultiPaned      *self,
+                                        SpMultiPanedChild *child)
+{
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+  guint i;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (child != NULL);
+  g_assert (priv->children != NULL);
+  g_assert (priv->children->len > 0);
+
+  i = child - ((SpMultiPanedChild *)(gpointer)priv->children->data);
+
+  for (++i; i < priv->children->len; i++)
+    {
+      SpMultiPanedChild *next = &g_array_index (priv->children, SpMultiPanedChild, i);
+
+      if (gtk_widget_get_visible (next->widget))
+        return next;
+    }
+
+  return NULL;
+}
+
+static gboolean
+sp_multi_paned_is_last_visible_child (SpMultiPaned      *self,
+                                       SpMultiPanedChild *child)
+{
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (child != NULL);
+
+  return !sp_multi_paned_get_next_visible_child (self, child);
+}
+
+static void
+sp_multi_paned_get_handle_rect (SpMultiPaned      *self,
+                                 SpMultiPanedChild *child,
+                                 GdkRectangle       *handle_rect)
+{
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+  GtkAllocation alloc;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (child != NULL);
+  g_assert (handle_rect != NULL);
+
+  handle_rect->x = -1;
+  handle_rect->y = -1;
+  handle_rect->width = 0;
+  handle_rect->height = 0;
+
+  if (!gtk_widget_get_visible (child->widget) ||
+      !gtk_widget_get_realized (child->widget))
+    return;
+
+  if (sp_multi_paned_is_last_visible_child (self, child))
+    return;
+
+  gtk_widget_get_allocation (child->widget, &alloc);
+
+  if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+    {
+      handle_rect->x = alloc.x + alloc.width - (HANDLE_WIDTH / 2);
+      handle_rect->width = HANDLE_WIDTH;
+      handle_rect->y = alloc.y;
+      handle_rect->height = alloc.height;
+    }
+  else
+    {
+      handle_rect->x = alloc.x;
+      handle_rect->width = alloc.width;
+      handle_rect->y = alloc.y + alloc.height - (HANDLE_HEIGHT / 2);
+      handle_rect->height = HANDLE_HEIGHT;
+    }
+}
+
+static void
+sp_multi_paned_create_child_handle (SpMultiPaned      *self,
+                                     SpMultiPanedChild *child)
+{
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+  GdkWindowAttr attributes = { 0 };
+  GdkDisplay *display;
+  GdkWindow *parent;
+  const char *cursor_name;
+  GdkRectangle handle_rect;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (child != NULL);
+  g_assert (child->handle == NULL);
+
+  display = gtk_widget_get_display (GTK_WIDGET (self));
+  parent = gtk_widget_get_window (GTK_WIDGET (self));
+
+  cursor_name = (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+                ? "col-resize"
+                : "row-resize";
+
+  sp_multi_paned_get_handle_rect (self, child, &handle_rect);
+
+  attributes.window_type = GDK_WINDOW_CHILD;
+  attributes.wclass = GDK_INPUT_ONLY;
+  attributes.x = handle_rect.x;
+  attributes.y = -handle_rect.y;
+  attributes.width = handle_rect.width;
+  attributes.height = handle_rect.height;
+  attributes.visual = gtk_widget_get_visual (GTK_WIDGET (self));
+  attributes.event_mask = (GDK_BUTTON_PRESS_MASK |
+                           GDK_BUTTON_RELEASE_MASK |
+                           GDK_ENTER_NOTIFY_MASK |
+                           GDK_LEAVE_NOTIFY_MASK |
+                           GDK_POINTER_MOTION_MASK);
+  attributes.cursor = gdk_cursor_new_from_name (display, cursor_name);
+
+  child->handle = gdk_window_new (parent, &attributes, GDK_WA_CURSOR);
+  gtk_widget_register_window (GTK_WIDGET (self), child->handle);
+
+  g_clear_object (&attributes.cursor);
+}
+
+static gint
+sp_multi_paned_calc_handle_size (SpMultiPaned *self)
+{
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+  gint visible_children = 0;
+  gint handle_size = 1;
+  guint i;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+
+  gtk_widget_style_get (GTK_WIDGET (self), "handle-size", &handle_size, NULL);
+
+  for (i = 0; i < priv->children->len; i++)
+    {
+      SpMultiPanedChild *child = &g_array_index (priv->children, SpMultiPanedChild, i);
+
+      if (gtk_widget_get_visible (child->widget))
+        visible_children++;
+    }
+
+  return MAX (0, (visible_children - 1) * handle_size);
+}
+
+static void
+sp_multi_paned_destroy_child_handle (SpMultiPaned      *self,
+                                      SpMultiPanedChild *child)
+{
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (child != NULL);
+
+  if (child->handle != NULL)
+    {
+      gdk_window_destroy (child->handle);
+      child->handle = NULL;
+    }
+}
+
+static void
+sp_multi_paned_update_child_handles (SpMultiPaned *self)
+{
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+  GtkWidget *widget = GTK_WIDGET (self);
+
+  if (gtk_widget_get_realized (widget))
+    {
+      GdkCursor *cursor;
+      guint i;
+
+      if (gtk_widget_is_sensitive (widget))
+        cursor = gdk_cursor_new_from_name (gtk_widget_get_display (widget),
+                                           priv->orientation == GTK_ORIENTATION_HORIZONTAL
+                                           ? "col-resize"
+                                           : "row-resize");
+      else
+        cursor = NULL;
+
+      for (i = 0; i < priv->children->len; i++)
+        {
+          SpMultiPanedChild *child = &g_array_index (priv->children, SpMultiPanedChild, i);
+
+          gdk_window_set_cursor (child->handle, cursor);
+        }
+
+      if (cursor)
+        g_object_unref (cursor);
+    }
+}
+
+static SpMultiPanedChild *
+sp_multi_paned_get_child (SpMultiPaned *self,
+                           GtkWidget     *widget)
+{
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+  guint i;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (GTK_IS_WIDGET (widget));
+
+  for (i = 0; i < priv->children->len; i++)
+    {
+      SpMultiPanedChild *child = &g_array_index (priv->children, SpMultiPanedChild, i);
+
+      if (child->widget == widget)
+        return child;
+    }
+
+  g_assert_not_reached ();
+
+  return NULL;
+}
+
+static gint
+sp_multi_paned_get_child_position (SpMultiPaned *self,
+                                    GtkWidget     *widget)
+{
+  SpMultiPanedChild *child;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (GTK_IS_WIDGET (widget));
+
+  child = sp_multi_paned_get_child (self, widget);
+
+  return child->position;
+}
+
+static void
+sp_multi_paned_set_child_position (SpMultiPaned *self,
+                                    GtkWidget     *widget,
+                                    gint           position)
+{
+  SpMultiPanedChild *child;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (GTK_IS_WIDGET (widget));
+  g_assert (position >= -1);
+
+  child = sp_multi_paned_get_child (self, widget);
+
+  if (child->position != position)
+    {
+      child->position = position;
+      child->position_set = (position != -1);
+      gtk_container_child_notify_by_pspec (GTK_CONTAINER (self), widget,
+                                           child_properties [CHILD_PROP_POSITION]);
+      gtk_widget_queue_resize (GTK_WIDGET (self));
+    }
+}
+
+static void
+sp_multi_paned_add (GtkContainer *container,
+                     GtkWidget    *widget)
+{
+  SpMultiPaned *self = (SpMultiPaned *)container;
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+  SpMultiPanedChild child = { 0 };
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (GTK_IS_WIDGET (widget));
+
+  child.widget = g_object_ref_sink (widget);
+  child.position = -1;
+
+  if (gtk_widget_get_realized (GTK_WIDGET (self)))
+    sp_multi_paned_create_child_handle (self, &child);
+
+  gtk_widget_set_parent (widget, GTK_WIDGET (self));
+
+  g_array_append_val (priv->children, child);
+
+  sp_multi_paned_reset_positions (self);
+
+  gtk_gesture_set_state (GTK_GESTURE (priv->gesture), GTK_EVENT_SEQUENCE_DENIED);
+}
+
+static void
+sp_multi_paned_remove (GtkContainer *container,
+                        GtkWidget    *widget)
+{
+  SpMultiPaned *self = (SpMultiPaned *)container;
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+  guint i;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (GTK_IS_WIDGET (widget));
+
+  for (i = 0; i < priv->children->len; i++)
+    {
+      SpMultiPanedChild *child = &g_array_index (priv->children, SpMultiPanedChild, i);
+
+      if (child->widget == widget)
+        {
+          sp_multi_paned_destroy_child_handle (self, child);
+
+          g_array_remove_index (priv->children, i);
+          child = NULL;
+
+          gtk_widget_unparent (widget);
+          g_object_unref (widget);
+
+          break;
+        }
+    }
+
+  sp_multi_paned_reset_positions (self);
+
+  gtk_gesture_set_state (GTK_GESTURE (priv->gesture), GTK_EVENT_SEQUENCE_DENIED);
+}
+
+static void
+sp_multi_paned_forall (GtkContainer *container,
+                        gboolean      include_internals,
+                        GtkCallback   callback,
+                        gpointer      user_data)
+{
+  SpMultiPaned *self = (SpMultiPaned *)container;
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+  gint i;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (callback != NULL);
+
+  for (i = priv->children->len; i > 0; i--)
+    {
+      SpMultiPanedChild *child = &g_array_index (priv->children, SpMultiPanedChild, i - 1);
+
+      callback (child->widget, user_data);
+    }
+}
+
+static GtkSizeRequestMode
+sp_multi_paned_get_request_mode (GtkWidget *widget)
+{
+  SpMultiPaned *self = (SpMultiPaned *)widget;
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+
+  g_assert (SP_IS_MULTI_PANED (self));
+
+  return (priv->orientation == GTK_ORIENTATION_HORIZONTAL) ? GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT
+                                                           : GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
+}
+
+static void
+sp_multi_paned_get_preferred_height (GtkWidget *widget,
+                                      gint      *min_height,
+                                      gint      *nat_height)
+{
+  SpMultiPaned *self = (SpMultiPaned *)widget;
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+  guint i;
+  gint real_min_height = 0;
+  gint real_nat_height = 0;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (min_height != NULL);
+  g_assert (nat_height != NULL);
+
+  for (i = 0; i < priv->children->len; i++)
+    {
+      SpMultiPanedChild *child = &g_array_index (priv->children, SpMultiPanedChild, i);
+      gint child_min_height = 0;
+      gint child_nat_height = 0;
+
+      if (gtk_widget_get_visible (child->widget))
+        {
+          gtk_widget_get_preferred_height (child->widget, &child_min_height, &child_nat_height);
+
+          if (priv->orientation == GTK_ORIENTATION_VERTICAL)
+            {
+              real_min_height += child_min_height;
+              real_nat_height += child_nat_height;
+            }
+          else
+            {
+              real_min_height = MAX (real_min_height, child_min_height);
+              real_nat_height = MAX (real_nat_height, child_nat_height);
+            }
+        }
+    }
+
+  if (priv->orientation == GTK_ORIENTATION_VERTICAL)
+    {
+      gint handle_size = sp_multi_paned_calc_handle_size (self);
+
+      real_min_height += handle_size;
+      real_nat_height += handle_size;
+    }
+
+  *min_height = real_min_height;
+  *nat_height = real_nat_height;
+}
+
+static void
+sp_multi_paned_get_child_preferred_height_for_width (SpMultiPaned      *self,
+                                                      SpMultiPanedChild *children,
+                                                      gint                n_children,
+                                                      gint                width,
+                                                      gint               *min_height,
+                                                      gint               *nat_height)
+{
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+  SpMultiPanedChild *child = children;
+  gint child_min_height = 0;
+  gint child_nat_height = 0;
+  gint neighbor_min_height = 0;
+  gint neighbor_nat_height = 0;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (n_children == 0 || children != NULL);
+  g_assert (min_height != NULL);
+  g_assert (nat_height != NULL);
+
+  *min_height = 0;
+  *nat_height = 0;
+
+  if (n_children == 0)
+    return;
+
+  if (gtk_widget_get_visible (child->widget))
+    gtk_widget_get_preferred_height_for_width (child->widget,
+                                               width,
+                                               &child_min_height,
+                                               &child_nat_height);
+
+  sp_multi_paned_get_child_preferred_height_for_width (self,
+                                                        children + 1,
+                                                        n_children - 1,
+                                                        width,
+                                                        &neighbor_min_height,
+                                                        &neighbor_nat_height);
+
+  if (priv->orientation == GTK_ORIENTATION_VERTICAL)
+    {
+      *min_height = child_min_height + neighbor_min_height;
+      *nat_height = child_nat_height + neighbor_nat_height;
+    }
+  else
+    {
+      *min_height = MAX (child_min_height, neighbor_min_height);
+      *nat_height = MAX (child_nat_height, neighbor_nat_height);
+    }
+}
+
+static void
+sp_multi_paned_get_preferred_height_for_width (GtkWidget *widget,
+                                                gint       width,
+                                                gint      *min_height,
+                                                gint      *nat_height)
+{
+  SpMultiPaned *self = (SpMultiPaned *)widget;
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (min_height != NULL);
+  g_assert (nat_height != NULL);
+
+  *min_height = 0;
+  *nat_height = 0;
+
+  sp_multi_paned_get_child_preferred_height_for_width (self,
+                                                        (SpMultiPanedChild *)(gpointer)priv->children->data,
+                                                        priv->children->len,
+                                                        width,
+                                                        min_height,
+                                                        nat_height);
+
+  if (priv->orientation == GTK_ORIENTATION_VERTICAL)
+    {
+      gint handle_size = sp_multi_paned_calc_handle_size (self);
+
+      *min_height += handle_size;
+      *nat_height += handle_size;
+    }
+}
+
+static void
+sp_multi_paned_get_preferred_width (GtkWidget *widget,
+                                     gint      *min_width,
+                                     gint      *nat_width)
+{
+  SpMultiPaned *self = (SpMultiPaned *)widget;
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+  guint i;
+  gint real_min_width = 0;
+  gint real_nat_width = 0;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (min_width != NULL);
+  g_assert (nat_width != NULL);
+
+  for (i = 0; i < priv->children->len; i++)
+    {
+      SpMultiPanedChild *child = &g_array_index (priv->children, SpMultiPanedChild, i);
+      gint child_min_width = 0;
+      gint child_nat_width = 0;
+
+      if (gtk_widget_get_visible (child->widget))
+        {
+          gtk_widget_get_preferred_width (child->widget, &child_min_width, &child_nat_width);
+
+          if (priv->orientation == GTK_ORIENTATION_VERTICAL)
+            {
+              real_min_width = MAX (real_min_width, child_min_width);
+              real_nat_width = MAX (real_nat_width, child_nat_width);
+            }
+          else
+            {
+              real_min_width += child_min_width;
+              real_nat_width += child_nat_width;
+            }
+        }
+    }
+
+  if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+    {
+      gint handle_size = sp_multi_paned_calc_handle_size (self);
+
+      real_min_width += handle_size;
+      real_nat_width += handle_size;
+    }
+
+  *min_width = real_min_width;
+  *nat_width = real_nat_width;
+}
+
+static void
+sp_multi_paned_get_child_preferred_width_for_height (SpMultiPaned      *self,
+                                                      SpMultiPanedChild *children,
+                                                      gint                n_children,
+                                                      gint                height,
+                                                      gint               *min_width,
+                                                      gint               *nat_width)
+{
+  SpMultiPanedChild *child = children;
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+  gint child_min_width = 0;
+  gint child_nat_width = 0;
+  gint neighbor_min_width = 0;
+  gint neighbor_nat_width = 0;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (n_children == 0 || children != NULL);
+  g_assert (min_width != NULL);
+  g_assert (nat_width != NULL);
+
+  *min_width = 0;
+  *nat_width = 0;
+
+  if (n_children == 0)
+    return;
+
+  if (gtk_widget_get_visible (child->widget))
+    gtk_widget_get_preferred_width_for_height (child->widget,
+                                               height,
+                                               &child_min_width,
+                                               &child_nat_width);
+
+  sp_multi_paned_get_child_preferred_width_for_height (self,
+                                                        children + 1,
+                                                        n_children - 1,
+                                                        height,
+                                                        &neighbor_min_width,
+                                                        &neighbor_nat_width);
+
+  if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+    {
+      *min_width = child_min_width + neighbor_min_width;
+      *nat_width = child_nat_width + neighbor_nat_width;
+    }
+  else
+    {
+      *min_width = MAX (child_min_width, neighbor_min_width);
+      *nat_width = MAX (child_nat_width, neighbor_nat_width);
+    }
+}
+
+static void
+sp_multi_paned_get_preferred_width_for_height (GtkWidget *widget,
+                                                gint       height,
+                                                gint      *min_width,
+                                                gint      *nat_width)
+{
+  SpMultiPaned *self = (SpMultiPaned *)widget;
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (min_width != NULL);
+  g_assert (nat_width != NULL);
+
+  sp_multi_paned_get_child_preferred_width_for_height (self,
+                                                        (SpMultiPanedChild *)(gpointer)priv->children->data,
+                                                        priv->children->len,
+                                                        height,
+                                                        min_width,
+                                                        nat_width);
+
+  if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+    {
+      gint handle_size = sp_multi_paned_calc_handle_size (self);
+
+      *min_width += handle_size;
+      *nat_width += handle_size;
+    }
+}
+
+static void
+allocation_stage_handles (SpMultiPaned   *self,
+                          AllocationState *state)
+{
+  guint i;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (state != NULL);
+  g_assert (state->children != NULL);
+  g_assert (state->n_children > 0);
+
+  /*
+   * Push each child allocation forward by the sum handle widths up to
+   * their position in the paned.
+   */
+
+  for (i = 1; i < state->n_children; i++)
+    {
+      SpMultiPanedChild *child = state->children [i];
+
+      if (IS_HORIZONTAL (state->orientation))
+        child->alloc.x += (i * state->handle_size);
+      else
+        child->alloc.y += (i * state->handle_size);
+    }
+
+  if (IS_HORIZONTAL (state->orientation))
+    state->avail_width -= (state->n_children - 1) * state->handle_size;
+  else
+    state->avail_height -= (state->n_children - 1) * state->handle_size;
+}
+
+static void
+allocation_stage_minimums (SpMultiPaned   *self,
+                           AllocationState *state)
+{
+  gint next_x;
+  gint next_y;
+  guint i;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (state != NULL);
+  g_assert (state->children != NULL);
+  g_assert (state->n_children > 0);
+
+  next_x = state->top_alloc.x;
+  next_y = state->top_alloc.y;
+
+  for (i = 0; i < state->n_children; i++)
+    {
+      SpMultiPanedChild *child = state->children [i];
+
+      if (IS_HORIZONTAL (state->orientation))
+        {
+          child->alloc.x = next_x;
+          child->alloc.y = state->top_alloc.y;
+          child->alloc.width = child->min_req.width;
+          child->alloc.height = state->top_alloc.height;
+
+          next_x = child->alloc.x + child->alloc.width;
+
+          state->avail_width -= child->alloc.width;
+        }
+      else
+        {
+          child->alloc.x = state->top_alloc.x;
+          child->alloc.y = next_y;
+          child->alloc.width = state->top_alloc.width;
+          child->alloc.height = child->min_req.height;
+
+          next_y = child->alloc.y + child->alloc.height;
+
+          state->avail_height -= child->alloc.height;
+        }
+    }
+}
+
+static void
+allocation_stage_naturals (SpMultiPaned   *self,
+                           AllocationState *state)
+{
+  gint x_adjust = 0;
+  gint y_adjust = 0;
+  guint i;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (state != NULL);
+  g_assert (state->children != NULL);
+  g_assert (state->n_children > 0);
+
+  for (i = 0; i < state->n_children; i++)
+    {
+      SpMultiPanedChild *child = state->children [i];
+
+      child->alloc.x += x_adjust;
+      child->alloc.y += y_adjust;
+
+      if (!child->position_set)
+        {
+          if (IS_HORIZONTAL (state->orientation))
+            {
+              if (child->nat_req.width > child->alloc.width)
+                {
+                  gint adjust = MIN (state->avail_width, child->nat_req.width - child->alloc.width);
+
+                  child->alloc.width += adjust;
+                  state->avail_width -= adjust;
+                  x_adjust += adjust;
+                }
+            }
+          else
+            {
+              if (child->nat_req.height > child->alloc.height)
+                {
+                  gint adjust = MIN (state->avail_height, child->nat_req.height - child->alloc.height);
+
+                  child->alloc.height += adjust;
+                  state->avail_height -= adjust;
+                  y_adjust += adjust;
+                }
+            }
+        }
+    }
+}
+
+static void
+allocation_stage_borders (SpMultiPaned   *self,
+                          AllocationState *state)
+{
+  gint border_width;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (state != NULL);
+  g_assert (state->children != NULL);
+  g_assert (state->n_children > 0);
+
+  border_width = gtk_container_get_border_width (GTK_CONTAINER (self));
+
+  state->top_alloc.x += border_width;
+  state->top_alloc.y += border_width;
+  state->top_alloc.width -= border_width * 2;
+  state->top_alloc.height -= border_width * 2;
+
+  if (state->top_alloc.width < 0)
+    state->top_alloc.width = 0;
+
+  if (state->top_alloc.height < 0)
+    state->top_alloc.height = 0;
+
+  state->avail_width = state->top_alloc.width;
+  state->avail_height = state->top_alloc.height;
+}
+
+static void
+allocation_stage_cache_request (SpMultiPaned   *self,
+                                AllocationState *state)
+{
+  guint i;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (state != NULL);
+  g_assert (state->children != NULL);
+  g_assert (state->n_children > 0);
+
+  for (i = 0; i < state->n_children; i++)
+    {
+      SpMultiPanedChild *child = state->children [i];
+
+      if (IS_HORIZONTAL (state->orientation))
+        gtk_widget_get_preferred_width_for_height (child->widget,
+                                                   state->avail_height,
+                                                   &child->min_req.width,
+                                                   &child->nat_req.width);
+      else
+        gtk_widget_get_preferred_height_for_width (child->widget,
+                                                   state->avail_width,
+                                                   &child->min_req.height,
+                                                   &child->nat_req.height);
+    }
+}
+
+static void
+allocation_stage_positions (SpMultiPaned   *self,
+                            AllocationState *state)
+{
+  gint x_adjust = 0;
+  gint y_adjust = 0;
+  guint i;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (state != NULL);
+  g_assert (state->children != NULL);
+  g_assert (state->n_children > 0);
+
+  /*
+   * Child may have a position set, which happens when dragging the input
+   * window (handle) to resize the child. If so, we want to try to allocate
+   * extra space above the minimum size.
+   */
+
+  for (i = 0; i < state->n_children; i++)
+    {
+      SpMultiPanedChild *child = state->children [i];
+
+      child->alloc.x += x_adjust;
+      child->alloc.y += y_adjust;
+
+      if (child->position_set)
+        {
+          if (IS_HORIZONTAL (state->orientation))
+            {
+              if (child->position > child->alloc.width)
+                {
+                  gint adjust = MIN (state->avail_width, child->position - child->alloc.width);
+
+                  child->alloc.width += adjust;
+                  state->avail_width -= adjust;
+                  x_adjust += adjust;
+                }
+            }
+          else
+            {
+              if (child->position > child->alloc.height)
+                {
+                  gint adjust = MIN (state->avail_height, child->position - child->alloc.height);
+
+                  child->alloc.height += adjust;
+                  state->avail_height -= adjust;
+                  y_adjust += adjust;
+                }
+            }
+        }
+    }
+}
+
+static void
+allocation_stage_drag_overflow (SpMultiPaned   *self,
+                                AllocationState *state)
+{
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+  guint drag_index;
+  gint j;
+  gint drag_overflow;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (state != NULL);
+  g_assert (state->children != NULL);
+  g_assert (state->n_children > 0);
+
+  if (priv->drag_begin == NULL)
+    return;
+
+  drag_overflow = ABS (priv->drag_extra_offset);
+
+  for (drag_index = 0; drag_index < state->n_children; drag_index++)
+    if (state->children [drag_index] == priv->drag_begin)
+      break;
+
+  if (drag_index == 0 ||
+      drag_index >= state->n_children ||
+      state->children [drag_index] != priv->drag_begin)
+    return;
+
+  /*
+   * If the user is dragging and we have run out of room in the drag
+   * child, then we need to start stealing space from the previous
+   * items.
+   *
+   * This works our way back to the beginning from the drag child
+   * stealing available space and giving it to the child *AFTER* the
+   * drag item. This is because the drag handle is after the drag
+   * child, so logically to the user, its drag_index+1.
+   */
+
+  for (j = (int)drag_index; j >= 0 && drag_overflow > 0; j--)
+    {
+      SpMultiPanedChild *child = state->children [j];
+      guint k;
+      gint adjust = 0;
+
+      if (IS_HORIZONTAL (state->orientation))
+        {
+          if (child->alloc.width > child->min_req.width)
+            {
+              if (drag_overflow > (child->alloc.width - child->min_req.width))
+                adjust = child->alloc.width - child->min_req.width;
+              else
+                adjust = drag_overflow;
+              drag_overflow -= adjust;
+              child->alloc.width -= adjust;
+              state->children [drag_index + 1]->alloc.width += adjust;
+            }
+        }
+      else
+        {
+          if (child->alloc.height > child->min_req.height)
+            {
+              if (drag_overflow > (child->alloc.height - child->min_req.height))
+                adjust = child->alloc.height - child->min_req.height;
+              else
+                adjust = drag_overflow;
+              drag_overflow -= adjust;
+              child->alloc.height -= adjust;
+              state->children [drag_index + 1]->alloc.height += adjust;
+            }
+        }
+
+      /*
+       * Now walk back forward and adjust x/y offsets for all of the
+       * children that will have just shifted.
+       */
+
+      for (k = j + 1; k <= drag_index + 1; k++)
+        {
+          SpMultiPanedChild *neighbor = state->children [k];
+
+          if (IS_HORIZONTAL (state->orientation))
+            neighbor->alloc.x -= adjust;
+          else
+            neighbor->alloc.y -= adjust;
+        }
+    }
+}
+
+static void
+allocation_stage_expand (SpMultiPaned   *self,
+                         AllocationState *state)
+{
+  gint x_adjust = 0;
+  gint y_adjust = 0;
+  gint n_expand = 0;
+  gint adjust;
+  guint i;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (state != NULL);
+  g_assert (state->children != NULL);
+  g_assert (state->n_children > 0);
+
+  if (state->n_children == 1)
+    {
+      SpMultiPanedChild *child = state->children [0];
+
+      /*
+       * Special case for single child, just expand to the
+       * available space. Ideally we would have much shorter
+       * allocation stages in this case.
+       */
+
+      if (IS_HORIZONTAL (state->orientation))
+        {
+          if (gtk_widget_get_hexpand (child->widget))
+            child->alloc.width = state->top_alloc.width;
+        }
+      else
+        {
+          if (gtk_widget_get_vexpand (child->widget))
+            child->alloc.height = state->top_alloc.height;
+        }
+
+      return;
+    }
+
+  for (i = 0; i < state->n_children; i++)
+    {
+      SpMultiPanedChild *child = state->children [i];
+
+      if (!child->position_set)
+        {
+          if (IS_HORIZONTAL (state->orientation))
+            {
+              if (gtk_widget_get_hexpand (child->widget))
+                n_expand++;
+            }
+          else
+            {
+              if (gtk_widget_get_vexpand (child->widget))
+                n_expand++;
+            }
+        }
+    }
+
+  if (n_expand == 0)
+    return;
+
+  if (IS_HORIZONTAL (state->orientation))
+    adjust = state->avail_width / n_expand;
+  else
+    adjust = state->avail_height / n_expand;
+
+  for (i = 0; i < state->n_children; i++)
+    {
+      SpMultiPanedChild *child = state->children [i];
+
+      child->alloc.x += x_adjust;
+      child->alloc.y += y_adjust;
+
+      if (!child->position_set)
+        {
+          if (IS_HORIZONTAL (state->orientation))
+            {
+              if (gtk_widget_get_hexpand (child->widget))
+                {
+                  child->alloc.width += adjust;
+                  state->avail_height -= adjust;
+                  x_adjust += adjust;
+                }
+            }
+          else
+            {
+              if (gtk_widget_get_vexpand (child->widget))
+                {
+                  child->alloc.height += adjust;
+                  state->avail_height -= adjust;
+                  y_adjust += adjust;
+                }
+            }
+        }
+    }
+
+  if (IS_HORIZONTAL (state->orientation))
+    {
+      if (state->avail_width > 0)
+        {
+          state->children [state->n_children - 1]->alloc.width += state->avail_width;
+          state->avail_width = 0;
+        }
+    }
+  else
+    {
+      if (state->avail_height > 0)
+        {
+          state->children [state->n_children - 1]->alloc.height += state->avail_height;
+          state->avail_height = 0;
+        }
+    }
+}
+
+static void
+allocation_stage_allocate (SpMultiPaned   *self,
+                           AllocationState *state)
+{
+  guint i;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (state != NULL);
+  g_assert (state->children != NULL);
+  g_assert (state->n_children > 0);
+
+  for (i = 0; i < state->n_children; i++)
+    {
+      SpMultiPanedChild *child = state->children [i];
+
+      gtk_widget_size_allocate (child->widget, &child->alloc);
+
+      if ((child->handle != NULL) && (state->n_children != (i + 1)))
+        {
+          if (state->orientation == GTK_ORIENTATION_HORIZONTAL)
+            {
+              gdk_window_move_resize (child->handle,
+                                      child->alloc.x + child->alloc.width - (HANDLE_WIDTH / 2),
+                                      child->alloc.y,
+                                      HANDLE_WIDTH,
+                                      child->alloc.height);
+            }
+          else
+            {
+              gdk_window_move_resize (child->handle,
+                                      child->alloc.x,
+                                      child->alloc.y + child->alloc.height - (HANDLE_HEIGHT / 2),
+                                      child->alloc.width,
+                                      HANDLE_HEIGHT);
+            }
+
+          gdk_window_show (child->handle);
+        }
+    }
+}
+
+static void
+sp_multi_paned_size_allocate (GtkWidget     *widget,
+                               GtkAllocation *allocation)
+{
+  SpMultiPaned *self = (SpMultiPaned *)widget;
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+  AllocationState state = { 0 };
+  GPtrArray *children;
+  guint i;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (allocation != NULL);
+
+  GTK_WIDGET_CLASS (sp_multi_paned_parent_class)->size_allocate (widget, allocation);
+
+  if (priv->children->len == 0)
+    return;
+
+  children = g_ptr_array_new ();
+
+  for (i = 0; i < priv->children->len; i++)
+    {
+      SpMultiPanedChild *child = &g_array_index (priv->children, SpMultiPanedChild, i);
+
+      child->alloc.x = 0;
+      child->alloc.y = 0;
+      child->alloc.width = 0;
+      child->alloc.height = 0;
+
+      if (child->widget != NULL &&
+          gtk_widget_get_child_visible (child->widget) &&
+          gtk_widget_get_visible (child->widget))
+        g_ptr_array_add (children, child);
+      else if (child->handle)
+        gdk_window_hide (child->handle);
+    }
+
+  state.children = (SpMultiPanedChild **)children->pdata;
+  state.n_children = children->len;
+
+  if (state.n_children == 0)
+    {
+      g_ptr_array_free (children, TRUE);
+      return;
+    }
+
+  gtk_widget_style_get (GTK_WIDGET (self),
+                        "handle-size", &state.handle_size,
+                        NULL);
+
+  state.orientation = priv->orientation;
+  state.top_alloc = *allocation;
+  state.avail_width = allocation->width;
+  state.avail_height = allocation->height;
+
+  for (i = 0; i < G_N_ELEMENTS (allocation_stages); i++)
+    allocation_stages [i] (self, &state);
+
+  g_ptr_array_free (children, TRUE);
+}
+
+static void
+sp_multi_paned_realize (GtkWidget *widget)
+{
+  SpMultiPaned *self = (SpMultiPaned *)widget;
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+  guint i;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+
+  GTK_WIDGET_CLASS (sp_multi_paned_parent_class)->realize (widget);
+
+  for (i = 0; i < priv->children->len; i++)
+    {
+      SpMultiPanedChild *child = &g_array_index (priv->children, SpMultiPanedChild, i);
+
+      sp_multi_paned_create_child_handle (self, child);
+    }
+}
+
+static void
+sp_multi_paned_unrealize (GtkWidget *widget)
+{
+  SpMultiPaned *self = (SpMultiPaned *)widget;
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+  guint i;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+
+  for (i = 0; i < priv->children->len; i++)
+    {
+      SpMultiPanedChild *child = &g_array_index (priv->children, SpMultiPanedChild, i);
+
+      sp_multi_paned_destroy_child_handle (self, child);
+    }
+
+  GTK_WIDGET_CLASS (sp_multi_paned_parent_class)->unrealize (widget);
+}
+
+static void
+sp_multi_paned_map (GtkWidget *widget)
+{
+  SpMultiPaned *self = (SpMultiPaned *)widget;
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+  guint i;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+
+  GTK_WIDGET_CLASS (sp_multi_paned_parent_class)->map (widget);
+
+  for (i = 0; i < priv->children->len; i++)
+    {
+      SpMultiPanedChild *child = &g_array_index (priv->children, SpMultiPanedChild, i);
+
+      gdk_window_show (child->handle);
+    }
+}
+
+static void
+sp_multi_paned_unmap (GtkWidget *widget)
+{
+  SpMultiPaned *self = (SpMultiPaned *)widget;
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+  guint i;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+
+  for (i = 0; i < priv->children->len; i++)
+    {
+      SpMultiPanedChild *child = &g_array_index (priv->children, SpMultiPanedChild, i);
+
+      gdk_window_hide (child->handle);
+    }
+
+  GTK_WIDGET_CLASS (sp_multi_paned_parent_class)->unmap (widget);
+}
+
+static gboolean
+sp_multi_paned_draw (GtkWidget *widget,
+                      cairo_t   *cr)
+{
+  SpMultiPaned *self = (SpMultiPaned *)widget;
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+  gboolean ret;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (cr != NULL);
+
+  ret = GTK_WIDGET_CLASS (sp_multi_paned_parent_class)->draw (widget, cr);
+
+  if (ret != GDK_EVENT_STOP)
+    {
+      GtkStyleContext *style_context;
+      gint handle_size = 1;
+      guint i;
+
+      style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
+
+      gtk_widget_style_get (widget, "handle-size", &handle_size, NULL);
+
+      for (i = 0; i < priv->children->len; i++)
+        {
+          SpMultiPanedChild *child = &g_array_index (priv->children, SpMultiPanedChild, i);
+          GtkAllocation alloc;
+
+          if (!gtk_widget_get_realized (child->widget) ||
+              !gtk_widget_get_visible (child->widget))
+            continue;
+
+          gtk_widget_get_allocation (child->widget, &alloc);
+
+          if (!sp_multi_paned_is_last_visible_child (self, child))
+            {
+              if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+                gtk_render_handle (style_context,
+                                   cr,
+                                   alloc.x + alloc.width,
+                                   0,
+                                   handle_size,
+                                   alloc.height);
+              else
+                gtk_render_handle (style_context,
+                                   cr,
+                                   0,
+                                   alloc.y + alloc.height,
+                                   alloc.width,
+                                   handle_size);
+            }
+        }
+    }
+
+  return ret;
+}
+
+static void
+sp_multi_paned_pan_gesture_drag_begin (SpMultiPaned *self,
+                                        gdouble        x,
+                                        gdouble        y,
+                                        GtkGesturePan *gesture)
+{
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+  GdkEventSequence *sequence;
+  const GdkEvent *event;
+  guint i;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (GTK_IS_GESTURE_PAN (gesture));
+  g_assert (gesture == priv->gesture);
+
+  sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+  event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
+
+  priv->drag_begin = NULL;
+  priv->drag_begin_position = 0;
+  priv->drag_extra_offset = 0;
+
+  for (i = 0; i < priv->children->len; i++)
+    {
+      SpMultiPanedChild *child = &g_array_index (priv->children, SpMultiPanedChild, i);
+
+      if (child->handle == event->any.window)
+        {
+          priv->drag_begin = child;
+          break;
+        }
+
+      /*
+       * We want to make any child before the drag child "sticky" so that it
+       * will no longer have expand adjustments while we perform the drag
+       * operation.
+       */
+      if (gtk_widget_get_child_visible (child->widget) &&
+          gtk_widget_get_visible (child->widget))
+        {
+          child->position_set = TRUE;
+          child->position = IS_HORIZONTAL (priv->orientation)
+            ? child->alloc.width
+            : child->alloc.height;
+        }
+    }
+
+  if (priv->drag_begin == NULL)
+    {
+      gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
+      return;
+    }
+
+  if (IS_HORIZONTAL (priv->orientation))
+    priv->drag_begin_position = priv->drag_begin->alloc.width;
+  else
+    priv->drag_begin_position = priv->drag_begin->alloc.height;
+
+  gtk_gesture_pan_set_orientation (gesture, priv->orientation);
+  gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+
+  g_signal_emit (self, signals [RESIZE_DRAG_BEGIN], 0, priv->drag_begin->widget);
+}
+
+static void
+sp_multi_paned_pan_gesture_drag_end (SpMultiPaned *self,
+                                      gdouble        x,
+                                      gdouble        y,
+                                      GtkGesturePan *gesture)
+{
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+  GdkEventSequence *sequence;
+  GtkEventSequenceState state;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (GTK_IS_GESTURE_PAN (gesture));
+  g_assert (gesture == priv->gesture);
+
+  sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+  state = gtk_gesture_get_sequence_state (GTK_GESTURE (gesture), sequence);
+
+  if (state != GTK_EVENT_SEQUENCE_CLAIMED)
+    goto cleanup;
+
+  g_assert (priv->drag_begin != NULL);
+
+  g_signal_emit (self, signals [RESIZE_DRAG_END], 0, priv->drag_begin->widget);
+
+cleanup:
+  priv->drag_begin = NULL;
+  priv->drag_begin_position = 0;
+  priv->drag_extra_offset = 0;
+}
+
+static void
+sp_multi_paned_pan_gesture_pan (SpMultiPaned   *self,
+                                 GtkPanDirection  direction,
+                                 gdouble          offset,
+                                 GtkGesturePan   *gesture)
+{
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+  GtkAllocation alloc;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (GTK_IS_GESTURE_PAN (gesture));
+  g_assert (gesture == priv->gesture);
+  g_assert (priv->drag_begin != NULL);
+
+  gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
+
+  if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+    {
+      if (direction == GTK_PAN_DIRECTION_LEFT)
+        offset = -offset;
+    }
+  else
+    {
+      g_assert (priv->orientation == GTK_ORIENTATION_VERTICAL);
+
+      if (direction == GTK_PAN_DIRECTION_UP)
+        offset = -offset;
+    }
+
+  if ((priv->drag_begin_position + offset) < 0)
+    priv->drag_extra_offset = (priv->drag_begin_position + offset);
+  else
+    priv->drag_extra_offset = 0;
+
+  priv->drag_begin->position = MAX (0, priv->drag_begin_position + offset);
+  priv->drag_begin->position_set = TRUE;
+
+  gtk_widget_queue_allocate (GTK_WIDGET (self));
+}
+
+static void
+sp_multi_paned_create_pan_gesture (SpMultiPaned *self)
+{
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+  GtkGesture *gesture;
+
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (priv->gesture == NULL);
+
+  gesture = gtk_gesture_pan_new (GTK_WIDGET (self), GTK_ORIENTATION_HORIZONTAL);
+  gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), FALSE);
+  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture), GTK_PHASE_CAPTURE);
+
+  g_signal_connect_object (gesture,
+                           "drag-begin",
+                           G_CALLBACK (sp_multi_paned_pan_gesture_drag_begin),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (gesture,
+                           "drag-end",
+                           G_CALLBACK (sp_multi_paned_pan_gesture_drag_end),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (gesture,
+                           "pan",
+                           G_CALLBACK (sp_multi_paned_pan_gesture_pan),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  priv->gesture = GTK_GESTURE_PAN (gesture);
+}
+
+static void
+sp_multi_paned_resize_drag_begin (SpMultiPaned *self,
+                                   GtkWidget     *child)
+{
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (GTK_IS_WIDGET (child));
+
+}
+
+static void
+sp_multi_paned_resize_drag_end (SpMultiPaned *self,
+                                 GtkWidget     *child)
+{
+  g_assert (SP_IS_MULTI_PANED (self));
+  g_assert (GTK_IS_WIDGET (child));
+
+}
+
+static void
+sp_multi_paned_get_child_property (GtkContainer *container,
+                                    GtkWidget    *widget,
+                                    guint         prop_id,
+                                    GValue       *value,
+                                    GParamSpec   *pspec)
+{
+  SpMultiPaned *self = SP_MULTI_PANED (container);
+
+  switch (prop_id)
+    {
+    case CHILD_PROP_POSITION:
+      g_value_set_int (value, sp_multi_paned_get_child_position (self, widget));
+      break;
+
+    default:
+      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec);
+    }
+}
+
+static void
+sp_multi_paned_set_child_property (GtkContainer *container,
+                                    GtkWidget    *widget,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+  SpMultiPaned *self = SP_MULTI_PANED (container);
+
+  switch (prop_id)
+    {
+    case CHILD_PROP_POSITION:
+      sp_multi_paned_set_child_position (self, widget, g_value_get_int (value));
+      break;
+
+    default:
+      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec);
+    }
+}
+
+static void
+sp_multi_paned_finalize (GObject *object)
+{
+  SpMultiPaned *self = (SpMultiPaned *)object;
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+
+  g_assert (priv->children->len == 0);
+
+  g_clear_pointer (&priv->children, g_array_unref);
+  g_clear_object (&priv->gesture);
+
+  G_OBJECT_CLASS (sp_multi_paned_parent_class)->finalize (object);
+}
+
+static void
+sp_multi_paned_get_property (GObject    *object,
+                              guint       prop_id,
+                              GValue     *value,
+                              GParamSpec *pspec)
+{
+  SpMultiPaned *self = SP_MULTI_PANED (object);
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_ORIENTATION:
+      g_value_set_enum (value, priv->orientation);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+sp_multi_paned_set_property (GObject      *object,
+                              guint         prop_id,
+                              const GValue *value,
+                              GParamSpec   *pspec)
+{
+  SpMultiPaned *self = SP_MULTI_PANED (object);
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_ORIENTATION:
+      priv->orientation = g_value_get_enum (value);
+      sp_multi_paned_update_child_handles (self);
+      gtk_widget_queue_resize (GTK_WIDGET (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+sp_multi_paned_state_flags_changed (GtkWidget     *widget,
+                                     GtkStateFlags  previous_state)
+{
+  sp_multi_paned_update_child_handles (SP_MULTI_PANED (widget));
+
+  GTK_WIDGET_CLASS (sp_multi_paned_parent_class)->state_flags_changed (widget, previous_state);
+}
+
+static void
+sp_multi_paned_class_init (SpMultiPanedClass *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 = sp_multi_paned_get_property;
+  object_class->set_property = sp_multi_paned_set_property;
+  object_class->finalize = sp_multi_paned_finalize;
+
+  widget_class->get_request_mode = sp_multi_paned_get_request_mode;
+  widget_class->get_preferred_width = sp_multi_paned_get_preferred_width;
+  widget_class->get_preferred_height = sp_multi_paned_get_preferred_height;
+  widget_class->get_preferred_width_for_height = sp_multi_paned_get_preferred_width_for_height;
+  widget_class->get_preferred_height_for_width = sp_multi_paned_get_preferred_height_for_width;
+  widget_class->size_allocate = sp_multi_paned_size_allocate;
+  widget_class->realize = sp_multi_paned_realize;
+  widget_class->unrealize = sp_multi_paned_unrealize;
+  widget_class->map = sp_multi_paned_map;
+  widget_class->unmap = sp_multi_paned_unmap;
+  widget_class->draw = sp_multi_paned_draw;
+  widget_class->state_flags_changed = sp_multi_paned_state_flags_changed;
+
+  container_class->add = sp_multi_paned_add;
+  container_class->remove = sp_multi_paned_remove;
+  container_class->get_child_property = sp_multi_paned_get_child_property;
+  container_class->set_child_property = sp_multi_paned_set_child_property;
+  container_class->forall = sp_multi_paned_forall;
+
+  klass->resize_drag_begin = sp_multi_paned_resize_drag_begin;
+  klass->resize_drag_end = sp_multi_paned_resize_drag_end;
+
+  gtk_widget_class_set_css_name (widget_class, "multipaned");
+
+  properties [PROP_ORIENTATION] =
+    g_param_spec_enum ("orientation",
+                       "Orientation",
+                       "Orientation",
+                       GTK_TYPE_ORIENTATION,
+                       GTK_ORIENTATION_VERTICAL,
+                       (G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  child_properties [CHILD_PROP_POSITION] =
+    g_param_spec_int ("position",
+                      "Position",
+                      "Position",
+                      -1,
+                      G_MAXINT,
+                      0,
+                      (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  gtk_container_class_install_child_properties (container_class, N_CHILD_PROPS, child_properties);
+
+  style_properties [STYLE_PROP_HANDLE_SIZE] =
+    g_param_spec_int ("handle-size",
+                      "Handle Size",
+                      "Width of the resize handle",
+                      0,
+                      G_MAXINT,
+                      1,
+                      (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+  gtk_widget_class_install_style_property (widget_class, style_properties [STYLE_PROP_HANDLE_SIZE]);
+
+  signals [RESIZE_DRAG_BEGIN] =
+    g_signal_new ("resize-drag-begin",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (SpMultiPanedClass, resize_drag_begin),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 1, GTK_TYPE_WIDGET);
+
+  signals [RESIZE_DRAG_END] =
+    g_signal_new ("resize-drag-end",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (SpMultiPanedClass, resize_drag_end),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 1, GTK_TYPE_WIDGET);
+}
+
+static void
+sp_multi_paned_init (SpMultiPaned *self)
+{
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+
+  gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
+
+  priv->children = g_array_new (FALSE, TRUE, sizeof (SpMultiPanedChild));
+
+  sp_multi_paned_create_pan_gesture (self);
+}
+
+GtkWidget *
+sp_multi_paned_new (void)
+{
+  return g_object_new (SP_TYPE_MULTI_PANED, NULL);
+}
+
+guint
+sp_multi_paned_get_n_children (SpMultiPaned *self)
+{
+  SpMultiPanedPrivate *priv = sp_multi_paned_get_instance_private (self);
+
+  g_return_val_if_fail (SP_IS_MULTI_PANED (self), 0);
+
+  return priv->children ? priv->children->len : 0;
+}
diff --git a/lib/sp-multi-paned.h b/lib/sp-multi-paned.h
new file mode 100644
index 0000000..1dd88fe
--- /dev/null
+++ b/lib/sp-multi-paned.h
@@ -0,0 +1,55 @@
+/* sp-multi-paned.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * 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 SP_MULTI_PANED_H
+#define SP_MULTI_PANED_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define SP_TYPE_MULTI_PANED (sp_multi_paned_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (SpMultiPaned, sp_multi_paned, SP, MULTI_PANED, GtkContainer)
+
+struct _SpMultiPanedClass
+{
+  GtkContainerClass parent;
+
+  void (*resize_drag_begin) (SpMultiPaned *self,
+                             GtkWidget     *child);
+  void (*resize_drag_end)   (SpMultiPaned *self,
+                             GtkWidget     *child);
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+  gpointer _reserved5;
+  gpointer _reserved6;
+  gpointer _reserved7;
+  gpointer _reserved8;
+};
+
+GtkWidget *sp_multi_paned_new            (void);
+guint      sp_multi_paned_get_n_children (SpMultiPaned *self);
+
+G_END_DECLS
+
+#endif /* SP_MULTI_PANED_H */


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