[gnome-builder] panel: add panel-gtk private library
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder] panel: add panel-gtk private library
- Date: Wed, 23 Mar 2016 02:32:12 +0000 (UTC)
commit 8cdac199b92e6315befa7ae94b36b8ae912f9b44
Author: Christian Hergert <chergert redhat com>
Date: Tue Mar 22 18:53:52 2016 -0700
panel: add panel-gtk private library
Makefile.am | 10 +-
configure.ac | 6 +
contrib/Makefile.am | 1 +
contrib/pnl/Makefile.am | 136 ++
contrib/pnl/pnl-animation.c | 1164 ++++++++++++
contrib/pnl/pnl-animation.h | 78 +
contrib/pnl/pnl-dock-bin-edge-private.h | 32 +
contrib/pnl/pnl-dock-bin-edge.c | 259 +++
contrib/pnl/pnl-dock-bin-edge.h | 48 +
contrib/pnl/pnl-dock-bin.c | 1922 ++++++++++++++++++++
contrib/pnl/pnl-dock-bin.h | 55 +
contrib/pnl/pnl-dock-item.c | 456 +++++
contrib/pnl/pnl-dock-item.h | 65 +
contrib/pnl/pnl-dock-manager.c | 306 ++++
contrib/pnl/pnl-dock-manager.h | 57 +
contrib/pnl/pnl-dock-overlay-edge-private.h | 39 +
contrib/pnl/pnl-dock-overlay-edge.c | 290 +++
contrib/pnl/pnl-dock-overlay.c | 704 +++++++
contrib/pnl/pnl-dock-overlay.h | 50 +
contrib/pnl/pnl-dock-paned-private.h | 31 +
contrib/pnl/pnl-dock-paned.c | 134 ++
contrib/pnl/pnl-dock-paned.h | 48 +
contrib/pnl/pnl-dock-revealer.c | 779 ++++++++
contrib/pnl/pnl-dock-revealer.h | 76 +
contrib/pnl/pnl-dock-stack.c | 320 ++++
contrib/pnl/pnl-dock-stack.h | 42 +
contrib/pnl/pnl-dock-tab-strip.c | 41 +
contrib/pnl/pnl-dock-tab-strip.h | 34 +
contrib/pnl/pnl-dock-transient-grab.c | 322 ++++
contrib/pnl/pnl-dock-transient-grab.h | 50 +
contrib/pnl/pnl-dock-types.h | 56 +
contrib/pnl/pnl-dock-widget.c | 184 ++
contrib/pnl/pnl-dock-widget.h | 51 +
contrib/pnl/pnl-dock-window.c | 113 ++
contrib/pnl/pnl-dock-window.h | 39 +
contrib/pnl/pnl-dock.c | 43 +
contrib/pnl/pnl-dock.h | 37 +
contrib/pnl/pnl-frame-source.c | 130 ++
contrib/pnl/pnl-frame-source.h | 36 +
contrib/pnl/pnl-multi-paned.c | 1863 +++++++++++++++++++
contrib/pnl/pnl-multi-paned.h | 59 +
contrib/pnl/pnl-tab-strip.c | 538 ++++++
contrib/pnl/pnl-tab-strip.h | 53 +
contrib/pnl/pnl-tab.c | 275 +++
contrib/pnl/pnl-tab.h | 47 +
contrib/pnl/pnl-util-private.h | 39 +
contrib/pnl/pnl-util.c | 109 ++
contrib/pnl/pnl-version.h.in | 97 +
contrib/pnl/pnl.gresource.xml | 8 +
contrib/pnl/pnl.h | 52 +
.../pnl/resources/panel-bottom-pane-symbolic.svg | 26 +
contrib/pnl/resources/panel-left-pane-symbolic.svg | 26 +
.../pnl/resources/panel-right-pane-symbolic.svg | 26 +
data/libide-1.0.pc.in | 4 +-
src/Makefile.am | 3 +
tests/Makefile.am | 2 +
56 files changed, 11465 insertions(+), 6 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 8730dad..d9d7bdb 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -67,12 +67,14 @@ dist-hook:
.PHONY: AUTHORS
+RUNTIME_TYPELIB_PATH = libide:contrib/egg:contrib/pnl:contrib/tmpl:$(GI_TYPELIB_PATH)
+
run:
PEAS_DEBUG=1 \
GB_IN_TREE_PLUGINS=1 \
GB_IN_TREE_FONTS=1 \
GB_IN_TREE_STYLE_SCHEMES=1 \
- GI_TYPELIB_PATH="libide:contrib/egg:contrib/tmpl:$(GI_TYPELIB_PATH)" \
+ GI_TYPELIB_PATH="$(RUNTIME_TYPELIB_PATH)" \
GOBJECT_DEBUG=instance-count \
PYTHONDONTWRITEBYTECODE=yes \
PATH=$(top_builddir)/src:${PATH} \
@@ -83,7 +85,7 @@ strace:
GB_IN_TREE_PLUGINS=1 \
GB_IN_TREE_FONTS=1 \
GB_IN_TREE_STYLE_SCHEMES=1 \
- GI_TYPELIB_PATH="libide:contrib/egg:contrib/tmpl:$(GI_TYPELIB_PATH)" \
+ GI_TYPELIB_PATH="$(RUNTIME_TYPELIB_PATH)" \
GOBJECT_DEBUG=instance-count \
PYTHONDONTWRITEBYTECODE=yes \
PATH=$(top_builddir)/src:${PATH} \
@@ -94,7 +96,7 @@ debug:
GB_IN_TREE_PLUGINS=1 \
GB_IN_TREE_FONTS=1 \
GB_IN_TREE_STYLE_SCHEMES=1 \
- GI_TYPELIB_PATH="libide:contrib/egg:contrib/tmpl:$(GI_TYPELIB_PATH)" \
+ GI_TYPELIB_PATH="$(RUNTIME_TYPELIB_PATH)" \
G_DEBUG=fatal-criticals \
GOBJECT_DEBUG=instance-count \
PYTHONDONTWRITEBYTECODE=yes \
@@ -106,7 +108,7 @@ valgrind:
GB_IN_TREE_PLUGINS=1 \
GB_IN_TREE_FONTS=1 \
GB_IN_TREE_STYLE_SCHEMES=1 \
- GI_TYPELIB_PATH="libide:contrib/egg:contrib/tmpl:$(GI_TYPELIB_PATH)" \
+ GI_TYPELIB_PATH="$(RUNTIME_TYPELIB_PATH)" \
G_DEBUG=fatal-criticals \
G_SLICE=always-malloc \
PYTHONDONTWRITEBYTECODE=yes \
diff --git a/configure.ac b/configure.ac
index d1942c5..1b448f7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -197,6 +197,7 @@ PKG_CHECK_MODULES(LIBIDE, [gio-2.0 >= glib_required_version
pangoft2 >= pangoft2_required_version])
PKG_CHECK_MODULES(NAUTILUS, [glib-2.0 >= glib_required_version
gtk+-3.0 >= gtk_required_version])
+PKG_CHECK_MODULES(PANEL_GTK,[gtk+-3.0 >= gtk_required_version])
PKG_CHECK_MODULES(PYGOBJECT,[pygobject-3.0 >= pygobject_required_version],
[have_pygobject=yes],
[have_pygobject=no])
@@ -395,10 +396,12 @@ dnl ***********************************************************************
dnl Setup common cflags and ldflags for plugins
dnl ***********************************************************************
PLUGIN_CFLAGS="$PLUGIN_CFLAGS -I\$(top_builddir)/contrib/egg"
+PLUGIN_CFLAGS="$PLUGIN_CFLAGS -I\$(top_builddir)/contrib/pnl"
PLUGIN_CFLAGS="$PLUGIN_CFLAGS -I\$(top_builddir)/libide"
PLUGIN_CFLAGS="$PLUGIN_CFLAGS -I\$(top_srcdir)/contrib/egg"
PLUGIN_CFLAGS="$PLUGIN_CFLAGS -I\$(top_srcdir)/contrib/gd"
PLUGIN_CFLAGS="$PLUGIN_CFLAGS -I\$(top_srcdir)/contrib/nautilus"
+PLUGIN_CFLAGS="$PLUGIN_CFLAGS -I\$(top_srcdir)/contrib/pnl"
PLUGIN_CFLAGS="$PLUGIN_CFLAGS -I\$(top_srcdir)/contrib/rg"
PLUGIN_CFLAGS="$PLUGIN_CFLAGS -I\$(top_srcdir)/contrib/search"
PLUGIN_CFLAGS="$PLUGIN_CFLAGS -I\$(top_srcdir)/contrib/xml"
@@ -423,6 +426,7 @@ PLUGIN_VALAFLAGS="$PLUGIN_VALAFLAGS --target-glib=2.44"
PLUGIN_VALAFLAGS="$PLUGIN_VALAFLAGS --thread"
PLUGIN_VALAFLAGS="$PLUGIN_VALAFLAGS --vapidir \$(top_builddir)/libide"
PLUGIN_VALAFLAGS="$PLUGIN_VALAFLAGS --vapidir \$(top_builddir)/contrib/egg"
+PLUGIN_VALAFLAGS="$PLUGIN_VALAFLAGS --vapidir \$(top_builddir)/contrib/pnl"
PLUGIN_VALAFLAGS="$PLUGIN_VALAFLAGS --vapidir \$(top_builddir)/contrib/tmpl"
PLUGIN_VALAFLAGS="$PLUGIN_VALAFLAGS --pkg libide-1.0"
PLUGIN_VALAFLAGS="$PLUGIN_VALAFLAGS --pkg libpeas-1.0"
@@ -443,6 +447,8 @@ AC_CONFIG_FILES([
contrib/gd/Makefile
contrib/libeditorconfig/Makefile
contrib/nautilus/Makefile
+ contrib/pnl/Makefile
+ contrib/pnl/pnl-version.h
contrib/rg/Makefile
contrib/search/Makefile
contrib/tmpl/Makefile
diff --git a/contrib/Makefile.am b/contrib/Makefile.am
index 9ed79c4..516886b 100644
--- a/contrib/Makefile.am
+++ b/contrib/Makefile.am
@@ -3,6 +3,7 @@ SUBDIRS = \
gd \
libeditorconfig \
nautilus \
+ pnl \
rg \
search \
tmpl \
diff --git a/contrib/pnl/Makefile.am b/contrib/pnl/Makefile.am
new file mode 100644
index 0000000..bfc42e0
--- /dev/null
+++ b/contrib/pnl/Makefile.am
@@ -0,0 +1,136 @@
+CLEANFILES =
+DISTCLEANFILES =
+EXTRA_DIST =
+BUILT_SOURCES =
+
+pkglibdir = $(libdir)/gnome-builder
+pkglib_LTLIBRARIES = libpanel-gtk.la
+
+headersdir = $(includedir)/gnome-builder- VERSION@/pnl
+headers_DATA = \
+ pnl-animation.h \
+ pnl-dock-bin.h \
+ pnl-dock-bin-edge.h \
+ pnl-dock-item.h \
+ pnl-dock-manager.h \
+ pnl-dock-overlay.h \
+ pnl-dock-paned.h \
+ pnl-dock-revealer.h \
+ pnl-dock-stack.h \
+ pnl-dock-types.h \
+ pnl-dock-widget.h \
+ pnl-dock-window.h \
+ pnl-dock.h \
+ pnl-frame-source.h \
+ pnl-multi-paned.h \
+ pnl-tab-strip.h \
+ pnl-tab.h \
+ pnl-version.h \
+ pnl.h \
+ $(NULL)
+
+libpanel_gtk_la_SOURCES = \
+ $(headers_DATA) \
+ pnl-animation.c \
+ pnl-dock-bin-edge.c \
+ pnl-dock-bin-edge-private.h \
+ pnl-dock-bin.c \
+ pnl-dock-item.c \
+ pnl-dock-manager.c \
+ pnl-dock-overlay-edge-private.h \
+ pnl-dock-overlay-edge.c \
+ pnl-dock-overlay.c \
+ pnl-dock-paned-private.h \
+ pnl-dock-paned.c \
+ pnl-dock-revealer.c \
+ pnl-dock-stack.c \
+ pnl-dock-tab-strip.c \
+ pnl-dock-tab-strip.h \
+ pnl-dock-transient-grab.c \
+ pnl-dock-transient-grab.h \
+ pnl-dock-widget.c \
+ pnl-dock-window.c \
+ pnl-dock.c \
+ pnl-frame-source.c \
+ pnl-multi-paned.c \
+ pnl-tab-strip.c \
+ pnl-tab.c \
+ pnl-util-private.h \
+ pnl-util.c \
+ $(NULL)
+
+nodist_libpanel_gtk_la_SOURCES = pnl-resources.c pnl-resources.h
+
+libpanel_gtk_la_CFLAGS = \
+ -DPNL_COMPILATION \
+ $(PANEL_GTK_CFLAGS) \
+ $(NULL)
+
+libpanel_gtk_la_LIBADD = \
+ $(PANEL_GTK_LIBS) \
+ $(NULL)
+
+libpanel_gtk_la_LDFLAGS = \
+ --no-undefined \
+ $(NULL)
+
+
+glib_resources_h = pnl-resources.h
+glib_resources_c = pnl-resources.c
+glib_resources_xml = pnl.gresource.xml
+glib_resources_namespace = pnl
+include $(top_srcdir)/build/autotools/Makefile.am.gresources
+
+
+if HAVE_INTROSPECTION
+-include $(INTROSPECTION_MAKEFILE)
+
+INTROSPECTION_GIRS =
+INTROSPECTION_SCANNER_ARGS = --add-include-path=$(srcdir) --warn-all
+INTROSPECTION_COMPILER_ARGS = --includedir=$(srcdir)
+
+Pnl-1.0.gir: libpanel-gtk.la
+Pnl_1_0_gir_INCLUDES = Gio-2.0 Gdk-3.0 Gtk-3.0
+Pnl_1_0_gir_CFLAGS = $(libpanel_gtk_la_CFLAGS) -DPNL_COMPILATION
+Pnl_1_0_gir_LIBS = libpanel-gtk.la
+Pnl_1_0_gir_FILES = $(libpanel_gtk_la_SOURCES)
+Pnl_1_0_gir_SCANNERFLAGS = \
+ --c-include="pnl.h" \
+ -n Pnl \
+ $(NULL)
+INTROSPECTION_GIRS += Pnl-1.0.gir
+
+girdir = $(datadir)/gnome-builder/gir-1.0
+dist_gir_DATA = $(INTROSPECTION_GIRS)
+
+typelibdir = $(pkglibdir)/girepository-1.0
+typelib_DATA = $(INTROSPECTION_GIRS:.gir=.typelib)
+
+CLEANFILES += $(dist_gir_DATA) $(typelib_DATA)
+endif
+
+
+if ENABLE_VAPIGEN
+-include $(VAPIGEN_MAKEFILE)
+
+panel-gtk.vapi: Pnl-1.0.gir
+
+VAPIGEN_VAPIS = panel-gtk.vapi
+
+panel_gtk_vapi_DEPS = gio-2.0 gtk+-3.0
+panel_gtk_vapi_METADATADIRS = $(srcdir)
+panel_gtk_vapi_FILES = Pnl-1.0.gir
+
+panel-gtk.deps: Makefile
+ $(AM_V_GEN) echo $(libpanel_gtk_vapi_DEPS) | tr ' ' '\n' > $@
+
+vapidir = $(datadir)/gnome-builder/vapi
+vapi_DATA = $(VAPIGEN_VAPIS) $(VAPIGEN_VAPIS:.vapi=.deps)
+
+EXTRA_DIST += panel-gtk.deps
+
+DISTCLEANFILES += $(vapi_DATA)
+endif
+
+
+-include $(top_srcdir)/git.mk
diff --git a/contrib/pnl/pnl-animation.c b/contrib/pnl/pnl-animation.c
new file mode 100644
index 0000000..e0a15d6
--- /dev/null
+++ b/contrib/pnl/pnl-animation.c
@@ -0,0 +1,1164 @@
+/* pnl-animation.c
+ *
+ * Copyright (C) 2010-2016 Christian Hergert <christian hergert me>
+ *
+ * This file 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.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file 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 General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+#include <gobject/gvaluecollector.h>
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "pnl-animation.h"
+#include "pnl-frame-source.h"
+
+#define FALLBACK_FRAME_RATE 60
+
+typedef gdouble (*AlphaFunc) (gdouble offset);
+typedef void (*TweenFunc) (const GValue *begin,
+ const GValue *end,
+ GValue *value,
+ gdouble offset);
+
+typedef struct
+{
+ gboolean is_child; /* Does GParamSpec belong to parent widget */
+ GParamSpec *pspec; /* GParamSpec of target property */
+ GValue begin; /* Begin value in animation */
+ GValue end; /* End value in animation */
+} Tween;
+
+
+struct _PnlAnimation
+{
+ GInitiallyUnowned parent_instance;
+
+ gpointer target; /* Target object to animate */
+ guint64 begin_msec; /* Time in which animation started */
+ guint duration_msec; /* Duration of animation */
+ guint mode; /* Tween mode */
+ gulong tween_handler; /* GSource or signal handler */
+ gdouble last_offset; /* Track our last offset */
+ GArray *tweens; /* Array of tweens to perform */
+ GdkFrameClock *frame_clock; /* An optional frame-clock for sync. */
+ GDestroyNotify notify; /* Notify callback */
+ gpointer notify_data; /* Data for notify */
+ guint debug_ticks; /* Number of tick updates */
+};
+
+G_DEFINE_TYPE (PnlAnimation, pnl_animation, G_TYPE_INITIALLY_UNOWNED)
+
+enum {
+ PROP_0,
+ PROP_DURATION,
+ PROP_FRAME_CLOCK,
+ PROP_MODE,
+ PROP_TARGET,
+ N_PROPS
+};
+
+
+enum {
+ TICK,
+ N_SIGNALS
+};
+
+
+/*
+ * Helper macros.
+ */
+#define LAST_FUNDAMENTAL 64
+#define TWEEN(type) \
+ static void \
+ tween_ ## type (const GValue * begin, \
+ const GValue * end, \
+ GValue * value, \
+ gdouble offset) \
+ { \
+ g ## type x = g_value_get_ ## type (begin); \
+ g ## type y = g_value_get_ ## type (end); \
+ g_value_set_ ## type (value, x + ((y - x) * offset)); \
+ }
+
+
+/*
+ * Globals.
+ */
+static AlphaFunc alpha_funcs[PNL_ANIMATION_LAST];
+static gboolean debug;
+static GParamSpec *properties[N_PROPS];
+static guint signals[N_SIGNALS];
+static TweenFunc tween_funcs[LAST_FUNDAMENTAL];
+static guint slow_down_factor = 1;
+
+
+/*
+ * Tweeners for basic types.
+ */
+TWEEN (int);
+TWEEN (uint);
+TWEEN (long);
+TWEEN (ulong);
+TWEEN (float);
+TWEEN (double);
+
+
+/**
+ * pnl_animation_alpha_ease_in_cubic:
+ * @offset: (in): The position within the animation; 0.0 to 1.0.
+ *
+ * An alpha function to transform the offset within the animation.
+ * @PNL_ANIMATION_CUBIC means the valu ewill be transformed into
+ * cubic acceleration (x * x * x).
+ */
+static gdouble
+pnl_animation_alpha_ease_in_cubic (gdouble offset)
+{
+ return offset * offset * offset;
+}
+
+
+static gdouble
+pnl_animation_alpha_ease_out_cubic (gdouble offset)
+{
+ gdouble p = offset - 1.0;
+
+ return p * p * p + 1.0;
+}
+
+static gdouble
+pnl_animation_alpha_ease_in_out_cubic (gdouble offset)
+{
+ if (offset < .5)
+ return pnl_animation_alpha_ease_in_cubic (offset * 2.0) / 2.0;
+ else
+ return .5 + pnl_animation_alpha_ease_out_cubic ((offset - .5) * 2.0) / 2.0;
+}
+
+
+/**
+ * pnl_animation_alpha_linear:
+ * @offset: (in): The position within the animation; 0.0 to 1.0.
+ *
+ * An alpha function to transform the offset within the animation.
+ * @PNL_ANIMATION_LINEAR means no tranformation will be made.
+ *
+ * Returns: @offset.
+ * Side effects: None.
+ */
+static gdouble
+pnl_animation_alpha_linear (gdouble offset)
+{
+ return offset;
+}
+
+
+/**
+ * pnl_animation_alpha_ease_in_quad:
+ * @offset: (in): The position within the animation; 0.0 to 1.0.
+ *
+ * An alpha function to transform the offset within the animation.
+ * @PNL_ANIMATION_EASE_IN_QUAD means that the value will be transformed
+ * into a quadratic acceleration.
+ *
+ * Returns: A tranformation of @offset.
+ * Side effects: None.
+ */
+static gdouble
+pnl_animation_alpha_ease_in_quad (gdouble offset)
+{
+ return offset * offset;
+}
+
+
+/**
+ * pnl_animation_alpha_ease_out_quad:
+ * @offset: (in): The position within the animation; 0.0 to 1.0.
+ *
+ * An alpha function to transform the offset within the animation.
+ * @PNL_ANIMATION_EASE_OUT_QUAD means that the value will be transformed
+ * into a quadratic deceleration.
+ *
+ * Returns: A tranformation of @offset.
+ * Side effects: None.
+ */
+static gdouble
+pnl_animation_alpha_ease_out_quad (gdouble offset)
+{
+ return -1.0 * offset * (offset - 2.0);
+}
+
+
+/**
+ * pnl_animation_alpha_ease_in_out_quad:
+ * @offset: (in): The position within the animation; 0.0 to 1.0.
+ *
+ * An alpha function to transform the offset within the animation.
+ * @PNL_ANIMATION_EASE_IN_OUT_QUAD means that the value will be transformed
+ * into a quadratic acceleration for the first half, and quadratic
+ * deceleration the second half.
+ *
+ * Returns: A tranformation of @offset.
+ * Side effects: None.
+ */
+static gdouble
+pnl_animation_alpha_ease_in_out_quad (gdouble offset)
+{
+ offset *= 2.0;
+ if (offset < 1.0)
+ return 0.5 * offset * offset;
+ offset -= 1.0;
+ return -0.5 * (offset * (offset - 2.0) - 1.0);
+}
+
+
+/**
+ * pnl_animation_load_begin_values:
+ * @animation: (in): A #PnlAnimation.
+ *
+ * Load the begin values for all the properties we are about to
+ * animate.
+ *
+ * Side effects: None.
+ */
+static void
+pnl_animation_load_begin_values (PnlAnimation *animation)
+{
+ GtkContainer *container;
+ Tween *tween;
+ guint i;
+
+ g_return_if_fail (PNL_IS_ANIMATION (animation));
+
+ for (i = 0; i < animation->tweens->len; i++)
+ {
+ tween = &g_array_index (animation->tweens, Tween, i);
+ g_value_reset (&tween->begin);
+ if (tween->is_child)
+ {
+ container = GTK_CONTAINER (gtk_widget_get_parent (animation->target));
+ gtk_container_child_get_property (container,
+ animation->target,
+ tween->pspec->name,
+ &tween->begin);
+ }
+ else
+ {
+ g_object_get_property (animation->target,
+ tween->pspec->name,
+ &tween->begin);
+ }
+ }
+}
+
+
+/**
+ * pnl_animation_unload_begin_values:
+ * @animation: (in): A #PnlAnimation.
+ *
+ * Unloads the begin values for the animation. This might be particularly
+ * useful once we support pointer types.
+ *
+ * Side effects: None.
+ */
+static void
+pnl_animation_unload_begin_values (PnlAnimation *animation)
+{
+ Tween *tween;
+ guint i;
+
+ g_return_if_fail (PNL_IS_ANIMATION (animation));
+
+ for (i = 0; i < animation->tweens->len; i++)
+ {
+ tween = &g_array_index (animation->tweens, Tween, i);
+ g_value_reset (&tween->begin);
+ }
+}
+
+
+/**
+ * pnl_animation_get_offset:
+ * @animation: A #PnlAnimation.
+ * @frame_time: the time to present the frame, or 0 for current timing.
+ *
+ * Retrieves the position within the animation from 0.0 to 1.0. This
+ * value is calculated using the msec of the beginning of the animation
+ * and the current time.
+ *
+ * Returns: The offset of the animation from 0.0 to 1.0.
+ */
+static gdouble
+pnl_animation_get_offset (PnlAnimation *animation,
+ gint64 frame_time)
+{
+ gdouble offset;
+ gint64 frame_msec;
+
+ g_return_val_if_fail (PNL_IS_ANIMATION (animation), 0.0);
+
+ if (frame_time == 0)
+ {
+ if (animation->frame_clock != NULL)
+ frame_time = gdk_frame_clock_get_frame_time (animation->frame_clock);
+ else
+ frame_time = g_get_monotonic_time ();
+ }
+
+ frame_msec = frame_time / 1000L;
+
+ offset = (gdouble) (frame_msec - animation->begin_msec) /
+ (gdouble) MAX (animation->duration_msec, 1);
+
+ return CLAMP (offset, 0.0, 1.0);
+}
+
+
+/**
+ * pnl_animation_update_property:
+ * @animation: (in): A #PnlAnimation.
+ * @target: (in): A #GObject.
+ * @tween: (in): a #Tween containing the property.
+ * @value: (in): The new value for the property.
+ *
+ * Updates the value of a property on an object using @value.
+ *
+ * Side effects: The property of @target is updated.
+ */
+static void
+pnl_animation_update_property (PnlAnimation *animation,
+ gpointer target,
+ Tween *tween,
+ const GValue *value)
+{
+ g_assert (PNL_IS_ANIMATION (animation));
+ g_assert (G_IS_OBJECT (target));
+ g_assert (tween);
+ g_assert (value);
+
+ g_object_set_property (target, tween->pspec->name, value);
+}
+
+
+/**
+ * pnl_animation_update_child_property:
+ * @animation: (in): A #PnlAnimation.
+ * @target: (in): A #GObject.
+ * @tween: (in): A #Tween containing the property.
+ * @value: (in): The new value for the property.
+ *
+ * Updates the value of the parent widget of the target to @value.
+ *
+ * Side effects: The property of @target<!-- -->'s parent widget is updated.
+ */
+static void
+pnl_animation_update_child_property (PnlAnimation *animation,
+ gpointer target,
+ Tween *tween,
+ const GValue *value)
+{
+ GtkWidget *parent;
+
+ g_assert (PNL_IS_ANIMATION (animation));
+ g_assert (G_IS_OBJECT (target));
+ g_assert (tween);
+ g_assert (value);
+
+ parent = gtk_widget_get_parent (GTK_WIDGET (target));
+ gtk_container_child_set_property (GTK_CONTAINER (parent),
+ target,
+ tween->pspec->name,
+ value);
+}
+
+
+/**
+ * pnl_animation_get_value_at_offset:
+ * @animation: (in): A #PnlAnimation.
+ * @offset: (in): The offset in the animation from 0.0 to 1.0.
+ * @tween: (in): A #Tween containing the property.
+ * @value: (out): A #GValue in which to store the property.
+ *
+ * Retrieves a value for a particular position within the animation.
+ *
+ * Side effects: None.
+ */
+static void
+pnl_animation_get_value_at_offset (PnlAnimation *animation,
+ gdouble offset,
+ Tween *tween,
+ GValue *value)
+{
+ g_return_if_fail (PNL_IS_ANIMATION (animation));
+ g_return_if_fail (tween != NULL);
+ g_return_if_fail (value != NULL);
+ g_return_if_fail (value->g_type == tween->pspec->value_type);
+
+ if (value->g_type < LAST_FUNDAMENTAL)
+ {
+ /*
+ * If you hit the following assertion, you need to add a function
+ * to create the new value at the given offset.
+ */
+ g_assert (tween_funcs[value->g_type]);
+ tween_funcs[value->g_type](&tween->begin, &tween->end, value, offset);
+ }
+ else
+ {
+ /*
+ * TODO: Support complex transitions.
+ */
+ if (offset >= 1.0)
+ g_value_copy (&tween->end, value);
+ }
+}
+
+static void
+pnl_animation_set_frame_clock (PnlAnimation *animation,
+ GdkFrameClock *frame_clock)
+{
+ if (animation->frame_clock != frame_clock)
+ {
+ g_clear_object (&animation->frame_clock);
+ animation->frame_clock = frame_clock ? g_object_ref (frame_clock) : NULL;
+ }
+}
+
+static void
+pnl_animation_set_target (PnlAnimation *animation,
+ gpointer target)
+{
+ g_assert (!animation->target);
+
+ animation->target = g_object_ref (target);
+
+ if (GTK_IS_WIDGET (animation->target))
+ pnl_animation_set_frame_clock (animation,
+ gtk_widget_get_frame_clock (animation->target));
+}
+
+
+/**
+ * pnl_animation_tick:
+ * @animation: (in): A #PnlAnimation.
+ *
+ * Moves the object properties to the next position in the animation.
+ *
+ * Returns: %TRUE if the animation has not completed; otherwise %FALSE.
+ * Side effects: None.
+ */
+static gboolean
+pnl_animation_tick (PnlAnimation *animation,
+ gdouble offset)
+{
+ gdouble alpha;
+ GValue value = { 0 };
+ Tween *tween;
+ guint i;
+
+ g_return_val_if_fail (PNL_IS_ANIMATION (animation), FALSE);
+
+ if (offset == animation->last_offset)
+ return offset < 1.0;
+
+ animation->debug_ticks++;
+
+ alpha = alpha_funcs[animation->mode](offset);
+
+ /*
+ * Update property values.
+ */
+ for (i = 0; i < animation->tweens->len; i++)
+ {
+ tween = &g_array_index (animation->tweens, Tween, i);
+ g_value_init (&value, tween->pspec->value_type);
+ pnl_animation_get_value_at_offset (animation, alpha, tween, &value);
+ if (!tween->is_child)
+ {
+ pnl_animation_update_property (animation,
+ animation->target,
+ tween,
+ &value);
+ }
+ else
+ {
+ pnl_animation_update_child_property (animation,
+ animation->target,
+ tween,
+ &value);
+ }
+ g_value_unset (&value);
+ }
+
+ /*
+ * Notify anyone interested in the tick signal.
+ */
+ g_signal_emit (animation, signals[TICK], 0);
+
+ /*
+ * Flush any outstanding events to the graphics server (in the case of X).
+ */
+#if !GTK_CHECK_VERSION (3, 13, 0)
+ if (GTK_IS_WIDGET (animation->target))
+ {
+ GdkWindow *window;
+
+ if ((window = gtk_widget_get_window (GTK_WIDGET (animation->target))))
+ gdk_window_flush (window);
+ }
+#endif
+
+ animation->last_offset = offset;
+
+ return offset < 1.0;
+}
+
+
+/**
+ * pnl_animation_timeout_cb:
+ * @user_data: (in): A #PnlAnimation.
+ *
+ * Timeout from the main loop to move to the next step of the animation.
+ *
+ * Returns: %TRUE until the animation has completed; otherwise %FALSE.
+ * Side effects: None.
+ */
+static gboolean
+pnl_animation_timeout_cb (gpointer user_data)
+{
+ PnlAnimation *animation = user_data;
+ gboolean ret;
+ gdouble offset;
+
+ offset = pnl_animation_get_offset (animation, 0);
+
+ if (!(ret = pnl_animation_tick (animation, offset)))
+ pnl_animation_stop (animation);
+
+ return ret;
+}
+
+
+static gboolean
+pnl_animation_widget_tick_cb (GdkFrameClock *frame_clock,
+ PnlAnimation *animation)
+{
+ gboolean ret = G_SOURCE_REMOVE;
+
+ g_assert (GDK_IS_FRAME_CLOCK (frame_clock));
+ g_assert (PNL_IS_ANIMATION (animation));
+
+ if (animation->tween_handler)
+ {
+ gdouble offset;
+
+ offset = pnl_animation_get_offset (animation, 0);
+
+ if (!(ret = pnl_animation_tick (animation, offset)))
+ pnl_animation_stop (animation);
+ }
+
+ return ret;
+}
+
+
+/**
+ * pnl_animation_start:
+ * @animation: (in): A #PnlAnimation.
+ *
+ * Start the animation. When the animation stops, the internal reference will
+ * be dropped and the animation may be finalized.
+ *
+ * Side effects: None.
+ */
+void
+pnl_animation_start (PnlAnimation *animation)
+{
+ g_return_if_fail (PNL_IS_ANIMATION (animation));
+ g_return_if_fail (!animation->tween_handler);
+
+ g_object_ref_sink (animation);
+ pnl_animation_load_begin_values (animation);
+
+ if (animation->frame_clock)
+ {
+ animation->begin_msec = gdk_frame_clock_get_frame_time (animation->frame_clock) / 1000UL;
+ animation->tween_handler =
+ g_signal_connect (animation->frame_clock,
+ "update",
+ G_CALLBACK (pnl_animation_widget_tick_cb),
+ animation);
+ gdk_frame_clock_begin_updating (animation->frame_clock);
+ }
+ else
+ {
+ animation->begin_msec = g_get_monotonic_time () / 1000UL;
+ animation->tween_handler = pnl_frame_source_add (FALLBACK_FRAME_RATE,
+ pnl_animation_timeout_cb,
+ animation);
+ }
+}
+
+
+static void
+pnl_animation_notify (PnlAnimation *self)
+{
+ g_assert (PNL_IS_ANIMATION (self));
+
+ if (self->notify != NULL)
+ {
+ GDestroyNotify notify = self->notify;
+ gpointer data = self->notify_data;
+
+ self->notify = NULL;
+ self->notify_data = NULL;
+
+ notify (data);
+ }
+}
+
+
+/**
+ * pnl_animation_stop:
+ * @animation: (in): A #PnlAnimation.
+ *
+ * Stops a running animation. The internal reference to the animation is
+ * dropped and therefore may cause the object to finalize.
+ *
+ * Side effects: None.
+ */
+void
+pnl_animation_stop (PnlAnimation *animation)
+{
+ g_return_if_fail (PNL_IS_ANIMATION (animation));
+
+ if (animation->tween_handler)
+ {
+ if (animation->frame_clock)
+ {
+ gdk_frame_clock_end_updating (animation->frame_clock);
+ g_signal_handler_disconnect (animation->frame_clock, animation->tween_handler);
+ animation->tween_handler = 0;
+ }
+ else
+ {
+ g_source_remove (animation->tween_handler);
+ animation->tween_handler = 0;
+ }
+ pnl_animation_unload_begin_values (animation);
+ pnl_animation_notify (animation);
+ g_object_unref (animation);
+ }
+}
+
+
+/**
+ * pnl_animation_add_property:
+ * @animation: (in): A #PnlAnimation.
+ * @pspec: (in): A #ParamSpec of @target or a #GtkWidget<!-- -->'s parent.
+ * @value: (in): The new value for the property at the end of the animation.
+ *
+ * Adds a new property to the set of properties to be animated during the
+ * lifetime of the animation.
+ *
+ * Side effects: None.
+ */
+void
+pnl_animation_add_property (PnlAnimation *animation,
+ GParamSpec *pspec,
+ const GValue *value)
+{
+ Tween tween = { 0 };
+ GType type;
+
+ g_return_if_fail (PNL_IS_ANIMATION (animation));
+ g_return_if_fail (pspec != NULL);
+ g_return_if_fail (value != NULL);
+ g_return_if_fail (value->g_type);
+ g_return_if_fail (animation->target);
+ g_return_if_fail (!animation->tween_handler);
+
+ type = G_TYPE_FROM_INSTANCE (animation->target);
+ tween.is_child = !g_type_is_a (type, pspec->owner_type);
+ if (tween.is_child)
+ {
+ if (!GTK_IS_WIDGET (animation->target))
+ {
+ g_critical (_("Cannot locate property %s in class %s"),
+ pspec->name, g_type_name (type));
+ return;
+ }
+ }
+
+ tween.pspec = g_param_spec_ref (pspec);
+ g_value_init (&tween.begin, pspec->value_type);
+ g_value_init (&tween.end, pspec->value_type);
+ g_value_copy (value, &tween.end);
+ g_array_append_val (animation->tweens, tween);
+}
+
+
+/**
+ * pnl_animation_dispose:
+ * @object: (in): A #PnlAnimation.
+ *
+ * Releases any object references the animation contains.
+ *
+ * Side effects: None.
+ */
+static void
+pnl_animation_dispose (GObject *object)
+{
+ PnlAnimation *self = PNL_ANIMATION (object);
+
+ g_clear_object (&self->target);
+ g_clear_object (&self->frame_clock);
+
+ G_OBJECT_CLASS (pnl_animation_parent_class)->dispose (object);
+}
+
+
+/**
+ * pnl_animation_finalize:
+ * @object: (in): A #PnlAnimation.
+ *
+ * Finalizes the object and releases any resources allocated.
+ *
+ * Side effects: None.
+ */
+static void
+pnl_animation_finalize (GObject *object)
+{
+ PnlAnimation *self = PNL_ANIMATION (object);
+ Tween *tween;
+ guint i;
+
+ for (i = 0; i < self->tweens->len; i++)
+ {
+ tween = &g_array_index (self->tweens, Tween, i);
+ g_value_unset (&tween->begin);
+ g_value_unset (&tween->end);
+ g_param_spec_unref (tween->pspec);
+ }
+
+ g_array_unref (self->tweens);
+
+ if (debug)
+ g_message ("%u tick updates, expected %d",
+ self->debug_ticks,
+ (int)(self->duration_msec / (1000.0 / FALLBACK_FRAME_RATE)));
+
+ G_OBJECT_CLASS (pnl_animation_parent_class)->finalize (object);
+}
+
+
+/**
+ * pnl_animation_set_property:
+ * @object: (in): A #GObject.
+ * @prop_id: (in): The property identifier.
+ * @value: (in): The given property.
+ * @pspec: (in): A #ParamSpec.
+ *
+ * Set a given #GObject property.
+ */
+static void
+pnl_animation_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PnlAnimation *animation = PNL_ANIMATION (object);
+
+ switch (prop_id)
+ {
+ case PROP_DURATION:
+ animation->duration_msec = g_value_get_uint (value) * slow_down_factor;
+ break;
+
+ case PROP_FRAME_CLOCK:
+ pnl_animation_set_frame_clock (animation, g_value_get_object (value));
+ break;
+
+ case PROP_MODE:
+ animation->mode = g_value_get_enum (value);
+ break;
+
+ case PROP_TARGET:
+ pnl_animation_set_target (animation, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+
+/**
+ * pnl_animation_class_init:
+ * @klass: (in): A #PnlAnimationClass.
+ *
+ * Initializes the GObjectClass.
+ *
+ * Side effects: Properties, signals, and vtables are initialized.
+ */
+static void
+pnl_animation_class_init (PnlAnimationClass *klass)
+{
+ GObjectClass *object_class;
+ const gchar *slow_down_factor_env;
+
+ debug = !!g_getenv ("PNL_ANIMATION_DEBUG");
+ slow_down_factor_env = g_getenv ("PNL_ANIMATION_SLOW_DOWN_FACTOR");
+
+ if (slow_down_factor_env)
+ slow_down_factor = MAX (1, atoi (slow_down_factor_env));
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->dispose = pnl_animation_dispose;
+ object_class->finalize = pnl_animation_finalize;
+ object_class->set_property = pnl_animation_set_property;
+
+ /**
+ * PnlAnimation:duration:
+ *
+ * The "duration" property is the total number of milliseconds that the
+ * animation should run before being completed.
+ */
+ properties[PROP_DURATION] =
+ g_param_spec_uint ("duration",
+ "Duration",
+ "The duration of the animation",
+ 0,
+ G_MAXUINT,
+ 250,
+ (G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ properties[PROP_FRAME_CLOCK] =
+ g_param_spec_object ("frame-clock",
+ "Frame Clock",
+ "An optional frame-clock to synchronize with.",
+ GDK_TYPE_FRAME_CLOCK,
+ (G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * PnlAnimation:mode:
+ *
+ * The "mode" property is the Alpha function that should be used to
+ * determine the offset within the animation based on the current
+ * offset in the animations duration.
+ */
+ properties[PROP_MODE] =
+ g_param_spec_enum ("mode",
+ "Mode",
+ "The animation mode",
+ PNL_TYPE_ANIMATION_MODE,
+ PNL_ANIMATION_LINEAR,
+ (G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ /**
+ * PnlAnimation:target:
+ *
+ * The "target" property is the #GObject that should have it's properties
+ * animated.
+ */
+ properties[PROP_TARGET] =
+ g_param_spec_object ("target",
+ "Target",
+ "The target of the animation",
+ G_TYPE_OBJECT,
+ (G_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ /**
+ * PnlAnimation::tick:
+ *
+ * The "tick" signal is emitted on each frame in the animation.
+ */
+ signals[TICK] = g_signal_new ("tick",
+ PNL_TYPE_ANIMATION,
+ G_SIGNAL_RUN_FIRST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 0);
+
+#define SET_ALPHA(_T, _t) \
+ alpha_funcs[PNL_ANIMATION_ ## _T] = pnl_animation_alpha_ ## _t
+
+ SET_ALPHA (LINEAR, linear);
+ SET_ALPHA (EASE_IN_QUAD, ease_in_quad);
+ SET_ALPHA (EASE_OUT_QUAD, ease_out_quad);
+ SET_ALPHA (EASE_IN_OUT_QUAD, ease_in_out_quad);
+ SET_ALPHA (EASE_IN_CUBIC, ease_in_cubic);
+ SET_ALPHA (EASE_OUT_CUBIC, ease_out_cubic);
+ SET_ALPHA (EASE_IN_OUT_CUBIC, ease_in_out_cubic);
+
+#define SET_TWEEN(_T, _t) \
+ G_STMT_START { \
+ guint idx = G_TYPE_ ## _T; \
+ tween_funcs[idx] = tween_ ## _t; \
+ } G_STMT_END
+
+ SET_TWEEN (INT, int);
+ SET_TWEEN (UINT, uint);
+ SET_TWEEN (LONG, long);
+ SET_TWEEN (ULONG, ulong);
+ SET_TWEEN (FLOAT, float);
+ SET_TWEEN (DOUBLE, double);
+}
+
+
+/**
+ * pnl_animation_init:
+ * @animation: (in): A #PnlAnimation.
+ *
+ * Initializes the #PnlAnimation instance.
+ *
+ * Side effects: Everything.
+ */
+static void
+pnl_animation_init (PnlAnimation *animation)
+{
+ animation->duration_msec = 250;
+ animation->mode = PNL_ANIMATION_EASE_IN_OUT_QUAD;
+ animation->tweens = g_array_new (FALSE, FALSE, sizeof (Tween));
+ animation->last_offset = -G_MINDOUBLE;
+}
+
+
+/**
+ * pnl_animation_mode_get_type:
+ *
+ * Retrieves the GType for #PnlAnimationMode.
+ *
+ * Returns: A GType.
+ * Side effects: GType registered on first call.
+ */
+GType
+pnl_animation_mode_get_type (void)
+{
+ static GType type_id = 0;
+ static const GEnumValue values[] = {
+ { PNL_ANIMATION_LINEAR, "PNL_ANIMATION_LINEAR", "linear" },
+ { PNL_ANIMATION_EASE_IN_QUAD, "PNL_ANIMATION_EASE_IN_QUAD", "ease-in-quad" },
+ { PNL_ANIMATION_EASE_IN_OUT_QUAD, "PNL_ANIMATION_EASE_IN_OUT_QUAD", "ease-in-out-quad" },
+ { PNL_ANIMATION_EASE_OUT_QUAD, "PNL_ANIMATION_EASE_OUT_QUAD", "ease-out-quad" },
+ { PNL_ANIMATION_EASE_IN_CUBIC, "PNL_ANIMATION_EASE_IN_CUBIC", "ease-in-cubic" },
+ { PNL_ANIMATION_EASE_OUT_CUBIC, "PNL_ANIMATION_EASE_OUT_CUBIC", "ease-out-cubic" },
+ { PNL_ANIMATION_EASE_IN_OUT_CUBIC, "PNL_ANIMATION_EASE_IN_OUT_CUBIC", "ease-in-out-cubic" },
+ { 0 }
+ };
+
+ if (G_UNLIKELY (!type_id))
+ type_id = g_enum_register_static ("PnlAnimationMode", values);
+ return type_id;
+}
+
+/**
+ * pnl_object_animatev:
+ * @object: A #GObject.
+ * @mode: The animation mode.
+ * @duration_msec: The duration in milliseconds.
+ * @frame_clock: The target frame rate.
+ * @first_property: The first property to animate.
+ * @args: A variadac list of arguments
+ *
+ * Returns: (transfer none): A #PnlAnimation.
+ */
+PnlAnimation *
+pnl_object_animatev (gpointer object,
+ PnlAnimationMode mode,
+ guint duration_msec,
+ GdkFrameClock *frame_clock,
+ const gchar *first_property,
+ va_list args)
+{
+ PnlAnimation *animation;
+ GObjectClass *klass;
+ GObjectClass *pklass;
+ const gchar *name;
+ GParamSpec *pspec;
+ GtkWidget *parent;
+ GValue value = { 0 };
+ gchar *error = NULL;
+ GType type;
+ GType ptype;
+ gboolean enable_animations;
+
+ g_return_val_if_fail (first_property != NULL, NULL);
+ g_return_val_if_fail (mode < PNL_ANIMATION_LAST, NULL);
+
+ if ((frame_clock == NULL) && GTK_IS_WIDGET (object))
+ frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (object));
+
+ /*
+ * If we have a frame clock, then we must be in the gtk thread and we
+ * should check GtkSettings for disabled animations. If we are disabled,
+ * we will just make the timeout immediate.
+ */
+ if (frame_clock != NULL)
+ {
+ g_object_get (gtk_settings_get_default (),
+ "gtk-enable-animations", &enable_animations,
+ NULL);
+
+ if (enable_animations == FALSE)
+ duration_msec = 0;
+ }
+
+ name = first_property;
+ type = G_TYPE_FROM_INSTANCE (object);
+ klass = G_OBJECT_GET_CLASS (object);
+ animation = g_object_new (PNL_TYPE_ANIMATION,
+ "duration", duration_msec,
+ "frame-clock", frame_clock,
+ "mode", mode,
+ "target", object,
+ NULL);
+
+ do
+ {
+ /*
+ * First check for the property on the object. If that does not exist
+ * then check if the object has a parent and look at its child
+ * properties (if its a GtkWidget).
+ */
+ if (!(pspec = g_object_class_find_property (klass, name)))
+ {
+ if (!g_type_is_a (type, GTK_TYPE_WIDGET))
+ {
+ g_critical (_("Failed to find property %s in %s"),
+ name, g_type_name (type));
+ goto failure;
+ }
+ if (!(parent = gtk_widget_get_parent (object)))
+ {
+ g_critical (_("Failed to find property %s in %s"),
+ name, g_type_name (type));
+ goto failure;
+ }
+ pklass = G_OBJECT_GET_CLASS (parent);
+ ptype = G_TYPE_FROM_INSTANCE (parent);
+ if (!(pspec = gtk_container_class_find_child_property (pklass, name)))
+ {
+ g_critical (_("Failed to find property %s in %s or parent %s"),
+ name, g_type_name (type), g_type_name (ptype));
+ goto failure;
+ }
+ }
+
+ g_value_init (&value, pspec->value_type);
+ G_VALUE_COLLECT (&value, args, 0, &error);
+ if (error != NULL)
+ {
+ g_critical (_("Failed to retrieve va_list value: %s"), error);
+ g_free (error);
+ goto failure;
+ }
+
+ pnl_animation_add_property (animation, pspec, &value);
+ g_value_unset (&value);
+ }
+ while ((name = va_arg (args, const gchar *)));
+
+ pnl_animation_start (animation);
+
+ return animation;
+
+failure:
+ g_object_ref_sink (animation);
+ g_object_unref (animation);
+ return NULL;
+}
+
+/**
+ * pnl_object_animate:
+ * @object: (in): A #GObject.
+ * @mode: (in): The animation mode.
+ * @duration_msec: (in): The duration in milliseconds.
+ * @first_property: (in): The first property to animate.
+ *
+ * Animates the properties of @object. The can be set in a similar manner to g_object_set(). They
+ * will be animated from their current value to the target value over the time period.
+ *
+ * Return value: (transfer none): A #PnlAnimation.
+ * Side effects: None.
+ */
+PnlAnimation*
+pnl_object_animate (gpointer object,
+ PnlAnimationMode mode,
+ guint duration_msec,
+ GdkFrameClock *frame_clock,
+ const gchar *first_property,
+ ...)
+{
+ PnlAnimation *animation;
+ va_list args;
+
+ va_start (args, first_property);
+ animation = pnl_object_animatev (object,
+ mode,
+ duration_msec,
+ frame_clock,
+ first_property,
+ args);
+ va_end (args);
+
+ return animation;
+}
+
+/**
+ * pnl_object_animate_full:
+ *
+ * Return value: (transfer none): A #PnlAnimation.
+ */
+PnlAnimation*
+pnl_object_animate_full (gpointer object,
+ PnlAnimationMode mode,
+ guint duration_msec,
+ GdkFrameClock *frame_clock,
+ GDestroyNotify notify,
+ gpointer notify_data,
+ const gchar *first_property,
+ ...)
+{
+ PnlAnimation *animation;
+ va_list args;
+
+ va_start (args, first_property);
+ animation = pnl_object_animatev (object,
+ mode,
+ duration_msec,
+ frame_clock,
+ first_property,
+ args);
+ va_end (args);
+
+ animation->notify = notify;
+ animation->notify_data = notify_data;
+
+ return animation;
+}
diff --git a/contrib/pnl/pnl-animation.h b/contrib/pnl/pnl-animation.h
new file mode 100644
index 0000000..73117b0
--- /dev/null
+++ b/contrib/pnl/pnl-animation.h
@@ -0,0 +1,78 @@
+/* pnl-animation.h
+ *
+ * Copyright (C) 2010-2016 Christian Hergert <christian hergert me>
+ *
+ * This file 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.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file 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 General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_ANIMATION_H
+#define PNL_ANIMATION_H
+
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+#define PNL_TYPE_ANIMATION (pnl_animation_get_type())
+#define PNL_TYPE_ANIMATION_MODE (pnl_animation_mode_get_type())
+
+G_DECLARE_FINAL_TYPE (PnlAnimation, pnl_animation, PNL, ANIMATION, GInitiallyUnowned)
+
+typedef enum
+{
+ PNL_ANIMATION_LINEAR,
+ PNL_ANIMATION_EASE_IN_QUAD,
+ PNL_ANIMATION_EASE_OUT_QUAD,
+ PNL_ANIMATION_EASE_IN_OUT_QUAD,
+ PNL_ANIMATION_EASE_IN_CUBIC,
+ PNL_ANIMATION_EASE_OUT_CUBIC,
+ PNL_ANIMATION_EASE_IN_OUT_CUBIC,
+
+ PNL_ANIMATION_LAST
+} PnlAnimationMode;
+
+GType pnl_animation_mode_get_type (void);
+void pnl_animation_start (PnlAnimation *animation);
+void pnl_animation_stop (PnlAnimation *animation);
+void pnl_animation_add_property (PnlAnimation *animation,
+ GParamSpec *pspec,
+ const GValue *value);
+
+PnlAnimation *pnl_object_animatev (gpointer object,
+ PnlAnimationMode mode,
+ guint duration_msec,
+ GdkFrameClock *frame_clock,
+ const gchar *first_property,
+ va_list args);
+PnlAnimation* pnl_object_animate (gpointer object,
+ PnlAnimationMode mode,
+ guint duration_msec,
+ GdkFrameClock *frame_clock,
+ const gchar *first_property,
+ ...) G_GNUC_NULL_TERMINATED;
+PnlAnimation* pnl_object_animate_full (gpointer object,
+ PnlAnimationMode mode,
+ guint duration_msec,
+ GdkFrameClock *frame_clock,
+ GDestroyNotify notify,
+ gpointer notify_data,
+ const gchar *first_property,
+ ...) G_GNUC_NULL_TERMINATED;
+
+G_END_DECLS
+
+#endif /* PNL_ANIMATION_H */
diff --git a/contrib/pnl/pnl-dock-bin-edge-private.h b/contrib/pnl/pnl-dock-bin-edge-private.h
new file mode 100644
index 0000000..cf79307
--- /dev/null
+++ b/contrib/pnl/pnl-dock-bin-edge-private.h
@@ -0,0 +1,32 @@
+/* pnl-dock-bin-edge-private.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PNL_DOCK_BIN_EDGE_PRIVATE_H
+#define PNL_DOCK_BIN_EDGE_PRIVATE_H
+
+#include "pnl-dock-bin-edge.h"
+
+G_BEGIN_DECLS
+
+GtkPositionType pnl_dock_bin_edge_get_edge (PnlDockBinEdge *self);
+void pnl_dock_bin_edge_set_edge (PnlDockBinEdge *self,
+ GtkPositionType bin_edge);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_BIN_EDGE_PRIVATE_H */
diff --git a/contrib/pnl/pnl-dock-bin-edge.c b/contrib/pnl/pnl-dock-bin-edge.c
new file mode 100644
index 0000000..ac9d19d
--- /dev/null
+++ b/contrib/pnl/pnl-dock-bin-edge.c
@@ -0,0 +1,259 @@
+/* pnl-dock-bin-edge.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-dock-bin.h"
+#include "pnl-dock-bin-edge.h"
+#include "pnl-dock-bin-edge-private.h"
+#include "pnl-dock-revealer.h"
+
+typedef struct
+{
+ GtkPositionType edge : 3;
+} PnlDockBinEdgePrivate;
+
+G_DEFINE_TYPE_EXTENDED (PnlDockBinEdge, pnl_dock_bin_edge, PNL_TYPE_DOCK_REVEALER, 0,
+ G_ADD_PRIVATE (PnlDockBinEdge)
+ G_IMPLEMENT_INTERFACE (PNL_TYPE_DOCK_ITEM, NULL))
+
+enum {
+ PROP_0,
+ PROP_EDGE,
+ N_PROPS
+};
+
+enum {
+ MOVE_TO_BIN_CHILD,
+ N_SIGNALS
+};
+
+static GParamSpec *properties [N_PROPS];
+static guint signals [N_SIGNALS];
+
+static void
+pnl_dock_bin_edge_update_edge (PnlDockBinEdge *self)
+{
+ PnlDockBinEdgePrivate *priv = pnl_dock_bin_edge_get_instance_private (self);
+ GtkStyleContext *style_context;
+ PnlDockRevealerTransitionType transition_type;
+ const gchar *class_name = NULL;
+ GtkWidget *child;
+ GtkOrientation orientation;
+
+ g_assert (PNL_IS_DOCK_BIN_EDGE (self));
+
+ style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
+
+ gtk_style_context_remove_class (style_context, "left");
+ gtk_style_context_remove_class (style_context, "right");
+ gtk_style_context_remove_class (style_context, "top");
+ gtk_style_context_remove_class (style_context, "bottom");
+
+ if (priv->edge == GTK_POS_LEFT)
+ {
+ class_name = "left";
+ transition_type = PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_RIGHT;
+ orientation = GTK_ORIENTATION_VERTICAL;
+ }
+ else if (priv->edge == GTK_POS_RIGHT)
+ {
+ class_name = "right";
+ transition_type = PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_LEFT;
+ orientation = GTK_ORIENTATION_VERTICAL;
+ }
+ else if (priv->edge == GTK_POS_TOP)
+ {
+ class_name = "top";
+ transition_type = PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN;
+ orientation = GTK_ORIENTATION_HORIZONTAL;
+ }
+ else if (priv->edge == GTK_POS_BOTTOM)
+ {
+ class_name = "bottom";
+ transition_type = PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_UP;
+ orientation = GTK_ORIENTATION_HORIZONTAL;
+ }
+ else
+ {
+ g_assert_not_reached ();
+ return;
+ }
+
+ gtk_style_context_add_class (style_context, class_name);
+ pnl_dock_revealer_set_transition_type (PNL_DOCK_REVEALER (self), transition_type);
+
+ child = gtk_bin_get_child (GTK_BIN (self));
+
+ if (PNL_IS_DOCK_PANED (child))
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (child), orientation);
+}
+
+GtkPositionType
+pnl_dock_bin_edge_get_edge (PnlDockBinEdge *self)
+{
+ PnlDockBinEdgePrivate *priv = pnl_dock_bin_edge_get_instance_private (self);
+
+ g_return_val_if_fail (PNL_IS_DOCK_BIN_EDGE (self), 0);
+
+ return priv->edge;
+}
+
+void
+pnl_dock_bin_edge_set_edge (PnlDockBinEdge *self,
+ GtkPositionType edge)
+{
+ PnlDockBinEdgePrivate *priv = pnl_dock_bin_edge_get_instance_private (self);
+
+ g_return_if_fail (PNL_IS_DOCK_BIN_EDGE (self));
+
+ if (edge != priv->edge)
+ {
+ priv->edge = edge;
+ pnl_dock_bin_edge_update_edge (self);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_EDGE]);
+ }
+}
+
+static void
+pnl_dock_bin_edge_add (GtkContainer *container,
+ GtkWidget *widget)
+{
+ GtkWidget *child;
+
+ g_assert (GTK_IS_CONTAINER (container));
+ g_assert (GTK_IS_WIDGET (widget));
+
+ child = gtk_bin_get_child (GTK_BIN (container));
+
+ if (GTK_IS_CONTAINER (child))
+ gtk_container_add (GTK_CONTAINER (child), widget);
+ else
+ GTK_CONTAINER_CLASS (pnl_dock_bin_edge_parent_class)->add (container, widget);
+}
+
+static void
+pnl_dock_bin_edge_real_move_to_bin_child (PnlDockBinEdge *self)
+{
+ GtkWidget *parent;
+
+ g_assert (PNL_IS_DOCK_BIN_EDGE (self));
+
+ parent = gtk_widget_get_parent (GTK_WIDGET (self));
+
+ if (PNL_IS_DOCK_BIN (parent))
+ gtk_widget_grab_focus (parent);
+}
+
+static void
+pnl_dock_bin_edge_constructed (GObject *object)
+{
+ PnlDockBinEdge *self = (PnlDockBinEdge *)object;
+
+ G_OBJECT_CLASS (pnl_dock_bin_edge_parent_class)->constructed (object);
+
+ pnl_dock_bin_edge_update_edge (self);
+}
+
+static void
+pnl_dock_bin_edge_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PnlDockBinEdge *self = PNL_DOCK_BIN_EDGE (object);
+
+ switch (prop_id)
+ {
+ case PROP_EDGE:
+ g_value_set_enum (value, pnl_dock_bin_edge_get_edge (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pnl_dock_bin_edge_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PnlDockBinEdge *self = PNL_DOCK_BIN_EDGE (object);
+
+ switch (prop_id)
+ {
+ case PROP_EDGE:
+ pnl_dock_bin_edge_set_edge (self, g_value_get_enum (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pnl_dock_bin_edge_class_init (PnlDockBinEdgeClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+ GtkBindingSet *binding_set;
+
+ object_class->constructed = pnl_dock_bin_edge_constructed;
+ object_class->get_property = pnl_dock_bin_edge_get_property;
+ object_class->set_property = pnl_dock_bin_edge_set_property;
+
+ container_class->add = pnl_dock_bin_edge_add;
+
+ klass->move_to_bin_child = pnl_dock_bin_edge_real_move_to_bin_child;
+
+ properties [PROP_EDGE] =
+ g_param_spec_enum ("edge",
+ "Edge",
+ "The edge of the dock this widget is attached to",
+ GTK_TYPE_POSITION_TYPE,
+ GTK_POS_LEFT,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ signals [MOVE_TO_BIN_CHILD] =
+ g_signal_new ("move-to-bin-child",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (PnlDockBinEdgeClass, move_to_bin_child),
+ NULL, NULL, NULL, G_TYPE_NONE, 0);
+
+ binding_set = gtk_binding_set_by_class (klass);
+ gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0, "move-to-bin-child", 0);
+
+ gtk_widget_class_set_css_name (widget_class, "dockbinedge");
+}
+
+static void
+pnl_dock_bin_edge_init (PnlDockBinEdge *self)
+{
+ GtkWidget *child;
+
+ child = g_object_new (PNL_TYPE_DOCK_PANED,
+ "visible", TRUE,
+ NULL);
+ GTK_CONTAINER_CLASS (pnl_dock_bin_edge_parent_class)->add (GTK_CONTAINER (self), child);
+
+ pnl_dock_bin_edge_update_edge (self);
+}
diff --git a/contrib/pnl/pnl-dock-bin-edge.h b/contrib/pnl/pnl-dock-bin-edge.h
new file mode 100644
index 0000000..65dee0b
--- /dev/null
+++ b/contrib/pnl/pnl-dock-bin-edge.h
@@ -0,0 +1,48 @@
+/* pnl-dock-bin-edge.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PNL_DOCK_BIN_EDGE_H
+#define PNL_DOCK_BIN_EDGE_H
+
+#include "pnl-dock-revealer.h"
+
+G_BEGIN_DECLS
+
+#define PNL_TYPE_DOCK_BIN_EDGE (pnl_dock_bin_edge_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (PnlDockBinEdge, pnl_dock_bin_edge, PNL, DOCK_BIN_EDGE, PnlDockRevealer)
+
+struct _PnlDockBinEdgeClass
+{
+ PnlDockRevealerClass parent;
+
+ void (*move_to_bin_child) (PnlDockBinEdge *self);
+
+ void (*padding1) (void);
+ void (*padding2) (void);
+ void (*padding3) (void);
+ void (*padding4) (void);
+ void (*padding5) (void);
+ void (*padding6) (void);
+ void (*padding7) (void);
+ void (*padding8) (void);
+};
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_BIN_EDGE_H */
diff --git a/contrib/pnl/pnl-dock-bin.c b/contrib/pnl/pnl-dock-bin.c
new file mode 100644
index 0000000..ff54493
--- /dev/null
+++ b/contrib/pnl/pnl-dock-bin.c
@@ -0,0 +1,1922 @@
+/* pnl-dock-bin.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+
+#include "pnl-dock-bin.h"
+#include "pnl-dock-bin-edge-private.h"
+#include "pnl-dock-item.h"
+
+#define HANDLE_WIDTH 10
+#define HANDLE_HEIGHT 10
+
+typedef enum
+{
+ PNL_DOCK_BIN_CHILD_LEFT = GTK_POS_LEFT,
+ PNL_DOCK_BIN_CHILD_RIGHT = GTK_POS_RIGHT,
+ PNL_DOCK_BIN_CHILD_TOP = GTK_POS_TOP,
+ PNL_DOCK_BIN_CHILD_BOTTOM = GTK_POS_BOTTOM,
+ PNL_DOCK_BIN_CHILD_CENTER = 4,
+ LAST_PNL_DOCK_BIN_CHILD = 5
+} PnlDockBinChildType;
+
+typedef struct
+{
+ /*
+ * The child widget in question.
+ * Typically this is a PnlDockBinEdge, but the
+ * center widget can be whatever.
+ */
+ GtkWidget *widget;
+
+ /*
+ * The GdkWindow for the handle to resize the edge.
+ * This is an input only window, the pane handle is drawn
+ * with CSS by whatever styling the application has chose.
+ */
+ GdkWindow *handle;
+
+ /*
+ * When dragging, we need to know our offset relative to the
+ * grab position to alter preferred size requests.
+ */
+ gint drag_offset;
+
+ /*
+ * This is the position of the child before the drag started.
+ * We use this, combined with @drag_offset to determine the
+ * size the child should be in the drag operation.
+ */
+ gint drag_begin_position;
+
+ /*
+ * Priority child property used to alter which child is
+ * dominant in each slice stage. See
+ * pnl_dock_bin_get_children_preferred_width() for more information
+ * on how the slicing is performed.
+ */
+ gint priority;
+
+ /*
+ * Cached size request used during size allocation.
+ */
+ GtkRequisition min_req;
+ GtkRequisition nat_req;
+
+ /*
+ * The type of child. The PNL_DOCK_BIN_CHILD_CENTER is always
+ * the last child, and our sort function ensures that.
+ */
+ PnlDockBinChildType type : 3;
+} PnlDockBinChild;
+
+typedef struct
+{
+ /*
+ * All of our dock children, including edges and center child.
+ */
+ PnlDockBinChild children[LAST_PNL_DOCK_BIN_CHILD];
+
+ /*
+ * Actions used to toggle edge visibility.
+ */
+ GSimpleActionGroup *actions;
+
+ /*
+ * The pan gesture is used to resize edges.
+ */
+ GtkGesturePan *pan_gesture;
+
+ /*
+ * While in a pan gesture, we need to drag the current edge
+ * being dragged. This is left, right, top, or bottom only.
+ */
+ PnlDockBinChild *drag_child;
+
+ /*
+ * We need to track the position during a DnD request. We can use this
+ * to highlight the area where the drop will occur.
+ */
+ gint dnd_drag_x;
+ gint dnd_drag_y;
+} PnlDockBinPrivate;
+
+static void pnl_dock_bin_init_buildable_iface (GtkBuildableIface *iface);
+static void pnl_dock_bin_init_dock_iface (PnlDockInterface *iface);
+static void pnl_dock_bin_init_dock_item_iface (PnlDockItemInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (PnlDockBin, pnl_dock_bin, GTK_TYPE_CONTAINER, 0,
+ G_ADD_PRIVATE (PnlDockBin)
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
+ pnl_dock_bin_init_buildable_iface)
+ G_IMPLEMENT_INTERFACE (PNL_TYPE_DOCK_ITEM,
+ pnl_dock_bin_init_dock_item_iface)
+ G_IMPLEMENT_INTERFACE (PNL_TYPE_DOCK,
+ pnl_dock_bin_init_dock_iface))
+
+enum {
+ PROP_0,
+ PROP_MANAGER,
+ N_PROPS
+};
+
+enum {
+ CHILD_PROP_0,
+ CHILD_PROP_POSITION,
+ CHILD_PROP_PRIORITY,
+ N_CHILD_PROPS
+};
+
+enum {
+ STYLE_PROP_0,
+ STYLE_PROP_HANDLE_SIZE,
+ N_STYLE_PROPS
+};
+
+static GParamSpec *child_properties [N_CHILD_PROPS];
+static GParamSpec *style_properties [N_STYLE_PROPS];
+
+static gboolean
+map_boolean_to_variant (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data)
+{
+ g_assert (G_IS_BINDING (binding));
+
+ if (g_value_get_boolean (from_value))
+ g_value_set_variant (to_value, g_variant_new_boolean (TRUE));
+ else
+ g_value_set_variant (to_value, g_variant_new_boolean (FALSE));
+
+ return TRUE;
+}
+
+static PnlDockBinChild *
+pnl_dock_bin_get_child (PnlDockBin *self,
+ GtkWidget *widget)
+{
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+ guint i;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (GTK_IS_WIDGET (widget));
+
+ for (i = 0; i < G_N_ELEMENTS (priv->children); i++)
+ {
+ PnlDockBinChild *child = &priv->children [i];
+
+ if ((GtkWidget *)child->widget == widget)
+ return child;
+ }
+
+ g_assert_not_reached ();
+
+ return NULL;
+}
+
+#if 0
+static PnlDockBinChild *
+pnl_dock_bin_get_child_at_coordinates (PnlDockBin *self,
+ gint x,
+ gint y)
+{
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+ GtkAllocation our_alloc;
+ guint i;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+
+ gtk_widget_get_allocation (GTK_WIDGET (self), &our_alloc);
+
+ for (i = 0; i < G_N_ELEMENTS (priv->children); i++)
+ {
+ PnlDockBinChild *child = &priv->children [i];
+ GtkAllocation alloc;
+
+ if (child->widget == NULL)
+ continue;
+
+ gtk_widget_get_allocation (child->widget, &alloc);
+
+ alloc.x -= our_alloc.x;
+ alloc.y -= our_alloc.y;
+
+ if (x >= alloc.x &&
+ x <= alloc.x + alloc.width &&
+ y >= alloc.y &&
+ y <= alloc.y + alloc.height)
+ return child;
+ }
+
+ return NULL;
+}
+#endif
+
+static PnlDockBinChild *
+pnl_dock_bin_get_child_typed (PnlDockBin *self,
+ PnlDockBinChildType type)
+{
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+ guint i;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (type >= PNL_DOCK_BIN_CHILD_LEFT);
+ g_assert (type < LAST_PNL_DOCK_BIN_CHILD);
+
+ for (i = 0; i < G_N_ELEMENTS (priv->children); i++)
+ {
+ PnlDockBinChild *child = &priv->children [i];
+
+ if (child->type == type)
+ return child;
+ }
+
+ g_assert_not_reached ();
+
+ return NULL;
+}
+
+static void
+pnl_dock_bin_update_focus_chain (PnlDockBin *self)
+{
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+ PnlDockBinChild *child;
+ GList *focus_chain = NULL;
+ guint i;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+
+ for (i = G_N_ELEMENTS (priv->children); i > 0; i--)
+ {
+ child = &priv->children [i - 1];
+
+ if ((child->widget != NULL) &&
+ (child->type != PNL_DOCK_BIN_CHILD_CENTER))
+ focus_chain = g_list_prepend (focus_chain, child->widget);
+ }
+
+ child = pnl_dock_bin_get_child_typed (self, PNL_DOCK_BIN_CHILD_CENTER);
+
+ if (child->widget != NULL)
+ focus_chain = g_list_prepend (focus_chain, child->widget);
+
+ if (focus_chain != NULL)
+ {
+ gtk_container_set_focus_chain (GTK_CONTAINER (self), focus_chain);
+ g_list_free (focus_chain);
+ }
+}
+
+static GAction *
+pnl_dock_bin_get_action_for_type (PnlDockBin *self,
+ PnlDockBinChildType type)
+{
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+ const gchar *name = NULL;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+
+ if (type == PNL_DOCK_BIN_CHILD_LEFT)
+ name = "left-visible";
+ else if (type == PNL_DOCK_BIN_CHILD_RIGHT)
+ name = "right-visible";
+ else if (type == PNL_DOCK_BIN_CHILD_TOP)
+ name = "top-visible";
+ else if (type == PNL_DOCK_BIN_CHILD_BOTTOM)
+ name = "bottom-visible";
+ else
+ g_assert_not_reached ();
+
+ return g_action_map_lookup_action (G_ACTION_MAP (priv->actions), name);
+}
+
+static void
+pnl_dock_bin_add (GtkContainer *container,
+ GtkWidget *widget)
+{
+ PnlDockBin *self = (PnlDockBin *)container;
+ PnlDockBinChild *child;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (GTK_IS_WIDGET (widget));
+
+ child = pnl_dock_bin_get_child_typed (self, PNL_DOCK_BIN_CHILD_CENTER);
+
+ if (child->widget != NULL)
+ {
+ g_warning ("Attempt to add a %s to a %s, but it already has a child of type %s",
+ G_OBJECT_TYPE_NAME (widget),
+ G_OBJECT_TYPE_NAME (self),
+ G_OBJECT_TYPE_NAME (child->widget));
+ return;
+ }
+
+ if (PNL_IS_DOCK_ITEM (widget) &&
+ !pnl_dock_item_adopt (PNL_DOCK_ITEM (self), PNL_DOCK_ITEM (widget)))
+ {
+ g_warning ("Child of type %s has a different PnlDockManager than %s",
+ G_OBJECT_TYPE_NAME (widget), G_OBJECT_TYPE_NAME (self));
+ return;
+ }
+
+ child->widget = g_object_ref_sink (widget);
+ gtk_widget_set_parent (widget, GTK_WIDGET (self));
+
+ pnl_dock_bin_update_focus_chain (self);
+
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+}
+
+static void
+pnl_dock_bin_remove (GtkContainer *container,
+ GtkWidget *widget)
+{
+ PnlDockBin *self = (PnlDockBin *)container;
+ PnlDockBinChild *child;
+
+ g_return_if_fail (PNL_IS_DOCK_BIN (self));
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ child = pnl_dock_bin_get_child (self, widget);
+ gtk_widget_unparent (child->widget);
+ g_clear_object (&child->widget);
+
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+}
+
+static void
+pnl_dock_bin_forall (GtkContainer *container,
+ gboolean include_internal,
+ GtkCallback callback,
+ gpointer user_data)
+{
+ PnlDockBin *self = (PnlDockBin *)container;
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+ guint i;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (callback != NULL);
+
+ for (i = G_N_ELEMENTS (priv->children); i > 0; i--)
+ {
+ PnlDockBinChild *child = &priv->children [i - 1];
+
+ if (child->widget != NULL)
+ callback (GTK_WIDGET (child->widget), user_data);
+ }
+}
+
+static void
+pnl_dock_bin_get_children_preferred_width (PnlDockBin *self,
+ PnlDockBinChild *children,
+ gint n_children,
+ gint *min_width,
+ gint *nat_width)
+{
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+ PnlDockBinChild *child = children;
+ gint child_min_width = 0;
+ gint child_nat_width = 0;
+ gint neighbor_min_width = 0;
+ gint neighbor_nat_width = 0;
+ gint handle_size = 0;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (children != NULL);
+ g_assert (n_children > 0);
+ g_assert (min_width != NULL);
+ g_assert (nat_width != NULL);
+
+ *min_width = 0;
+ *nat_width = 0;
+
+ gtk_widget_style_get (GTK_WIDGET (self),
+ "handle-size", &handle_size,
+ NULL);
+
+ /*
+ * We have a fairly simple rule for deducing the size request of
+ * the children layout. Since children edges can have any priority,
+ * we need to know how to slice them into areas that allow us to
+ * combine (additive) or negotiate (maximum) widths with the
+ * neighboring widgets.
+ *
+ * .
+ * .
+ * +----+---------------------------------+
+ * | | 2 |
+ * | +=================================+.....
+ * | | | |
+ * | | | |
+ * | 1 | 5 | |
+ * | | | 3 |
+ * | +==.==.==.==.==.==.==.==.==.=+ |
+ * | | 4 | |
+ * +----+----------------------------+----+
+ * . .
+ * . .
+ *
+ * The children are sorted in their weighting order. Each child
+ * will dominate the leftover allocation, in the orientation that
+ * matters.
+ *
+ * 1 and 3 in the diagram above will always be additive with their
+ * neighbors horizontal neighbors. See the guide does for how this
+ * gets sliced. Even if 3 were dominant (instead of 2), it would still
+ * be additive to its neighbors. Same for 1.
+ *
+ * Both 2 and 4, will always negotiate their widths with the next
+ * child.
+ *
+ * This allows us to make a fairly simple recursive function to
+ * size ourselves and then call again with the next child, working our
+ * way down to 5 (the center widget).
+ *
+ * At this point, we walk back up the recursive-stack and do our
+ * adding or negotiating.
+ */
+
+ if (child->widget != NULL)
+ gtk_widget_get_preferred_width (child->widget, &child_min_width, &child_nat_width);
+
+ if (child == priv->drag_child)
+ child_nat_width = MAX (child_min_width,
+ child->drag_begin_position + child->drag_offset);
+
+ if (n_children > 1)
+ pnl_dock_bin_get_children_preferred_width (self,
+ &children [1],
+ n_children - 1,
+ &neighbor_min_width,
+ &neighbor_nat_width);
+
+ switch (child->type)
+ {
+ case PNL_DOCK_BIN_CHILD_LEFT:
+ case PNL_DOCK_BIN_CHILD_RIGHT:
+ *min_width = (child_min_width + neighbor_min_width + handle_size);
+ *nat_width = (child_nat_width + neighbor_nat_width + handle_size);
+ break;
+
+ case PNL_DOCK_BIN_CHILD_TOP:
+ case PNL_DOCK_BIN_CHILD_BOTTOM:
+ *min_width = MAX (child_min_width, neighbor_min_width + handle_size);
+ *nat_width = MAX (child_nat_width, neighbor_nat_width + handle_size);
+ break;
+
+ case PNL_DOCK_BIN_CHILD_CENTER:
+ *min_width = child_min_width;
+ *nat_width = child_min_width;
+ break;
+
+ case LAST_PNL_DOCK_BIN_CHILD:
+ default:
+ g_assert_not_reached ();
+ }
+
+ child->min_req.width = *min_width;
+ child->nat_req.width = *nat_width;
+}
+
+static void
+pnl_dock_bin_get_preferred_width (GtkWidget *widget,
+ gint *min_width,
+ gint *nat_width)
+{
+ PnlDockBin *self = (PnlDockBin *)widget;
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (min_width != NULL);
+ g_assert (nat_width != NULL);
+
+ pnl_dock_bin_get_children_preferred_width (self,
+ priv->children,
+ G_N_ELEMENTS (priv->children),
+ min_width,
+ nat_width);
+}
+
+static void
+pnl_dock_bin_get_children_preferred_height (PnlDockBin *self,
+ PnlDockBinChild *children,
+ gint n_children,
+ gint *min_height,
+ gint *nat_height)
+{
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+ PnlDockBinChild *child = children;
+ gint child_min_height = 0;
+ gint child_nat_height = 0;
+ gint neighbor_min_height = 0;
+ gint neighbor_nat_height = 0;
+ gint handle_size = 0;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (children != NULL);
+ g_assert (n_children > 0);
+ g_assert (min_height != NULL);
+ g_assert (nat_height != NULL);
+
+ *min_height = 0;
+ *nat_height = 0;
+
+ gtk_widget_style_get (GTK_WIDGET (self),
+ "handle-size", &handle_size,
+ NULL);
+
+ /*
+ * See pnl_dock_bin_get_children_preferred_width() for more information on
+ * how this works. This works just like that but the negotiated/additive
+ * operations are switched between the left/right and top/bottom.
+ */
+
+ if (child->widget != NULL)
+ gtk_widget_get_preferred_height (child->widget, &child_min_height, &child_nat_height);
+
+ if (child == priv->drag_child)
+ child_nat_height = MAX (child_min_height,
+ child->drag_begin_position + child->drag_offset);
+
+ if (n_children > 1)
+ pnl_dock_bin_get_children_preferred_height (self,
+ &children [1],
+ n_children - 1,
+ &neighbor_min_height,
+ &neighbor_nat_height);
+
+ switch (child->type)
+ {
+ case PNL_DOCK_BIN_CHILD_LEFT:
+ case PNL_DOCK_BIN_CHILD_RIGHT:
+ *min_height = MAX (child_min_height, neighbor_min_height + handle_size);
+ *nat_height = MAX (child_nat_height, neighbor_nat_height + handle_size);
+ break;
+
+ case PNL_DOCK_BIN_CHILD_TOP:
+ case PNL_DOCK_BIN_CHILD_BOTTOM:
+ *min_height = (child_min_height + neighbor_min_height + handle_size);
+ *nat_height = (child_nat_height + neighbor_nat_height + handle_size);
+ break;
+
+ case PNL_DOCK_BIN_CHILD_CENTER:
+ *min_height = child_min_height;
+ *nat_height = child_min_height;
+ break;
+
+ case LAST_PNL_DOCK_BIN_CHILD:
+ default:
+ g_assert_not_reached ();
+ }
+
+ child->min_req.height = *min_height;
+ child->nat_req.height = *nat_height;
+}
+
+static void
+pnl_dock_bin_get_preferred_height (GtkWidget *widget,
+ gint *min_height,
+ gint *nat_height)
+{
+ PnlDockBin *self = (PnlDockBin *)widget;
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (min_height != NULL);
+ g_assert (nat_height != NULL);
+
+ pnl_dock_bin_get_children_preferred_height (self,
+ priv->children,
+ G_N_ELEMENTS (priv->children),
+ min_height,
+ nat_height);
+}
+
+static void
+pnl_dock_bin_negotiate_size (PnlDockBin *self,
+ const GtkAllocation *allocation,
+ const GtkRequisition *child_min,
+ const GtkRequisition *child_nat,
+ const GtkRequisition *neighbor_min,
+ const GtkRequisition *neighbor_nat,
+ GtkAllocation *child_alloc)
+{
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (allocation != NULL);
+ g_assert (child_min != NULL);
+ g_assert (child_nat != NULL);
+ g_assert (neighbor_min != NULL);
+ g_assert (neighbor_nat != NULL);
+ g_assert (child_alloc != NULL);
+
+ if (allocation->width - child_nat->width < neighbor_min->width)
+ child_alloc->width = allocation->width - neighbor_min->width;
+ else
+ child_alloc->width = child_nat->width;
+
+ if (allocation->height - child_nat->height < neighbor_min->height)
+ child_alloc->height = allocation->height - neighbor_min->height;
+ else
+ child_alloc->height = child_nat->height;
+}
+
+static void
+pnl_dock_bin_child_size_allocate (PnlDockBin *self,
+ PnlDockBinChild *children,
+ gint n_children,
+ GtkAllocation *allocation)
+{
+ PnlDockBinChild *child = children;
+ gint handle_size = 0;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (children != NULL);
+ g_assert (n_children >= 1);
+ g_assert (allocation != NULL);
+
+ if (n_children == 1)
+ {
+ g_assert (child->type == PNL_DOCK_BIN_CHILD_CENTER);
+
+ if (child->widget != NULL && gtk_widget_get_visible (child->widget))
+ gtk_widget_size_allocate (child->widget, allocation);
+
+ return;
+ }
+
+ gtk_widget_style_get (GTK_WIDGET (self),
+ "handle-size", &handle_size,
+ NULL);
+
+ if (child->widget != NULL && gtk_widget_get_visible (child->widget))
+ {
+ GtkAllocation child_alloc = { 0 };
+ GtkAllocation handle_alloc = { 0 };
+ GtkRequisition neighbor_min = { 0 };
+ GtkRequisition neighbor_nat = { 0 };
+
+ pnl_dock_bin_get_children_preferred_height (self, child, 1,
+ &child->min_req.height,
+ &child->nat_req.height);
+
+ pnl_dock_bin_get_children_preferred_width (self, child, 1,
+ &child->min_req.width,
+ &child->nat_req.width);
+
+ pnl_dock_bin_get_children_preferred_height (self,
+ &children [1],
+ n_children - 1,
+ &neighbor_min.height,
+ &neighbor_nat.height);
+
+ pnl_dock_bin_get_children_preferred_width (self,
+ &children [1],
+ n_children - 1,
+ &neighbor_min.width,
+ &neighbor_nat.width);
+
+ pnl_dock_bin_negotiate_size (self,
+ allocation,
+ &child->min_req,
+ &child->nat_req,
+ &neighbor_min,
+ &neighbor_nat,
+ &child_alloc);
+
+ switch (child->type)
+ {
+ case PNL_DOCK_BIN_CHILD_LEFT:
+ child_alloc.x = allocation->x;
+ child_alloc.y = allocation->y;
+ child_alloc.height = allocation->height;
+ child_alloc.width -= handle_size;
+ allocation->x += child_alloc.width + handle_size;
+ allocation->width -= child_alloc.width + handle_size;
+ break;
+
+ case PNL_DOCK_BIN_CHILD_RIGHT:
+ child_alloc.width -= handle_size;
+ child_alloc.x = allocation->x + allocation->width - child_alloc.width;
+ child_alloc.y = allocation->y;
+ child_alloc.height = allocation->height;
+ allocation->width -= child_alloc.width + handle_size;
+ break;
+
+ case PNL_DOCK_BIN_CHILD_TOP:
+ child_alloc.x = allocation->x;
+ child_alloc.y = allocation->y;
+ child_alloc.width = allocation->width;
+ child_alloc.height -= handle_size;
+ allocation->y += child_alloc.height + handle_size;
+ allocation->height -= child_alloc.height + handle_size;
+ break;
+
+ case PNL_DOCK_BIN_CHILD_BOTTOM:
+ child_alloc.height -= handle_size;
+ child_alloc.x = allocation->x;
+ child_alloc.y = allocation->y + allocation->height - child_alloc.height;
+ child_alloc.width = allocation->width;
+ allocation->height -= child_alloc.height + handle_size;
+ break;
+
+ case PNL_DOCK_BIN_CHILD_CENTER:
+ case LAST_PNL_DOCK_BIN_CHILD:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ handle_alloc = child_alloc;
+
+ switch (child->type)
+ {
+ case PNL_DOCK_BIN_CHILD_LEFT:
+ handle_alloc.x += handle_alloc.width - HANDLE_WIDTH;
+ handle_alloc.width = HANDLE_WIDTH;
+
+ case PNL_DOCK_BIN_CHILD_RIGHT:
+ handle_alloc.width = HANDLE_WIDTH;
+ break;
+
+ case PNL_DOCK_BIN_CHILD_BOTTOM:
+ handle_alloc.height = HANDLE_HEIGHT;
+ break;
+
+ case PNL_DOCK_BIN_CHILD_TOP:
+ handle_alloc.y += handle_alloc.height - HANDLE_HEIGHT;
+ handle_alloc.height = HANDLE_HEIGHT;
+ break;
+
+ case PNL_DOCK_BIN_CHILD_CENTER:
+ case LAST_PNL_DOCK_BIN_CHILD:
+ default:
+ break;
+ }
+
+ if (child_alloc.width > 0 && child_alloc.height > 0 && child->handle)
+ gdk_window_move_resize (child->handle,
+ handle_alloc.x, handle_alloc.y,
+ handle_alloc.width, handle_alloc.height);
+
+ gtk_widget_size_allocate (child->widget, &child_alloc);
+ }
+
+ pnl_dock_bin_child_size_allocate (self, &children [1], n_children - 1, allocation);
+}
+
+static void
+pnl_dock_bin_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ PnlDockBin *self = (PnlDockBin *)widget;
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+ GtkAllocation child_allocation;
+ guint i;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (allocation != NULL);
+
+ gtk_widget_set_allocation (widget, allocation);
+
+ child_allocation.x = 0;
+ child_allocation.y = 0;
+ child_allocation.width = allocation->width;
+ child_allocation.height = allocation->height;
+
+ if (gtk_widget_get_realized (widget))
+ {
+ gdk_window_move_resize (gtk_widget_get_window (widget),
+ allocation->x,
+ allocation->y,
+ child_allocation.width,
+ child_allocation.height);
+ }
+
+ pnl_dock_bin_child_size_allocate (self,
+ priv->children,
+ G_N_ELEMENTS (priv->children),
+ &child_allocation);
+
+ /*
+ * Hide all of the handle input windows that should be hidden
+ * because the child has an empty allocation.
+ */
+
+ for (i = 0; i < PNL_DOCK_BIN_CHILD_CENTER; i++)
+ {
+ PnlDockBinChild *child = &priv->children [i];
+
+ if (child->handle != NULL)
+ {
+ if (PNL_IS_DOCK_BIN_EDGE (child->widget) &&
+ pnl_dock_revealer_get_reveal_child (PNL_DOCK_REVEALER (child->widget)))
+ gdk_window_show (child->handle);
+ else
+ gdk_window_hide (child->handle);
+ }
+ }
+}
+
+static void
+pnl_dock_bin_visible_action (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ PnlDockBin *self = user_data;
+ PnlDockBinChild *child;
+ PnlDockBinChildType type;
+ const gchar *action_name;
+ gboolean reveal_child;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (G_IS_SIMPLE_ACTION (action));
+ g_assert (state != NULL);
+ g_assert (g_variant_is_of_type (state, G_VARIANT_TYPE_BOOLEAN));
+
+ action_name = g_action_get_name (G_ACTION (action));
+ reveal_child = g_variant_get_boolean (state);
+
+ if (g_str_has_prefix (action_name, "left"))
+ type = PNL_DOCK_BIN_CHILD_LEFT;
+ else if (g_str_has_prefix (action_name, "right"))
+ type = PNL_DOCK_BIN_CHILD_RIGHT;
+ else if (g_str_has_prefix (action_name, "top"))
+ type = PNL_DOCK_BIN_CHILD_TOP;
+ else if (g_str_has_prefix (action_name, "bottom"))
+ type = PNL_DOCK_BIN_CHILD_BOTTOM;
+ else
+ return;
+
+ child = pnl_dock_bin_get_child_typed (self, type);
+
+ pnl_dock_revealer_set_reveal_child (PNL_DOCK_REVEALER (child->widget), reveal_child);
+}
+
+static gint
+pnl_dock_bin_child_compare (gconstpointer a,
+ gconstpointer b)
+{
+ const PnlDockBinChild *child_a = a;
+ const PnlDockBinChild *child_b = b;
+
+ if (child_a->type == PNL_DOCK_BIN_CHILD_CENTER)
+ return 1;
+ else if (child_b->type == PNL_DOCK_BIN_CHILD_CENTER)
+ return -1;
+
+ return child_a->priority - child_b->priority;
+}
+
+static void
+pnl_dock_bin_set_child_priority (PnlDockBin *self,
+ GtkWidget *widget,
+ gint priority)
+{
+ PnlDockBinChild *child;
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (GTK_IS_WIDGET (widget));
+
+ child = pnl_dock_bin_get_child (self, widget);
+ child->priority = priority;
+
+ g_qsort_with_data (&priv->children[0],
+ PNL_DOCK_BIN_CHILD_CENTER,
+ sizeof (PnlDockBinChild),
+ (GCompareDataFunc)pnl_dock_bin_child_compare,
+ NULL);
+
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+}
+
+static void
+pnl_dock_bin_create_child_handle (PnlDockBin *self,
+ PnlDockBinChild *child)
+{
+ GdkWindowAttr attributes = { 0 };
+ GdkDisplay *display;
+ GdkWindow *parent;
+ GdkCursorType cursor_type;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (child != NULL);
+ g_assert (child->type < PNL_DOCK_BIN_CHILD_CENTER);
+ g_assert (child->handle == NULL);
+
+ display = gtk_widget_get_display (GTK_WIDGET (self));
+ parent = gtk_widget_get_window (GTK_WIDGET (self));
+
+ cursor_type = (child->type == PNL_DOCK_BIN_CHILD_LEFT || child->type == PNL_DOCK_BIN_CHILD_RIGHT)
+ ? GDK_SB_H_DOUBLE_ARROW
+ : GDK_SB_V_DOUBLE_ARROW;
+
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.wclass = GDK_INPUT_ONLY;
+ attributes.x = -1;
+ attributes.y = -1;
+ attributes.width = 1;
+ attributes.height = 1;
+ 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_for_display (display, cursor_type);
+
+ 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 void
+pnl_dock_bin_destroy_child_handle (PnlDockBin *self,
+ PnlDockBinChild *child)
+{
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (child != NULL);
+ g_assert (child->type < PNL_DOCK_BIN_CHILD_CENTER);
+
+ if (child->handle != NULL)
+ {
+ gdk_window_destroy (child->handle);
+ child->handle = NULL;
+ }
+}
+
+static void
+pnl_dock_bin_realize (GtkWidget *widget)
+{
+ PnlDockBin *self = (PnlDockBin *)widget;
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+ GdkWindowAttr attributes = { 0 };
+ GdkWindow *parent;
+ GdkWindow *window;
+ GtkAllocation alloc;
+ gint attributes_mask = 0;
+ guint i;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+
+ gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
+
+ gtk_widget_set_realized (GTK_WIDGET (self), TRUE);
+
+ parent = gtk_widget_get_parent_window (GTK_WIDGET (self));
+
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.visual = gtk_widget_get_visual (GTK_WIDGET (self));
+ attributes.x = alloc.x;
+ attributes.y = alloc.y;
+ attributes.width = alloc.width;
+ attributes.height = alloc.height;
+ attributes.event_mask = 0;
+
+ attributes_mask = (GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL);
+
+ window = gdk_window_new (parent, &attributes, attributes_mask);
+ gtk_widget_set_window (GTK_WIDGET (self), window);
+ gtk_widget_register_window (GTK_WIDGET (self), window);
+
+ for (i = 0; i < PNL_DOCK_BIN_CHILD_CENTER; i++)
+ {
+ PnlDockBinChild *child = &priv->children [i];
+
+ pnl_dock_bin_create_child_handle (self, child);
+ }
+}
+
+static void
+pnl_dock_bin_unrealize (GtkWidget *widget)
+{
+ PnlDockBin *self = (PnlDockBin *)widget;
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+ guint i;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+
+ for (i = 0; i < PNL_DOCK_BIN_CHILD_CENTER; i++)
+ {
+ PnlDockBinChild *child = &priv->children [i];
+
+ pnl_dock_bin_destroy_child_handle (self, child);
+ }
+
+ GTK_WIDGET_CLASS (pnl_dock_bin_parent_class)->unrealize (widget);
+}
+
+static void
+pnl_dock_bin_map (GtkWidget *widget)
+{
+ PnlDockBin *self = (PnlDockBin *)widget;
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+ guint i;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+
+ GTK_WIDGET_CLASS (pnl_dock_bin_parent_class)->map (widget);
+
+ for (i = 0; i < PNL_DOCK_BIN_CHILD_CENTER; i++)
+ {
+ PnlDockBinChild *child = &priv->children [i];
+
+ if (child->handle != NULL)
+ gdk_window_show (child->handle);
+ }
+}
+
+static void
+pnl_dock_bin_unmap (GtkWidget *widget)
+{
+ PnlDockBin *self = (PnlDockBin *)widget;
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+ guint i;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+
+ for (i = 0; i < PNL_DOCK_BIN_CHILD_CENTER; i++)
+ {
+ PnlDockBinChild *child = &priv->children [i];
+
+ if (child->handle != NULL)
+ gdk_window_hide (child->handle);
+ }
+
+ GTK_WIDGET_CLASS (pnl_dock_bin_parent_class)->unmap (widget);
+}
+
+static void
+pnl_dock_bin_pan_gesture_drag_begin (PnlDockBin *self,
+ gdouble x,
+ gdouble y,
+ GtkGesturePan *gesture)
+{
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+ GdkEventSequence *sequence;
+ PnlDockBinChild *child = NULL;
+ GtkAllocation child_alloc;
+ const GdkEvent *event;
+ guint i;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (GTK_IS_GESTURE_PAN (gesture));
+
+ sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+ event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
+
+ for (i = 0; i < G_N_ELEMENTS (priv->children); i++)
+ {
+ if (priv->children [i].handle == event->any.window)
+ {
+ child = &priv->children [i];
+ break;
+ }
+ }
+
+ if (child == NULL || child->type >= PNL_DOCK_BIN_CHILD_CENTER)
+ {
+ gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
+ return;
+ }
+
+ gtk_widget_get_allocation (child->widget, &child_alloc);
+
+ priv->drag_child = child;
+ priv->drag_child->drag_offset = 0;
+
+ if (child->type == PNL_DOCK_BIN_CHILD_LEFT || child->type == PNL_DOCK_BIN_CHILD_RIGHT)
+ {
+ gtk_gesture_pan_set_orientation (gesture, GTK_ORIENTATION_HORIZONTAL);
+ priv->drag_child->drag_begin_position = child_alloc.width;
+ }
+ else
+ {
+ gtk_gesture_pan_set_orientation (gesture, GTK_ORIENTATION_VERTICAL);
+ priv->drag_child->drag_begin_position = child_alloc.height;
+ }
+
+ gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+}
+
+static void
+pnl_dock_bin_pan_gesture_drag_end (PnlDockBin *self,
+ gdouble x,
+ gdouble y,
+ GtkGesturePan *gesture)
+{
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+ GdkEventSequence *sequence;
+ GtkEventSequenceState state;
+ GtkAllocation child_alloc;
+ gint position;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (GTK_IS_GESTURE_PAN (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_DENIED)
+ goto cleanup;
+
+ g_assert (priv->drag_child != NULL);
+ g_assert (PNL_IS_DOCK_BIN_EDGE (priv->drag_child->widget));
+
+ gtk_widget_get_allocation (priv->drag_child->widget, &child_alloc);
+
+ if ((priv->drag_child->type == PNL_DOCK_BIN_CHILD_LEFT) ||
+ (priv->drag_child->type == PNL_DOCK_BIN_CHILD_RIGHT))
+ position = child_alloc.width;
+ else
+ position = child_alloc.height;
+
+ pnl_dock_revealer_set_position (PNL_DOCK_REVEALER (priv->drag_child->widget), position);
+
+cleanup:
+ if (priv->drag_child != NULL)
+ {
+ priv->drag_child->drag_offset = 0;
+ priv->drag_child->drag_begin_position = 0;
+ priv->drag_child = NULL;
+ }
+}
+
+static void
+pnl_dock_bin_pan_gesture_pan (PnlDockBin *self,
+ GtkPanDirection direction,
+ gdouble offset,
+ GtkGesturePan *gesture)
+{
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+ gint position;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (GTK_IS_GESTURE_PAN (gesture));
+ g_assert (priv->drag_child != NULL);
+ g_assert (priv->drag_child->type < PNL_DOCK_BIN_CHILD_CENTER);
+
+ /*
+ * This callback is used to adjust the size allocation of the edge in
+ * question (denoted by priv->drag_child). It is always one of the
+ * left, right, top, or bottom children. Never the center child.
+ *
+ * Because of how GtkRevealer works, we are doing a bit of a workaround
+ * here. We need the revealer (the PnlDockBinEdge) child to have a size
+ * request that matches the visible area, otherwise animating out the
+ * revealer will not look right.
+ */
+
+ if (direction == GTK_PAN_DIRECTION_UP)
+ {
+ if (priv->drag_child->type == PNL_DOCK_BIN_CHILD_TOP)
+ offset = -offset;
+ }
+ else if (direction == GTK_PAN_DIRECTION_DOWN)
+ {
+ if (priv->drag_child->type == PNL_DOCK_BIN_CHILD_BOTTOM)
+ offset = -offset;
+ }
+ else if (direction == GTK_PAN_DIRECTION_LEFT)
+ {
+ if (priv->drag_child->type == PNL_DOCK_BIN_CHILD_LEFT)
+ offset = -offset;
+ }
+ else if (direction == GTK_PAN_DIRECTION_RIGHT)
+ {
+ if (priv->drag_child->type == PNL_DOCK_BIN_CHILD_RIGHT)
+ offset = -offset;
+ }
+
+ priv->drag_child->drag_offset = (gint)offset;
+
+ position = priv->drag_child->drag_offset + priv->drag_child->drag_begin_position;
+ if (position >= 0)
+ pnl_dock_revealer_set_position (PNL_DOCK_REVEALER (priv->drag_child->widget), position);
+}
+
+static void
+pnl_dock_bin_create_pan_gesture (PnlDockBin *self)
+{
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+ GtkGesture *gesture;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (priv->pan_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 (pnl_dock_bin_pan_gesture_drag_begin),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (gesture,
+ "drag-end",
+ G_CALLBACK (pnl_dock_bin_pan_gesture_drag_end),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (gesture,
+ "pan",
+ G_CALLBACK (pnl_dock_bin_pan_gesture_pan),
+ self,
+ G_CONNECT_SWAPPED);
+
+ priv->pan_gesture = GTK_GESTURE_PAN (gesture);
+}
+
+static void
+pnl_dock_bin_drag_enter (PnlDockBin *self,
+ GdkDragContext *drag_context,
+ gint x,
+ gint y,
+ guint time_)
+{
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (GDK_IS_DRAG_CONTEXT (drag_context));
+
+}
+
+static gboolean
+pnl_dock_bin_drag_motion (GtkWidget *widget,
+ GdkDragContext *drag_context,
+ gint x,
+ gint y,
+ guint time_)
+{
+ PnlDockBin *self = (PnlDockBin *)widget;
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (GDK_IS_DRAG_CONTEXT (drag_context));
+
+ /*
+ * The purpose of this function is to determine of the location for which
+ * the drag is currently located, is a valid drop site. We first calculate
+ * the locations for the various zones, and then simply determine which
+ * zone we are in (or none).
+ */
+
+ if (priv->dnd_drag_x == -1 && priv->dnd_drag_y == -1)
+ pnl_dock_bin_drag_enter (self, drag_context, x, y, time_);
+
+ priv->dnd_drag_x = x;
+ priv->dnd_drag_y = y;
+
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+
+ return TRUE;
+}
+
+static void
+pnl_dock_bin_drag_leave (GtkWidget *widget,
+ GdkDragContext *context,
+ guint time_)
+{
+ PnlDockBin *self = (PnlDockBin *)widget;
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (GDK_IS_DRAG_CONTEXT (context));
+
+ priv->dnd_drag_x = -1;
+ priv->dnd_drag_y = -1;
+}
+
+static void
+pnl_dock_bin_grab_focus (GtkWidget *widget)
+{
+ PnlDockBin *self = (PnlDockBin *)widget;
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+ PnlDockBinChild *child;
+ guint i;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+
+ child = pnl_dock_bin_get_child_typed (self, PNL_DOCK_BIN_CHILD_CENTER);
+
+ if (child->widget != NULL)
+ {
+ if (gtk_widget_child_focus (child->widget, GTK_DIR_TAB_FORWARD))
+ return;
+ }
+
+ for (i = 0; i < G_N_ELEMENTS (priv->children); i++)
+ {
+ child = &priv->children [i];
+
+ if (child->widget != NULL)
+ {
+ if (gtk_widget_child_focus (child->widget, GTK_DIR_TAB_FORWARD))
+ return;
+ }
+ }
+}
+
+static GtkWidget *
+pnl_dock_bin_real_create_edge (PnlDockBin *self)
+{
+ g_assert (PNL_IS_DOCK_BIN (self));
+
+ return g_object_new (PNL_TYPE_DOCK_BIN_EDGE,
+ "visible", TRUE,
+ "reveal-child", FALSE,
+ NULL);
+}
+
+static void
+pnl_dock_bin_create_edge (PnlDockBin *self,
+ PnlDockBinChild *child,
+ PnlDockBinChildType type)
+{
+ GAction *action;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (child != NULL);
+ g_assert (type >= PNL_DOCK_BIN_CHILD_LEFT);
+ g_assert (type < LAST_PNL_DOCK_BIN_CHILD);
+
+ child->widget = PNL_DOCK_BIN_GET_CLASS (self)->create_edge (self);
+
+ if (child->widget == NULL)
+ {
+ g_warning ("%s failed to create edge widget",
+ G_OBJECT_TYPE_NAME (self));
+ return;
+ }
+ else if (!PNL_IS_DOCK_BIN_EDGE (child->widget))
+ {
+ g_warning ("%s child %s is not a PnlDockBinEdge",
+ G_OBJECT_TYPE_NAME (self),
+ G_OBJECT_TYPE_NAME (child));
+ return;
+ }
+
+ g_object_set (child->widget, "edge", (GtkPositionType)type, NULL);
+ gtk_widget_set_parent (g_object_ref_sink (child->widget), GTK_WIDGET (self));
+
+ action = pnl_dock_bin_get_action_for_type (self, type);
+ g_object_bind_property_full (child->widget, "reveal-child",
+ action, "state",
+ G_BINDING_SYNC_CREATE,
+ map_boolean_to_variant,
+ NULL, NULL, NULL);
+}
+
+static gboolean
+pnl_dock_bin_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ PnlDockBin *self = (PnlDockBin *)widget;
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+ GtkStyleContext *style_context;
+ gboolean ret;
+ guint i;
+ gint handle_size = 0;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (cr != NULL);
+
+ ret = GTK_WIDGET_CLASS (pnl_dock_bin_parent_class)->draw (widget, cr);
+
+ if (ret == GDK_EVENT_STOP)
+ return ret;
+
+ gtk_widget_style_get (widget,
+ "handle-size", &handle_size,
+ NULL);
+
+ if (handle_size == 0)
+ return ret;
+
+ style_context = gtk_widget_get_style_context (widget);
+
+ for (i = 0; i < PNL_DOCK_BIN_CHILD_CENTER; i++)
+ {
+ PnlDockBinChild *child = &priv->children [i];
+
+ if ((child->widget != NULL) &&
+ gtk_widget_get_visible (child->widget) &&
+ gtk_widget_get_child_visible (child->widget))
+ {
+ GtkAllocation handle;
+
+ gtk_widget_get_allocation (child->widget, &handle);
+
+ if (((child->type == PNL_DOCK_BIN_CHILD_LEFT) ||
+ (child->type == PNL_DOCK_BIN_CHILD_RIGHT)) &&
+ (handle.width <= handle_size))
+ continue;
+
+ if (((child->type == PNL_DOCK_BIN_CHILD_TOP) ||
+ (child->type == PNL_DOCK_BIN_CHILD_BOTTOM)) &&
+ (handle.height <= handle_size))
+ continue;
+
+ switch (child->type)
+ {
+ case PNL_DOCK_BIN_CHILD_LEFT:
+ handle.x += handle.width;
+ handle.width = handle_size;
+ break;
+
+ case PNL_DOCK_BIN_CHILD_RIGHT:
+ handle.x -= handle_size;
+ handle.width = handle_size;
+ break;
+
+ case PNL_DOCK_BIN_CHILD_TOP:
+ handle.y += handle.height;
+ handle.height = handle_size;
+ break;
+
+ case PNL_DOCK_BIN_CHILD_BOTTOM:
+ handle.y -= handle_size;
+ handle.height = handle_size;
+ break;
+
+ case PNL_DOCK_BIN_CHILD_CENTER:
+ case LAST_PNL_DOCK_BIN_CHILD:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ gtk_render_handle (style_context, cr, handle.x, handle.y, handle.width, handle.height);
+ }
+ }
+
+ return ret;
+}
+
+static void
+pnl_dock_bin_init_child (PnlDockBin *self,
+ PnlDockBinChild *child,
+ PnlDockBinChildType type)
+{
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (child != NULL);
+ g_assert (type >= PNL_DOCK_BIN_CHILD_LEFT);
+ g_assert (type < LAST_PNL_DOCK_BIN_CHILD);
+
+ child->type = type;
+ child->priority = (int)type * 100;
+}
+
+static void
+pnl_dock_bin_destroy (GtkWidget *widget)
+{
+ PnlDockBin *self = PNL_DOCK_BIN (widget);
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+
+ g_clear_object (&priv->actions);
+ g_clear_object (&priv->pan_gesture);
+
+ GTK_WIDGET_CLASS (pnl_dock_bin_parent_class)->destroy (widget);
+}
+
+static void
+pnl_dock_bin_get_child_property (GtkContainer *container,
+ GtkWidget *widget,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PnlDockBin *self = PNL_DOCK_BIN (container);
+ PnlDockBinChild *child = pnl_dock_bin_get_child (self, widget);
+
+ switch (prop_id)
+ {
+ case CHILD_PROP_PRIORITY:
+ g_value_set_int (value, child->priority);
+ break;
+
+ case CHILD_PROP_POSITION:
+ g_value_set_enum (value, (GtkPositionType)child->type);
+ break;
+
+ default:
+ GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec);
+ }
+}
+
+static void
+pnl_dock_bin_set_child_property (GtkContainer *container,
+ GtkWidget *widget,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PnlDockBin *self = PNL_DOCK_BIN (container);
+
+ switch (prop_id)
+ {
+ case CHILD_PROP_PRIORITY:
+ pnl_dock_bin_set_child_priority (self, widget, g_value_get_int (value));
+ break;
+
+ default:
+ GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec);
+ }
+}
+
+static void
+pnl_dock_bin_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PnlDockBin *self = PNL_DOCK_BIN (object);
+
+ switch (prop_id)
+ {
+ case PROP_MANAGER:
+ g_value_set_object (value, pnl_dock_item_get_manager (PNL_DOCK_ITEM (self)));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pnl_dock_bin_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PnlDockBin *self = PNL_DOCK_BIN (object);
+
+ switch (prop_id)
+ {
+ case PROP_MANAGER:
+ pnl_dock_item_set_manager (PNL_DOCK_ITEM (self), g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pnl_dock_bin_class_init (PnlDockBinClass *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 = pnl_dock_bin_get_property;
+ object_class->set_property = pnl_dock_bin_set_property;
+
+ widget_class->draw = pnl_dock_bin_draw;
+ widget_class->destroy = pnl_dock_bin_destroy;
+ widget_class->drag_leave = pnl_dock_bin_drag_leave;
+ widget_class->drag_motion = pnl_dock_bin_drag_motion;
+ widget_class->get_preferred_height = pnl_dock_bin_get_preferred_height;
+ widget_class->get_preferred_width = pnl_dock_bin_get_preferred_width;
+ widget_class->grab_focus = pnl_dock_bin_grab_focus;
+ widget_class->map = pnl_dock_bin_map;
+ widget_class->realize = pnl_dock_bin_realize;
+ widget_class->size_allocate = pnl_dock_bin_size_allocate;
+ widget_class->unmap = pnl_dock_bin_unmap;
+ widget_class->unrealize = pnl_dock_bin_unrealize;
+
+ container_class->add = pnl_dock_bin_add;
+ container_class->forall = pnl_dock_bin_forall;
+ container_class->get_child_property = pnl_dock_bin_get_child_property;
+ container_class->remove = pnl_dock_bin_remove;
+ container_class->set_child_property = pnl_dock_bin_set_child_property;
+
+ klass->create_edge = pnl_dock_bin_real_create_edge;
+
+ g_object_class_override_property (object_class, PROP_MANAGER, "manager");
+
+ child_properties [CHILD_PROP_POSITION] =
+ g_param_spec_enum ("position",
+ "Position",
+ "The position of the dock edge",
+ GTK_TYPE_POSITION_TYPE,
+ GTK_POS_LEFT,
+ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ child_properties [CHILD_PROP_PRIORITY] =
+ g_param_spec_int ("priority",
+ "Priority",
+ "The priority of the dock edge",
+ G_MININT,
+ 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]);
+
+ gtk_widget_class_set_css_name (widget_class, "dockbin");
+}
+
+static void
+pnl_dock_bin_init (PnlDockBin *self)
+{
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+ static GtkTargetEntry drag_entries[] = {
+ { (gchar *)"PNL_DOCK_BIN_WIDGET", GTK_TARGET_SAME_APP, 0 },
+ };
+ static const GActionEntry entries[] = {
+ { "left-visible", NULL, NULL, "false", pnl_dock_bin_visible_action },
+ { "right-visible", NULL, NULL, "false", pnl_dock_bin_visible_action },
+ { "top-visible", NULL, NULL, "false", pnl_dock_bin_visible_action },
+ { "bottom-visible", NULL, NULL, "false", pnl_dock_bin_visible_action },
+ };
+
+ gtk_widget_set_has_window (GTK_WIDGET (self), TRUE);
+
+ priv->actions = g_simple_action_group_new ();
+ g_action_map_add_action_entries (G_ACTION_MAP (priv->actions),
+ entries, G_N_ELEMENTS (entries),
+ self);
+ gtk_widget_insert_action_group (GTK_WIDGET (self), "dockbin", G_ACTION_GROUP (priv->actions));
+
+ pnl_dock_bin_create_pan_gesture (self);
+
+ gtk_drag_dest_set (GTK_WIDGET (self),
+ GTK_DEST_DEFAULT_ALL,
+ drag_entries,
+ G_N_ELEMENTS (drag_entries),
+ GDK_ACTION_MOVE);
+
+ priv->dnd_drag_x = -1;
+ priv->dnd_drag_y = -1;
+
+ pnl_dock_bin_init_child (self, &priv->children [0], PNL_DOCK_BIN_CHILD_LEFT);
+ pnl_dock_bin_init_child (self, &priv->children [1], PNL_DOCK_BIN_CHILD_RIGHT);
+ pnl_dock_bin_init_child (self, &priv->children [2], PNL_DOCK_BIN_CHILD_BOTTOM);
+ pnl_dock_bin_init_child (self, &priv->children [3], PNL_DOCK_BIN_CHILD_TOP);
+ pnl_dock_bin_init_child (self, &priv->children [4], PNL_DOCK_BIN_CHILD_CENTER);
+}
+
+GtkWidget *
+pnl_dock_bin_new (void)
+{
+ return g_object_new (PNL_TYPE_DOCK_BIN, NULL);
+}
+
+/**
+ * pnl_dock_bin_get_center_widget:
+ * @self: A #PnlDockBin
+ *
+ * Gets the center widget for the dock.
+ *
+ * Returns: (transfer none) (nullable): A #GtkWidget or %NULL.
+ */
+GtkWidget *
+pnl_dock_bin_get_center_widget (PnlDockBin *self)
+{
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+ PnlDockBinChild *child;
+
+ g_return_val_if_fail (PNL_IS_DOCK_BIN (self), NULL);
+
+ child = &priv->children [PNL_DOCK_BIN_CHILD_CENTER];
+
+ return child->widget;
+}
+
+/**
+ * pnl_dock_bin_get_top_edge:
+ * Returns: (transfer none): A #GtkWidget
+ */
+GtkWidget *
+pnl_dock_bin_get_top_edge (PnlDockBin *self)
+{
+ PnlDockBinChild *child;
+
+ g_return_val_if_fail (PNL_IS_DOCK_BIN (self), NULL);
+
+ child = pnl_dock_bin_get_child_typed (self, PNL_DOCK_BIN_CHILD_TOP);
+
+ if (child->widget == NULL)
+ pnl_dock_bin_create_edge (self, child, PNL_DOCK_BIN_CHILD_TOP);
+
+ return child->widget;
+}
+
+/**
+ * pnl_dock_bin_get_left_edge:
+ * Returns: (transfer none): A #GtkWidget
+ */
+GtkWidget *
+pnl_dock_bin_get_left_edge (PnlDockBin *self)
+{
+ PnlDockBinChild *child;
+
+ g_return_val_if_fail (PNL_IS_DOCK_BIN (self), NULL);
+
+ child = pnl_dock_bin_get_child_typed (self, PNL_DOCK_BIN_CHILD_LEFT);
+
+ if (child->widget == NULL)
+ pnl_dock_bin_create_edge (self, child, PNL_DOCK_BIN_CHILD_LEFT);
+
+ return child->widget;
+}
+
+/**
+ * pnl_dock_bin_get_bottom_edge:
+ * Returns: (transfer none): A #GtkWidget
+ */
+GtkWidget *
+pnl_dock_bin_get_bottom_edge (PnlDockBin *self)
+{
+ PnlDockBinChild *child;
+
+ g_return_val_if_fail (PNL_IS_DOCK_BIN (self), NULL);
+
+ child = pnl_dock_bin_get_child_typed (self, PNL_DOCK_BIN_CHILD_BOTTOM);
+
+ if (child->widget == NULL)
+ pnl_dock_bin_create_edge (self, child, PNL_DOCK_BIN_CHILD_BOTTOM);
+
+ return child->widget;
+}
+
+/**
+ * pnl_dock_bin_get_right_edge:
+ * Returns: (transfer none): A #GtkWidget
+ */
+GtkWidget *
+pnl_dock_bin_get_right_edge (PnlDockBin *self)
+{
+ PnlDockBinChild *child;
+
+ g_return_val_if_fail (PNL_IS_DOCK_BIN (self), NULL);
+
+ child = pnl_dock_bin_get_child_typed (self, PNL_DOCK_BIN_CHILD_RIGHT);
+
+ if (child->widget == NULL)
+ pnl_dock_bin_create_edge (self, child, PNL_DOCK_BIN_CHILD_RIGHT);
+
+ return child->widget;
+}
+
+static void
+pnl_dock_bin_init_dock_iface (PnlDockInterface *iface)
+{
+}
+
+static void
+pnl_dock_bin_add_child (GtkBuildable *buildable,
+ GtkBuilder *builder,
+ GObject *child,
+ const gchar *type)
+{
+ PnlDockBin *self = (PnlDockBin *)buildable;
+ GtkWidget *parent;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (GTK_IS_BUILDER (builder));
+ g_assert (G_IS_OBJECT (child));
+
+ if (!GTK_IS_WIDGET (child))
+ {
+ g_warning ("Attempt to add a child of type \"%s\" to a \"%s\"",
+ G_OBJECT_TYPE_NAME (child), G_OBJECT_TYPE_NAME (self));
+ return;
+ }
+
+ if (PNL_IS_DOCK_ITEM (child) &&
+ !pnl_dock_item_adopt (PNL_DOCK_ITEM (self), PNL_DOCK_ITEM (child)))
+ {
+ g_warning ("Child of type %s has a different PnlDockManager than %s",
+ G_OBJECT_TYPE_NAME (child), G_OBJECT_TYPE_NAME (self));
+ return;
+ }
+
+ if (!type || !*type || (g_strcmp0 ("center", type) == 0))
+ {
+ gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (child));
+ return;
+ }
+
+ if (g_strcmp0 ("top", type) == 0)
+ parent = pnl_dock_bin_get_top_edge (self);
+ else if (g_strcmp0 ("bottom", type) == 0)
+ parent = pnl_dock_bin_get_bottom_edge (self);
+ else if (g_strcmp0 ("right", type) == 0)
+ parent = pnl_dock_bin_get_right_edge (self);
+ else
+ parent = pnl_dock_bin_get_left_edge (self);
+
+ if (PNL_IS_DOCK_BIN_EDGE (parent))
+ gtk_container_add (GTK_CONTAINER (parent), GTK_WIDGET (child));
+}
+
+static void
+pnl_dock_bin_init_buildable_iface (GtkBuildableIface *iface)
+{
+ iface->add_child = pnl_dock_bin_add_child;
+}
+
+static void
+pnl_dock_bin_present_child (PnlDockItem *item,
+ PnlDockItem *widget)
+{
+ PnlDockBin *self = (PnlDockBin *)item;
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+ guint i;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (PNL_IS_DOCK_ITEM (widget));
+ g_assert (GTK_IS_WIDGET (widget));
+
+ for (i = 0; i < G_N_ELEMENTS (priv->children); i++)
+ {
+ PnlDockBinChild *child = &priv->children [i];
+
+ if (PNL_IS_DOCK_BIN_EDGE (child->widget) &&
+ gtk_widget_is_ancestor (GTK_WIDGET (child->widget), child->widget))
+ {
+ pnl_dock_revealer_set_reveal_child (PNL_DOCK_REVEALER (child->widget), TRUE);
+ return;
+ }
+ }
+}
+
+static gboolean
+pnl_dock_bin_get_child_visible (PnlDockItem *item,
+ PnlDockItem *child)
+{
+ PnlDockBin *self = (PnlDockBin *)item;
+ PnlDockBinPrivate *priv = pnl_dock_bin_get_instance_private (self);
+ GtkWidget *ancestor;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (PNL_IS_DOCK_ITEM (item));
+
+ ancestor = gtk_widget_get_ancestor (GTK_WIDGET (child), PNL_TYPE_DOCK_BIN_EDGE);
+
+ if (ancestor == NULL)
+ return FALSE;
+
+ if ((ancestor == priv->children [0].widget) ||
+ (ancestor == priv->children [1].widget) ||
+ (ancestor == priv->children [2].widget) ||
+ (ancestor == priv->children [3].widget))
+ return pnl_dock_revealer_get_reveal_child (PNL_DOCK_REVEALER (ancestor));
+
+ return FALSE;
+}
+
+static void
+pnl_dock_bin_set_child_visible (PnlDockItem *item,
+ PnlDockItem *child,
+ gboolean child_visible)
+{
+ PnlDockBin *self = (PnlDockBin *)item;
+ GtkWidget *ancestor;
+
+ g_assert (PNL_IS_DOCK_BIN (self));
+ g_assert (PNL_IS_DOCK_ITEM (item));
+
+ ancestor = gtk_widget_get_ancestor (GTK_WIDGET (child), PNL_TYPE_DOCK_BIN_EDGE);
+
+ if (ancestor != NULL)
+ pnl_dock_revealer_set_reveal_child (PNL_DOCK_REVEALER (ancestor), child_visible);
+}
+
+static void
+pnl_dock_bin_init_dock_item_iface (PnlDockItemInterface *iface)
+{
+ iface->present_child = pnl_dock_bin_present_child;
+ iface->get_child_visible = pnl_dock_bin_get_child_visible;
+ iface->set_child_visible = pnl_dock_bin_set_child_visible;
+}
diff --git a/contrib/pnl/pnl-dock-bin.h b/contrib/pnl/pnl-dock-bin.h
new file mode 100644
index 0000000..9ba8ec8
--- /dev/null
+++ b/contrib/pnl/pnl-dock-bin.h
@@ -0,0 +1,55 @@
+/* pnl-dock-bin-bin.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_DOCK_BIN_BIN_H
+#define PNL_DOCK_BIN_BIN_H
+
+#include "pnl-dock-types.h"
+
+G_BEGIN_DECLS
+
+struct _PnlDockBinClass
+{
+ GtkContainerClass parent;
+
+ GtkWidget *(*create_edge) (PnlDockBin *self);
+
+ void (*padding1) (void);
+ void (*padding2) (void);
+ void (*padding3) (void);
+ void (*padding4) (void);
+ void (*padding5) (void);
+ void (*padding6) (void);
+ void (*padding7) (void);
+ void (*padding8) (void);
+};
+
+GtkWidget *pnl_dock_bin_new (void);
+GtkWidget *pnl_dock_bin_get_center_widget (PnlDockBin *self);
+GtkWidget *pnl_dock_bin_get_top_edge (PnlDockBin *self);
+GtkWidget *pnl_dock_bin_get_left_edge (PnlDockBin *self);
+GtkWidget *pnl_dock_bin_get_bottom_edge (PnlDockBin *self);
+GtkWidget *pnl_dock_bin_get_right_edge (PnlDockBin *self);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_BIN_BIN_H */
diff --git a/contrib/pnl/pnl-dock-item.c b/contrib/pnl/pnl-dock-item.c
new file mode 100644
index 0000000..60e335e
--- /dev/null
+++ b/contrib/pnl/pnl-dock-item.c
@@ -0,0 +1,456 @@
+/* pnl-dock-item.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-dock-item.h"
+#include "pnl-dock-manager.h"
+#include "pnl-dock-widget.h"
+
+G_DEFINE_INTERFACE (PnlDockItem, pnl_dock_item, GTK_TYPE_WIDGET)
+
+/*
+ * The PnlDockItem is an interface that acts more like a mixin.
+ * This is mostly out of wanting to preserve widget inheritance
+ * without having to duplicate all sorts of plumbing.
+ */
+
+enum {
+ MANAGER_SET,
+ N_SIGNALS
+};
+
+static guint signals [N_SIGNALS];
+
+static void
+pnl_dock_item_real_set_manager (PnlDockItem *self,
+ PnlDockManager *manager)
+{
+ PnlDockManager *old_manager;
+
+ g_assert (PNL_IS_DOCK_ITEM (self));
+ g_assert (!manager || PNL_IS_DOCK_MANAGER (manager));
+
+ if (NULL != (old_manager = pnl_dock_item_get_manager (self)))
+ {
+ if (PNL_IS_DOCK (self))
+ pnl_dock_manager_unregister_dock (old_manager, PNL_DOCK (self));
+ }
+
+ if (manager != NULL)
+ {
+ g_object_set_data_full (G_OBJECT (self),
+ "PNL_DOCK_MANAGER",
+ g_object_ref (manager),
+ g_object_unref);
+ if (PNL_IS_DOCK (self))
+ pnl_dock_manager_register_dock (manager, PNL_DOCK (self));
+ }
+ else
+ g_object_set_data (G_OBJECT (self), "PNL_DOCK_MANAGER", NULL);
+
+ g_signal_emit (self, signals [MANAGER_SET], 0, old_manager);
+}
+
+static PnlDockManager *
+pnl_dock_item_real_get_manager (PnlDockItem *self)
+{
+ g_assert (PNL_IS_DOCK_ITEM (self));
+
+ return g_object_get_data (G_OBJECT (self), "PNL_DOCK_MANAGER");
+}
+
+static void
+pnl_dock_item_real_update_visibility (PnlDockItem *self)
+{
+ GtkWidget *parent;
+
+ g_assert (PNL_IS_DOCK_ITEM (self));
+
+ for (parent = gtk_widget_get_parent (GTK_WIDGET (self));
+ parent != NULL;
+ parent = gtk_widget_get_parent (parent))
+ {
+ if (PNL_IS_DOCK_ITEM (parent))
+ {
+ pnl_dock_item_update_visibility (PNL_DOCK_ITEM (parent));
+ break;
+ }
+ }
+}
+
+static void
+pnl_dock_item_propagate_manager (PnlDockItem *self)
+{
+ PnlDockManager *manager;
+ GPtrArray *ar;
+ guint i;
+
+ g_return_if_fail (PNL_IS_DOCK_ITEM (self));
+
+ if (!GTK_IS_CONTAINER (self))
+ return;
+
+ if (NULL == (manager = pnl_dock_item_get_manager (self)))
+ return;
+
+ if (NULL == (ar = g_object_get_data (G_OBJECT (self), "PNL_DOCK_ITEM_DESCENDANTS")))
+ return;
+
+ for (i = 0; i < ar->len; i++)
+ {
+ PnlDockItem *item = g_ptr_array_index (ar, i);
+
+ pnl_dock_item_set_manager (item, manager);
+ }
+}
+
+static void
+pnl_dock_item_real_manager_set (PnlDockItem *self,
+ PnlDockManager *manager)
+{
+ g_assert (PNL_IS_DOCK_ITEM (self));
+ g_assert (!manager || PNL_IS_DOCK_MANAGER (manager));
+
+ pnl_dock_item_propagate_manager (self);
+}
+
+static void
+pnl_dock_item_default_init (PnlDockItemInterface *iface)
+{
+ iface->get_manager = pnl_dock_item_real_get_manager;
+ iface->set_manager = pnl_dock_item_real_set_manager;
+ iface->manager_set = pnl_dock_item_real_manager_set;
+ iface->update_visibility = pnl_dock_item_real_update_visibility;
+
+ signals [MANAGER_SET] =
+ g_signal_new ("manager-set",
+ G_TYPE_FROM_INTERFACE (iface),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (PnlDockItemInterface, manager_set),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1, PNL_TYPE_DOCK_MANAGER);
+}
+
+/**
+ * pnl_dock_item_get_manager:
+ * @self: A #PnlDockItem
+ *
+ * Gets the dock manager for this dock item.
+ *
+ * Returns: (nullable) (transfer none): A #PnlDockmanager.
+ */
+PnlDockManager *
+pnl_dock_item_get_manager (PnlDockItem *self)
+{
+ g_return_val_if_fail (PNL_IS_DOCK_ITEM (self), NULL);
+
+ return PNL_DOCK_ITEM_GET_IFACE (self)->get_manager (self);
+}
+
+/**
+ * pnl_dock_item_set_manager:
+ * @self: A #PnlDockItem
+ * @manager: (nullable): A #PnlDockManager
+ *
+ * Sets the dock manager for this #PnlDockItem.
+ */
+void
+pnl_dock_item_set_manager (PnlDockItem *self,
+ PnlDockManager *manager)
+{
+ g_return_if_fail (PNL_IS_DOCK_ITEM (self));
+ g_return_if_fail (!manager || PNL_IS_DOCK_MANAGER (manager));
+
+ PNL_DOCK_ITEM_GET_IFACE (self)->set_manager (self, manager);
+}
+
+void
+pnl_dock_item_update_visibility (PnlDockItem *self)
+{
+ g_return_if_fail (PNL_IS_DOCK_ITEM (self));
+
+ PNL_DOCK_ITEM_GET_IFACE (self)->update_visibility (self);
+}
+
+static void
+pnl_dock_item_child_weak_notify (gpointer data,
+ GObject *where_object_was)
+{
+ PnlDockItem *self = data;
+ GPtrArray *descendants;
+
+ g_assert (PNL_IS_DOCK_ITEM (self));
+
+ descendants = g_object_get_data (G_OBJECT (self), "PNL_DOCK_ITEM_DESCENDANTS");
+
+ if (descendants != NULL)
+ g_ptr_array_remove (descendants, where_object_was);
+
+ pnl_dock_item_update_visibility (self);
+}
+
+static void
+pnl_dock_item_destroy (PnlDockItem *self)
+{
+ GPtrArray *descendants;
+ guint i;
+
+ g_assert (PNL_IS_DOCK_ITEM (self));
+
+ descendants = g_object_get_data (G_OBJECT (self), "PNL_DOCK_ITEM_DESCENDANTS");
+
+ if (descendants != NULL)
+ {
+ for (i = 0; i < descendants->len; i++)
+ {
+ PnlDockItem *child = g_ptr_array_index (descendants, i);
+
+ g_object_weak_unref (G_OBJECT (child),
+ pnl_dock_item_child_weak_notify,
+ self);
+ }
+
+ g_object_set_data (G_OBJECT (self), "PNL_DOCK_ITEM_DESCENDANTS", NULL);
+ g_ptr_array_unref (descendants);
+ }
+}
+
+static void
+pnl_dock_item_track_child (PnlDockItem *self,
+ PnlDockItem *child)
+{
+ GPtrArray *descendants;
+ guint i;
+
+ g_assert (PNL_IS_DOCK_ITEM (self));
+ g_assert (PNL_IS_DOCK_ITEM (child));
+
+ descendants = g_object_get_data (G_OBJECT (self), "PNL_DOCK_ITEM_DESCENDANTS");
+
+ if (descendants == NULL)
+ {
+ descendants = g_ptr_array_new ();
+ g_object_set_data (G_OBJECT (self), "PNL_DOCK_ITEM_DESCENDANTS", descendants);
+ g_signal_connect (self,
+ "destroy",
+ G_CALLBACK (pnl_dock_item_destroy),
+ NULL);
+ }
+
+ for (i = 0; i < descendants->len; i++)
+ {
+ PnlDockItem *item = g_ptr_array_index (descendants, i);
+
+ if (item == child)
+ return;
+ }
+
+ g_object_weak_ref (G_OBJECT (child),
+ pnl_dock_item_child_weak_notify,
+ self);
+
+ g_ptr_array_add (descendants, child);
+
+ pnl_dock_item_update_visibility (child);
+}
+
+gboolean
+pnl_dock_item_adopt (PnlDockItem *self,
+ PnlDockItem *child)
+{
+ PnlDockManager *manager;
+ PnlDockManager *child_manager;
+
+ g_return_val_if_fail (PNL_IS_DOCK_ITEM (self), FALSE);
+ g_return_val_if_fail (PNL_IS_DOCK_ITEM (child), FALSE);
+
+ manager = pnl_dock_item_get_manager (self);
+ child_manager = pnl_dock_item_get_manager (child);
+
+ if ((child_manager != NULL) && (manager != NULL) && (child_manager != manager))
+ return FALSE;
+
+ if (manager != NULL)
+ pnl_dock_item_set_manager (child, manager);
+
+ pnl_dock_item_track_child (self, child);
+
+ return TRUE;
+}
+
+void
+pnl_dock_item_present_child (PnlDockItem *self,
+ PnlDockItem *child)
+{
+ g_assert (PNL_IS_DOCK_ITEM (self));
+ g_assert (PNL_IS_DOCK_ITEM (child));
+
+#if 0
+ g_print ("present_child (%s, %s)\n",
+ G_OBJECT_TYPE_NAME (self),
+ G_OBJECT_TYPE_NAME (child));
+#endif
+
+ if (PNL_DOCK_ITEM_GET_IFACE (self)->present_child)
+ PNL_DOCK_ITEM_GET_IFACE (self)->present_child (self, child);
+}
+
+/**
+ * pnl_dock_item_present:
+ * @self: A #PnlDockItem
+ *
+ * This widget will walk the widget hierarchy to ensure that the
+ * dock item is visible to the user.
+ */
+void
+pnl_dock_item_present (PnlDockItem *self)
+{
+ GtkWidget *parent;
+
+ g_return_if_fail (PNL_IS_DOCK_ITEM (self));
+
+ for (parent = gtk_widget_get_parent (GTK_WIDGET (self));
+ parent != NULL;
+ parent = gtk_widget_get_parent (parent))
+ {
+ if (PNL_IS_DOCK_ITEM (parent))
+ {
+ pnl_dock_item_present_child (PNL_DOCK_ITEM (parent), self);
+ pnl_dock_item_present (PNL_DOCK_ITEM (parent));
+ return;
+ }
+ }
+}
+
+gboolean
+pnl_dock_item_has_widgets (PnlDockItem *self)
+{
+ GPtrArray *ar;
+
+ g_return_val_if_fail (PNL_IS_DOCK_ITEM (self), FALSE);
+
+ if (PNL_IS_DOCK_WIDGET (self))
+ return TRUE;
+
+ ar = g_object_get_data (G_OBJECT (self), "PNL_DOCK_ITEM_DESCENDANTS");
+
+ if (ar != NULL)
+ {
+ guint i;
+
+ for (i = 0; i < ar->len; i++)
+ {
+ PnlDockItem *child = g_ptr_array_index (ar, i);
+
+ if (pnl_dock_item_has_widgets (child))
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+pnl_dock_item_printf_internal (PnlDockItem *self,
+ GString *str,
+ guint depth)
+{
+ GPtrArray *ar;
+ guint i;
+
+ g_assert (PNL_IS_DOCK_ITEM (self));
+ g_assert (str != NULL);
+
+
+ for (i = 0; i < depth; i++)
+ g_string_append_c (str, ' ');
+
+ g_string_append_printf (str, "%s\n", G_OBJECT_TYPE_NAME (self));
+
+ ++depth;
+
+ ar = g_object_get_data (G_OBJECT (self), "PNL_DOCK_ITEM_DESCENDANTS");
+
+ if (ar != NULL)
+ {
+ for (i = 0; i < ar->len; i++)
+ pnl_dock_item_printf_internal (g_ptr_array_index (ar, i), str, depth);
+ }
+}
+
+void
+_pnl_dock_item_printf (PnlDockItem *self)
+{
+ GString *str;
+
+ g_return_if_fail (PNL_IS_DOCK_ITEM (self));
+
+ str = g_string_new (NULL);
+ pnl_dock_item_printf_internal (self, str, 0);
+ g_printerr ("%s", str->str);
+ g_string_free (str, TRUE);
+}
+
+/**
+ * pnl_dock_item_get_parent:
+ *
+ * Gets the parent #PnlDockItem, or %NULL.
+ *
+ * Returns: (transfer none) (nullable): A #PnlDockItem or %NULL.
+ */
+PnlDockItem *
+pnl_dock_item_get_parent (PnlDockItem *self)
+{
+ GtkWidget *parent;
+
+ g_return_val_if_fail (PNL_IS_DOCK_ITEM (self), NULL);
+
+ for (parent = gtk_widget_get_parent (GTK_WIDGET (self));
+ parent != NULL;
+ parent = gtk_widget_get_parent (parent))
+ {
+ if (PNL_IS_DOCK_ITEM (parent))
+ return PNL_DOCK_ITEM (parent);
+ }
+
+ return NULL;
+}
+
+gboolean
+pnl_dock_item_get_child_visible (PnlDockItem *self,
+ PnlDockItem *child)
+{
+ g_return_val_if_fail (PNL_IS_DOCK_ITEM (self), FALSE);
+ g_return_val_if_fail (PNL_IS_DOCK_ITEM (child), FALSE);
+
+ if (PNL_DOCK_ITEM_GET_IFACE (self)->get_child_visible)
+ return PNL_DOCK_ITEM_GET_IFACE (self)->get_child_visible (self, child);
+
+ return TRUE;
+}
+
+void
+pnl_dock_item_set_child_visible (PnlDockItem *self,
+ PnlDockItem *child,
+ gboolean child_visible)
+{
+ g_return_if_fail (PNL_IS_DOCK_ITEM (self));
+ g_return_if_fail (PNL_IS_DOCK_ITEM (child));
+
+ if (PNL_DOCK_ITEM_GET_IFACE (self)->set_child_visible)
+ PNL_DOCK_ITEM_GET_IFACE (self)->set_child_visible (self, child, child_visible);
+}
diff --git a/contrib/pnl/pnl-dock-item.h b/contrib/pnl/pnl-dock-item.h
new file mode 100644
index 0000000..2a067e1
--- /dev/null
+++ b/contrib/pnl/pnl-dock-item.h
@@ -0,0 +1,65 @@
+/* pnl-dock-item.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PNL_DOCK_ITEM_H
+#define PNL_DOCK_ITEM_H
+
+#include "pnl-dock-manager.h"
+
+G_BEGIN_DECLS
+
+struct _PnlDockItemInterface
+{
+ GTypeInterface parent;
+
+ void (*set_manager) (PnlDockItem *self,
+ PnlDockManager *manager);
+ PnlDockManager *(*get_manager) (PnlDockItem *self);
+ void (*manager_set) (PnlDockItem *self,
+ PnlDockManager *old_manager);
+ void (*present_child) (PnlDockItem *self,
+ PnlDockItem *child);
+ void (*update_visibility) (PnlDockItem *self);
+ gboolean (*get_child_visible) (PnlDockItem *self,
+ PnlDockItem *child);
+ void (*set_child_visible) (PnlDockItem *self,
+ PnlDockItem *child,
+ gboolean child_visible);
+};
+
+PnlDockManager *pnl_dock_item_get_manager (PnlDockItem *self);
+void pnl_dock_item_set_manager (PnlDockItem *self,
+ PnlDockManager *manager);
+gboolean pnl_dock_item_adopt (PnlDockItem *self,
+ PnlDockItem *child);
+void pnl_dock_item_present (PnlDockItem *self);
+void pnl_dock_item_present_child (PnlDockItem *self,
+ PnlDockItem *child);
+void pnl_dock_item_update_visibility (PnlDockItem *self);
+gboolean pnl_dock_item_has_widgets (PnlDockItem *self);
+gboolean pnl_dock_item_get_child_visible (PnlDockItem *self,
+ PnlDockItem *child);
+void pnl_dock_item_set_child_visible (PnlDockItem *self,
+ PnlDockItem *child,
+ gboolean child_visible);
+PnlDockItem *pnl_dock_item_get_parent (PnlDockItem *self);
+void _pnl_dock_item_printf (PnlDockItem *self);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_ITEM_H */
diff --git a/contrib/pnl/pnl-dock-manager.c b/contrib/pnl/pnl-dock-manager.c
new file mode 100644
index 0000000..a116da9
--- /dev/null
+++ b/contrib/pnl/pnl-dock-manager.c
@@ -0,0 +1,306 @@
+/* pnl-dock-manager.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-dock-manager.h"
+#include "pnl-dock-transient-grab.h"
+
+typedef struct
+{
+ GPtrArray *docks;
+ PnlDockTransientGrab *grab;
+} PnlDockManagerPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (PnlDockManager, pnl_dock_manager, G_TYPE_OBJECT)
+
+enum {
+ REGISTER_DOCK,
+ UNREGISTER_DOCK,
+ N_SIGNALS
+};
+
+static guint signals [N_SIGNALS];
+
+static void
+pnl_dock_manager_set_focus (PnlDockManager *self,
+ GtkWidget *focus,
+ GtkWidget *toplevel)
+{
+ PnlDockManagerPrivate *priv = pnl_dock_manager_get_instance_private (self);
+ PnlDockTransientGrab *grab = NULL;
+ GtkWidget *parent;
+
+ g_assert (PNL_IS_DOCK_MANAGER (self));
+ g_assert (GTK_IS_WINDOW (toplevel));
+
+ /*
+ * Don't do anything if we get a NULL focus. Instead, wait for the focus
+ * to be updated with a widget.
+ */
+ if (focus == NULL)
+ return;
+
+#if 0
+ g_print ("Attempting to set focus on %s\n", G_OBJECT_TYPE_NAME (focus));
+#endif
+
+ if (priv->grab != NULL)
+ {
+ /*
+ * If the current transient grab contains the new focus widget,
+ * then there is nothing for us to do now.
+ */
+ if (pnl_dock_transient_grab_is_descendant (priv->grab, focus))
+ return;
+ }
+
+ /*
+ * If their is a PnlDockItem in the hierarchy, create a new transient grab.
+ */
+ for (parent = focus;
+ parent != NULL;
+ parent = gtk_widget_get_parent (parent))
+ {
+ if (PNL_IS_DOCK_ITEM (parent))
+ {
+ if (grab == NULL)
+ grab = pnl_dock_transient_grab_new ();
+ pnl_dock_transient_grab_add_item (grab, PNL_DOCK_ITEM (parent));
+ }
+ }
+
+ /*
+ * Steal common hierarchy so that we don't hide it when breaking grabs.
+ */
+ if (priv->grab != NULL && grab != NULL)
+ pnl_dock_transient_grab_steal_common_ancestors (grab, priv->grab);
+
+ /*
+ * Release the previous grab.
+ */
+ if (priv->grab != NULL)
+ {
+ pnl_dock_transient_grab_release (priv->grab);
+ g_clear_object (&priv->grab);
+ }
+
+ /* Start the grab process */
+ if (grab != NULL)
+ {
+ priv->grab = grab;
+ pnl_dock_transient_grab_acquire (priv->grab);
+ }
+}
+
+static void
+pnl_dock_manager_hierarchy_changed (PnlDockManager *self,
+ GtkWidget *old_toplevel,
+ GtkWidget *widget)
+{
+ GtkWidget *toplevel;
+
+ g_assert (PNL_IS_DOCK_MANAGER (self));
+ g_assert (!old_toplevel || GTK_IS_WIDGET (old_toplevel));
+ g_assert (GTK_IS_WIDGET (widget));
+
+ if (GTK_IS_WINDOW (old_toplevel))
+ g_signal_handlers_disconnect_by_func (old_toplevel,
+ G_CALLBACK (pnl_dock_manager_set_focus),
+ self);
+
+ toplevel = gtk_widget_get_toplevel (widget);
+
+ if (GTK_IS_WINDOW (toplevel))
+ g_signal_connect_object (toplevel,
+ "set-focus",
+ G_CALLBACK (pnl_dock_manager_set_focus),
+ self,
+ G_CONNECT_SWAPPED);
+}
+
+static void
+pnl_dock_manager_watch_toplevel (PnlDockManager *self,
+ GtkWidget *widget)
+{
+ g_assert (PNL_IS_DOCK_MANAGER (self));
+ g_assert (GTK_IS_WIDGET (widget));
+
+ g_signal_connect_object (widget,
+ "hierarchy-changed",
+ G_CALLBACK (pnl_dock_manager_hierarchy_changed),
+ self,
+ G_CONNECT_SWAPPED);
+
+ pnl_dock_manager_hierarchy_changed (self, NULL, widget);
+}
+
+static void
+pnl_dock_manager_weak_notify (gpointer data,
+ GObject *where_the_object_was)
+{
+ PnlDockManager *self = data;
+ PnlDockManagerPrivate *priv = pnl_dock_manager_get_instance_private (self);
+
+ g_assert (PNL_IS_DOCK_MANAGER (self));
+
+ g_ptr_array_remove (priv->docks, where_the_object_was);
+}
+
+static void
+pnl_dock_manager_real_register_dock (PnlDockManager *self,
+ PnlDock *dock)
+{
+ PnlDockManagerPrivate *priv = pnl_dock_manager_get_instance_private (self);
+
+ g_return_if_fail (PNL_IS_DOCK_MANAGER (self));
+ g_return_if_fail (PNL_IS_DOCK (dock));
+
+ g_object_weak_ref (G_OBJECT (dock), pnl_dock_manager_weak_notify, self);
+ g_ptr_array_add (priv->docks, dock);
+ pnl_dock_manager_watch_toplevel (self, GTK_WIDGET (dock));
+}
+
+static void
+pnl_dock_manager_real_unregister_dock (PnlDockManager *self,
+ PnlDock *dock)
+{
+ PnlDockManagerPrivate *priv = pnl_dock_manager_get_instance_private (self);
+ guint i;
+
+ g_return_if_fail (PNL_IS_DOCK_MANAGER (self));
+ g_return_if_fail (PNL_IS_DOCK (dock));
+
+ for (i = 0; i < priv->docks->len; i++)
+ {
+ PnlDock *iter = g_ptr_array_index (priv->docks, i);
+
+ if (iter == dock)
+ {
+ g_object_weak_unref (G_OBJECT (dock), pnl_dock_manager_weak_notify, self);
+ g_ptr_array_remove_index (priv->docks, i);
+ break;
+ }
+ }
+}
+
+static void
+pnl_dock_manager_finalize (GObject *object)
+{
+ PnlDockManager *self = (PnlDockManager *)object;
+ PnlDockManagerPrivate *priv = pnl_dock_manager_get_instance_private (self);
+
+ while (priv->docks->len > 0)
+ {
+ PnlDock *dock = g_ptr_array_index (priv->docks, priv->docks->len - 1);
+
+ g_object_weak_unref (G_OBJECT (dock), pnl_dock_manager_weak_notify, self);
+ g_ptr_array_remove_index (priv->docks, priv->docks->len - 1);
+ }
+
+ g_clear_pointer (&priv->docks, g_ptr_array_unref);
+
+ G_OBJECT_CLASS (pnl_dock_manager_parent_class)->finalize (object);
+}
+
+static void
+pnl_dock_manager_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (prop_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pnl_dock_manager_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (prop_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pnl_dock_manager_class_init (PnlDockManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = pnl_dock_manager_finalize;
+ object_class->get_property = pnl_dock_manager_get_property;
+ object_class->set_property = pnl_dock_manager_set_property;
+
+ klass->register_dock = pnl_dock_manager_real_register_dock;
+ klass->unregister_dock = pnl_dock_manager_real_unregister_dock;
+
+ signals [REGISTER_DOCK] =
+ g_signal_new ("register-dock",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (PnlDockManagerClass, register_dock),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1, PNL_TYPE_DOCK);
+
+ signals [UNREGISTER_DOCK] =
+ g_signal_new ("unregister-dock",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (PnlDockManagerClass, unregister_dock),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1, PNL_TYPE_DOCK);
+}
+
+static void
+pnl_dock_manager_init (PnlDockManager *self)
+{
+ PnlDockManagerPrivate *priv = pnl_dock_manager_get_instance_private (self);
+
+ priv->docks = g_ptr_array_new ();
+}
+
+PnlDockManager *
+pnl_dock_manager_new (void)
+{
+ return g_object_new (PNL_TYPE_DOCK_MANAGER, NULL);
+}
+
+void
+pnl_dock_manager_register_dock (PnlDockManager *self,
+ PnlDock *dock)
+{
+ g_return_if_fail (PNL_IS_DOCK_MANAGER (self));
+ g_return_if_fail (PNL_IS_DOCK (dock));
+
+ g_signal_emit (self, signals [REGISTER_DOCK], 0, dock);
+}
+
+void
+pnl_dock_manager_unregister_dock (PnlDockManager *self,
+ PnlDock *dock)
+{
+ g_return_if_fail (PNL_IS_DOCK_MANAGER (self));
+ g_return_if_fail (PNL_IS_DOCK (dock));
+
+ g_signal_emit (self, signals [UNREGISTER_DOCK], 0, dock);
+}
diff --git a/contrib/pnl/pnl-dock-manager.h b/contrib/pnl/pnl-dock-manager.h
new file mode 100644
index 0000000..a6d022d
--- /dev/null
+++ b/contrib/pnl/pnl-dock-manager.h
@@ -0,0 +1,57 @@
+/* pnl-dock-manager.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_DOCK_MANAGER_H
+#define PNL_DOCK_MANAGER_H
+
+#include "pnl-dock-types.h"
+
+G_BEGIN_DECLS
+
+struct _PnlDockManagerClass
+{
+ GObjectClass parent;
+
+ void (*register_dock) (PnlDockManager *self,
+ PnlDock *dock);
+ void (*unregister_dock) (PnlDockManager *self,
+ PnlDock *dock);
+
+ void (*padding1) (void);
+ void (*padding2) (void);
+ void (*padding3) (void);
+ void (*padding4) (void);
+ void (*padding5) (void);
+ void (*padding6) (void);
+ void (*padding7) (void);
+ void (*padding8) (void);
+};
+
+PnlDockManager *pnl_dock_manager_new (void);
+void pnl_dock_manager_register_dock (PnlDockManager *self,
+ PnlDock *dock);
+void pnl_dock_manager_unregister_dock (PnlDockManager *self,
+ PnlDock *dock);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_MANAGER_H */
diff --git a/contrib/pnl/pnl-dock-overlay-edge-private.h b/contrib/pnl/pnl-dock-overlay-edge-private.h
new file mode 100644
index 0000000..1bcc1e5
--- /dev/null
+++ b/contrib/pnl/pnl-dock-overlay-edge-private.h
@@ -0,0 +1,39 @@
+/* pnl-dock-overlay-edge-private.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PNL_DOCK_OVERLAY_EDGE_PRIVATE_H
+#define PNL_DOCK_OVERLAY_EDGE_PRIVATE_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define PNL_TYPE_DOCK_OVERLAY_EDGE (pnl_dock_overlay_edge_get_type())
+
+G_DECLARE_FINAL_TYPE (PnlDockOverlayEdge, pnl_dock_overlay_edge, PNL, DOCK_OVERLAY_EDGE, GtkBin)
+
+GtkPositionType pnl_dock_overlay_edge_get_edge (PnlDockOverlayEdge *self);
+void pnl_dock_overlay_edge_set_edge (PnlDockOverlayEdge *self,
+ GtkPositionType edge);
+gint pnl_dock_overlay_edge_get_position (PnlDockOverlayEdge *self);
+void pnl_dock_overlay_edge_set_position (PnlDockOverlayEdge *self,
+ gint position);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_OVERLAY_EDGE_PRIVATE_H */
diff --git a/contrib/pnl/pnl-dock-overlay-edge.c b/contrib/pnl/pnl-dock-overlay-edge.c
new file mode 100644
index 0000000..e85f69e
--- /dev/null
+++ b/contrib/pnl/pnl-dock-overlay-edge.c
@@ -0,0 +1,290 @@
+/* pnl-dock-overlay-edge.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-dock-item.h"
+#include "pnl-dock-overlay-edge-private.h"
+#include "pnl-dock-paned.h"
+#include "pnl-dock-paned-private.h"
+#include "pnl-dock-stack.h"
+#include "pnl-util-private.h"
+
+struct _PnlDockOverlayEdge
+{
+ GtkBin parent;
+ GtkPositionType edge : 2;
+ gint position;
+};
+
+G_DEFINE_TYPE_EXTENDED (PnlDockOverlayEdge, pnl_dock_overlay_edge, GTK_TYPE_BIN, 0,
+ G_IMPLEMENT_INTERFACE (PNL_TYPE_DOCK_ITEM, NULL))
+
+enum {
+ PROP_0,
+ PROP_EDGE,
+ PROP_POSITION,
+ N_PROPS
+};
+
+enum {
+ STYLE_PROP_0,
+ STYLE_PROP_OVERLAP_SIZE,
+ STYLE_PROP_MNEMONIC_OVERLAP_SIZE,
+ N_STYLE_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+static GParamSpec *style_properties [N_STYLE_PROPS];
+
+static void
+pnl_dock_overlay_edge_update_edge (PnlDockOverlayEdge *self)
+{
+ GtkWidget *child;
+ GtkPositionType edge;
+ GtkStyleContext *style_context;
+ GtkOrientation orientation;
+ const gchar *style_class;
+
+ g_assert (PNL_IS_DOCK_OVERLAY_EDGE (self));
+
+ style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
+
+ gtk_style_context_remove_class (style_context, "left-edge");
+ gtk_style_context_remove_class (style_context, "right-edge");
+ gtk_style_context_remove_class (style_context, "top-edge");
+ gtk_style_context_remove_class (style_context, "bottom-edge");
+
+ switch (self->edge)
+ {
+ case GTK_POS_TOP:
+ edge = GTK_POS_BOTTOM;
+ orientation = GTK_ORIENTATION_HORIZONTAL;
+ style_class = "top-edge";
+ break;
+
+ case GTK_POS_BOTTOM:
+ edge = GTK_POS_TOP;
+ orientation = GTK_ORIENTATION_HORIZONTAL;
+ style_class = "bottom-edge";
+ break;
+
+ case GTK_POS_LEFT:
+ edge = GTK_POS_RIGHT;
+ orientation = GTK_ORIENTATION_VERTICAL;
+ style_class = "left-edge";
+ break;
+
+ case GTK_POS_RIGHT:
+ edge = GTK_POS_LEFT;
+ orientation = GTK_ORIENTATION_VERTICAL;
+ style_class = "right-edge";
+ break;
+
+ default:
+ g_assert_not_reached ();
+ return;
+ }
+
+ gtk_style_context_add_class (style_context, style_class);
+
+ child = gtk_bin_get_child (GTK_BIN (self));
+
+ if (PNL_IS_DOCK_PANED (child))
+ {
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (child), orientation);
+ pnl_dock_paned_set_child_edge (PNL_DOCK_PANED (child), edge);
+ }
+ else if (PNL_IS_DOCK_STACK (child))
+ {
+ pnl_dock_stack_set_edge (PNL_DOCK_STACK (child), edge);
+ }
+}
+
+static void
+pnl_dock_overlay_edge_add (GtkContainer *container,
+ GtkWidget *child)
+{
+ PnlDockOverlayEdge *self = (PnlDockOverlayEdge *)container;
+
+ g_assert (PNL_IS_DOCK_OVERLAY_EDGE (self));
+ g_assert (GTK_IS_WIDGET (child));
+
+ GTK_CONTAINER_CLASS (pnl_dock_overlay_edge_parent_class)->add (container, child);
+
+ pnl_dock_overlay_edge_update_edge (self);
+
+ if (PNL_IS_DOCK_ITEM (child))
+ pnl_dock_item_adopt (PNL_DOCK_ITEM (self), PNL_DOCK_ITEM (child));
+}
+
+static void
+pnl_dock_overlay_edge_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PnlDockOverlayEdge *self = PNL_DOCK_OVERLAY_EDGE (object);
+
+ switch (prop_id)
+ {
+ case PROP_EDGE:
+ g_value_set_enum (value, pnl_dock_overlay_edge_get_edge (self));
+ break;
+
+ case PROP_POSITION:
+ g_value_set_int (value, pnl_dock_overlay_edge_get_position (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pnl_dock_overlay_edge_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PnlDockOverlayEdge *self = PNL_DOCK_OVERLAY_EDGE (object);
+
+ switch (prop_id)
+ {
+ case PROP_EDGE:
+ pnl_dock_overlay_edge_set_edge (self, g_value_get_enum (value));
+ break;
+
+ case PROP_POSITION:
+ pnl_dock_overlay_edge_set_position (self, g_value_get_int (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pnl_dock_overlay_edge_class_init (PnlDockOverlayEdgeClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->get_property = pnl_dock_overlay_edge_get_property;
+ object_class->set_property = pnl_dock_overlay_edge_set_property;
+
+ container_class->add = pnl_dock_overlay_edge_add;
+
+ widget_class->draw = pnl_gtk_bin_draw;
+ widget_class->size_allocate = pnl_gtk_bin_size_allocate;
+
+ properties [PROP_EDGE] =
+ g_param_spec_enum ("edge",
+ "Edge",
+ "Edge",
+ GTK_TYPE_POSITION_TYPE,
+ GTK_POS_LEFT,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_POSITION] =
+ g_param_spec_int ("position",
+ "Position",
+ "The size of the edge",
+ 0,
+ G_MAXINT,
+ 0,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ style_properties [STYLE_PROP_MNEMONIC_OVERLAP_SIZE] =
+ g_param_spec_int ("mnemonic-overlap-size",
+ "Mnemonic Overlap Size",
+ "The amount of pixels to overlap when mnemonics are visible",
+ 0,
+ G_MAXINT,
+ 30,
+ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ gtk_widget_class_install_style_property (widget_class,
+ style_properties [STYLE_PROP_MNEMONIC_OVERLAP_SIZE]);
+
+ style_properties [STYLE_PROP_OVERLAP_SIZE] =
+ g_param_spec_int ("overlap-size",
+ "Overlap Size",
+ "The amount of pixels to overlap when hidden",
+ 0,
+ G_MAXINT,
+ 5,
+ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ gtk_widget_class_install_style_property (widget_class,
+ style_properties [STYLE_PROP_OVERLAP_SIZE]);
+
+ gtk_widget_class_set_css_name (widget_class, "dockoverlayedge");
+}
+
+static void
+pnl_dock_overlay_edge_init (PnlDockOverlayEdge *self)
+{
+}
+
+gint
+pnl_dock_overlay_edge_get_position (PnlDockOverlayEdge *self)
+{
+ g_return_val_if_fail (PNL_IS_DOCK_OVERLAY_EDGE (self), 0);
+
+ return self->position;
+}
+
+void
+pnl_dock_overlay_edge_set_position (PnlDockOverlayEdge *self,
+ gint position)
+{
+ g_return_if_fail (PNL_IS_DOCK_OVERLAY_EDGE (self));
+ g_return_if_fail (position >= 0);
+
+ if (position != self->position)
+ {
+ self->position = position;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_POSITION]);
+ }
+}
+
+GtkPositionType
+pnl_dock_overlay_edge_get_edge (PnlDockOverlayEdge *self)
+{
+ g_return_val_if_fail (PNL_IS_DOCK_OVERLAY_EDGE (self), 0);
+
+ return self->edge;
+}
+
+void
+pnl_dock_overlay_edge_set_edge (PnlDockOverlayEdge *self,
+ GtkPositionType edge)
+{
+ g_return_if_fail (PNL_IS_DOCK_OVERLAY_EDGE (self));
+ g_return_if_fail (edge >= 0);
+ g_return_if_fail (edge <= 3);
+
+ if (edge != self->edge)
+ {
+ self->edge = edge;
+ pnl_dock_overlay_edge_update_edge (self);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_EDGE]);
+ }
+}
diff --git a/contrib/pnl/pnl-dock-overlay.c b/contrib/pnl/pnl-dock-overlay.c
new file mode 100644
index 0000000..167d1e7
--- /dev/null
+++ b/contrib/pnl/pnl-dock-overlay.c
@@ -0,0 +1,704 @@
+/* pnl-dock-overlay.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-animation.h"
+#include "pnl-dock-overlay-edge-private.h"
+#include "pnl-dock-item.h"
+#include "pnl-dock-overlay.h"
+#include "pnl-tab.h"
+#include "pnl-tab-strip.h"
+#include "pnl-util-private.h"
+
+#define REVEAL_DURATION 300
+#define MNEMONIC_REVEAL_DURATION 200
+
+typedef struct
+{
+ GtkOverlay *overlay;
+ PnlDockOverlayEdge *edges [4];
+ GtkAdjustment *edge_adj [4];
+ GtkAdjustment *edge_handle_adj [4];
+ guint child_reveal : 4;
+} PnlDockOverlayPrivate;
+
+static void pnl_dock_overlay_init_dock_iface (PnlDockInterface *iface);
+static void pnl_dock_overlay_init_dock_item_iface (PnlDockItemInterface *iface);
+static void pnl_dock_overlay_init_buildable_iface (GtkBuildableIface *iface);
+static void pnl_dock_overlay_set_child_reveal (PnlDockOverlay *self,
+ GtkWidget *child,
+ gboolean reveal);
+
+G_DEFINE_TYPE_EXTENDED (PnlDockOverlay, pnl_dock_overlay, GTK_TYPE_EVENT_BOX, 0,
+ G_ADD_PRIVATE (PnlDockOverlay)
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, pnl_dock_overlay_init_buildable_iface)
+ G_IMPLEMENT_INTERFACE (PNL_TYPE_DOCK_ITEM, pnl_dock_overlay_init_dock_item_iface)
+ G_IMPLEMENT_INTERFACE (PNL_TYPE_DOCK, pnl_dock_overlay_init_dock_iface))
+
+enum {
+ PROP_0,
+ PROP_MANAGER,
+ N_PROPS
+};
+
+enum {
+ CHILD_PROP_0,
+ CHILD_PROP_REVEAL,
+ N_CHILD_PROPS
+};
+
+enum {
+ HIDE_EDGES,
+ N_SIGNALS
+};
+
+static GParamSpec *child_properties [N_CHILD_PROPS];
+static guint signals [N_SIGNALS];
+
+static void
+pnl_dock_overlay_update_focus_chain (PnlDockOverlay *self)
+{
+ PnlDockOverlayPrivate *priv = pnl_dock_overlay_get_instance_private (self);
+ GList *focus_chain = NULL;
+ GtkWidget *child;
+ guint i;
+
+ g_assert (PNL_IS_DOCK_OVERLAY (self));
+
+ for (i = G_N_ELEMENTS (priv->edges); i > 0; i--)
+ {
+ PnlDockOverlayEdge *edge = priv->edges [i - 1];
+
+ if (edge != NULL)
+ focus_chain = g_list_prepend (focus_chain, edge);
+ }
+
+ child = gtk_bin_get_child (GTK_BIN (self));
+
+ if (child != NULL)
+ focus_chain = g_list_prepend (focus_chain, child);
+
+ if (focus_chain != NULL)
+ {
+ gtk_container_set_focus_chain (GTK_CONTAINER (self), focus_chain);
+ g_list_free (focus_chain);
+ }
+}
+
+static void
+pnl_dock_overlay_get_edge_position (PnlDockOverlay *self,
+ PnlDockOverlayEdge *edge,
+ GtkAllocation *allocation)
+{
+ PnlDockOverlayPrivate *priv = pnl_dock_overlay_get_instance_private (self);
+ GtkPositionType type;
+ gdouble value;
+ gdouble handle_value;
+ gdouble flipped_value;
+ gint nat_width;
+ gint nat_height;
+
+ g_assert (PNL_IS_DOCK_OVERLAY (self));
+ g_assert (PNL_IS_DOCK_OVERLAY_EDGE (edge));
+ g_assert (allocation != NULL);
+
+ gtk_widget_get_allocation (GTK_WIDGET (self), allocation);
+
+ allocation->x = 0;
+ allocation->y = 0;
+
+ type = pnl_dock_overlay_edge_get_edge (edge);
+
+ if (type == GTK_POS_LEFT || type == GTK_POS_RIGHT)
+ {
+ nat_height = MAX (allocation->height, 1);
+ gtk_widget_get_preferred_width_for_height (GTK_WIDGET (edge), nat_height, NULL, &nat_width);
+ }
+ else if (type == GTK_POS_TOP || type == GTK_POS_BOTTOM)
+ {
+ nat_width = MAX (allocation->width, 1);
+ gtk_widget_get_preferred_height_for_width (GTK_WIDGET (edge), nat_width, NULL, &nat_height);
+ }
+ else
+ {
+ g_assert_not_reached ();
+ return;
+ }
+
+ value = gtk_adjustment_get_value (priv->edge_adj [type]);
+ flipped_value = 1.0 - value;
+
+ handle_value = gtk_adjustment_get_value (priv->edge_handle_adj [type]);
+
+ switch (type)
+ {
+ case GTK_POS_LEFT:
+ allocation->width = nat_width;
+ allocation->x -= nat_width * value;
+ if (flipped_value * nat_width <= handle_value)
+ allocation->x += (handle_value - (flipped_value * nat_width));
+ break;
+
+ case GTK_POS_RIGHT:
+ allocation->x = allocation->x + allocation->width - nat_width;
+ allocation->width = nat_width;
+ allocation->x += nat_width * value;
+ if (flipped_value * nat_width <= handle_value)
+ allocation->x -= (handle_value - (flipped_value * nat_width));
+ break;
+
+ case GTK_POS_BOTTOM:
+ allocation->y = allocation->y + allocation->height - nat_height;
+ allocation->height = nat_height;
+ allocation->y += nat_height * value;
+ if (flipped_value * nat_height <= handle_value)
+ allocation->y -= (handle_value - (flipped_value * nat_height));
+ break;
+
+ case GTK_POS_TOP:
+ allocation->height = nat_height;
+ allocation->y -= nat_height * value;
+ if (flipped_value * nat_height <= handle_value)
+ allocation->y += (handle_value - (flipped_value * nat_height));
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static gboolean
+pnl_dock_overlay_get_child_position (PnlDockOverlay *self,
+ GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ g_assert (PNL_IS_DOCK_OVERLAY (self));
+ g_assert (GTK_IS_WIDGET (widget));
+ g_assert (allocation != NULL);
+
+ if (PNL_IS_DOCK_OVERLAY_EDGE (widget))
+ {
+ pnl_dock_overlay_get_edge_position (self, PNL_DOCK_OVERLAY_EDGE (widget), allocation);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+pnl_dock_overlay_add (GtkContainer *container,
+ GtkWidget *widget)
+{
+ PnlDockOverlay *self = (PnlDockOverlay *)container;
+
+ g_assert (PNL_IS_DOCK_OVERLAY (self));
+ g_assert (GTK_IS_WIDGET (widget));
+
+ GTK_CONTAINER_CLASS (pnl_dock_overlay_parent_class)->add (container, widget);
+
+ pnl_dock_overlay_update_focus_chain (self);
+
+ if (PNL_IS_DOCK_ITEM (widget))
+ {
+ pnl_dock_item_adopt (PNL_DOCK_ITEM (self), PNL_DOCK_ITEM (widget));
+ pnl_dock_item_update_visibility (PNL_DOCK_ITEM (widget));
+ }
+}
+
+static void
+pnl_dock_overlay_toplevel_mnemonics (PnlDockOverlay *self,
+ GParamSpec *pspec,
+ GtkWindow *toplevel)
+{
+ PnlDockOverlayPrivate *priv = pnl_dock_overlay_get_instance_private (self);
+ const gchar *style_prop;
+ gboolean mnemonics_visible;
+ guint i;
+
+ g_assert (PNL_IS_DOCK_OVERLAY (self));
+ g_assert (pspec != NULL);
+ g_assert (GTK_IS_WINDOW (toplevel));
+
+ mnemonics_visible = gtk_window_get_mnemonics_visible (toplevel);
+ style_prop = mnemonics_visible ? "mnemonic-overlap-size" : "overlap-size";
+
+ for (i = 0; i < G_N_ELEMENTS (priv->edges); i++)
+ {
+ PnlDockOverlayEdge *edge = priv->edges [i];
+ GtkAdjustment *handle_adj = priv->edge_handle_adj [i];
+ gint overlap = 0;
+
+ gtk_widget_style_get (GTK_WIDGET (edge), style_prop, &overlap, NULL);
+
+ pnl_object_animate (handle_adj,
+ PNL_ANIMATION_EASE_IN_OUT_CUBIC,
+ MNEMONIC_REVEAL_DURATION,
+ gtk_widget_get_frame_clock (GTK_WIDGET (edge)),
+ "value", (gdouble)overlap,
+ NULL);
+ }
+
+ gtk_widget_queue_allocate (GTK_WIDGET (self));
+}
+
+static void
+pnl_dock_overlay_toplevel_set_focus (PnlDockOverlay *self,
+ GtkWidget *widget,
+ GtkWindow *toplevel)
+{
+ PnlDockOverlayPrivate *priv = pnl_dock_overlay_get_instance_private (self);
+ guint i;
+
+ g_assert (PNL_IS_DOCK_OVERLAY (self));
+ g_assert (!widget || GTK_IS_WIDGET (widget));
+ g_assert (GTK_IS_WINDOW (toplevel));
+
+ /*
+ * TODO: If the overlay obscurs the new focus widget,
+ * hide immediately. Otherwise, use a short timeout.
+ */
+
+ for (i = 0; i < G_N_ELEMENTS (priv->edges); i++)
+ {
+ PnlDockOverlayEdge *edge = priv->edges [i];
+
+ if (!widget || !gtk_widget_is_ancestor (widget, GTK_WIDGET (edge)))
+ gtk_container_child_set (GTK_CONTAINER (self), GTK_WIDGET (edge),
+ "reveal", FALSE,
+ NULL);
+ }
+}
+
+static void
+pnl_dock_overlay_hide_edges (PnlDockOverlay *self)
+{
+ PnlDockOverlayPrivate *priv = pnl_dock_overlay_get_instance_private (self);
+ GtkWidget *child;
+ guint i;
+
+ g_assert (PNL_IS_DOCK_OVERLAY (self));
+
+ for (i = 0; i < G_N_ELEMENTS (priv->edges); i++)
+ {
+ PnlDockOverlayEdge *edge = priv->edges [i];
+
+ gtk_container_child_set (GTK_CONTAINER (self), GTK_WIDGET (edge),
+ "reveal", FALSE,
+ NULL);
+ }
+
+ child = gtk_bin_get_child (GTK_BIN (self));
+
+ if (child != NULL)
+ gtk_widget_grab_focus (child);
+}
+
+static void
+pnl_dock_overlay_destroy (GtkWidget *widget)
+{
+ PnlDockOverlay *self = (PnlDockOverlay *)widget;
+ PnlDockOverlayPrivate *priv = pnl_dock_overlay_get_instance_private (self);
+ guint i;
+
+ g_assert (GTK_IS_WIDGET (widget));
+
+ for (i = 0; i < G_N_ELEMENTS (priv->edge_adj); i++)
+ g_clear_object (&priv->edge_adj [i]);
+
+ GTK_WIDGET_CLASS (pnl_dock_overlay_parent_class)->destroy (widget);
+}
+
+static void
+pnl_dock_overlay_hierarchy_changed (GtkWidget *widget,
+ GtkWidget *old_toplevel)
+{
+ PnlDockOverlay *self = (PnlDockOverlay *)widget;
+ GtkWidget *toplevel;
+
+ g_assert (PNL_IS_DOCK_OVERLAY (self));
+ g_assert (!old_toplevel || GTK_IS_WIDGET (old_toplevel));
+
+ if (old_toplevel != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (old_toplevel,
+ G_CALLBACK (pnl_dock_overlay_toplevel_mnemonics),
+ self);
+ g_signal_handlers_disconnect_by_func (old_toplevel,
+ G_CALLBACK (pnl_dock_overlay_toplevel_set_focus),
+ self);
+ }
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));
+
+ if (GTK_IS_WINDOW (toplevel))
+ {
+ g_signal_connect_object (toplevel,
+ "notify::mnemonics-visible",
+ G_CALLBACK (pnl_dock_overlay_toplevel_mnemonics),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (toplevel,
+ "set-focus",
+ G_CALLBACK (pnl_dock_overlay_toplevel_set_focus),
+ self,
+ G_CONNECT_SWAPPED);
+ }
+}
+
+static gboolean
+pnl_dock_overlay_get_child_reveal (PnlDockOverlay *self,
+ GtkWidget *child)
+{
+ PnlDockOverlayPrivate *priv = pnl_dock_overlay_get_instance_private (self);
+
+ g_assert (PNL_IS_DOCK_OVERLAY (self));
+ g_assert (GTK_IS_WIDGET (child));
+
+ if (PNL_IS_DOCK_OVERLAY_EDGE (child))
+ {
+ GtkPositionType edge;
+
+ edge = pnl_dock_overlay_edge_get_edge (PNL_DOCK_OVERLAY_EDGE (child));
+
+ return !!(priv->child_reveal & (1 << edge));
+ }
+
+ return FALSE;
+}
+
+static void
+pnl_dock_overlay_set_child_reveal (PnlDockOverlay *self,
+ GtkWidget *child,
+ gboolean reveal)
+{
+ PnlDockOverlayPrivate *priv = pnl_dock_overlay_get_instance_private (self);
+ GtkPositionType edge;
+ guint child_reveal;
+
+ g_assert (PNL_IS_DOCK_OVERLAY (self));
+ g_assert (GTK_IS_WIDGET (child));
+
+ if (!PNL_IS_DOCK_OVERLAY_EDGE (child))
+ return;
+
+ edge = pnl_dock_overlay_edge_get_edge (PNL_DOCK_OVERLAY_EDGE (child));
+
+ if (reveal)
+ child_reveal = priv->child_reveal | (1 << edge);
+ else
+ child_reveal = priv->child_reveal & ~(1 << edge);
+
+ if (priv->child_reveal != child_reveal)
+ {
+ priv->child_reveal = child_reveal;
+
+ pnl_object_animate (priv->edge_adj [edge],
+ PNL_ANIMATION_EASE_IN_OUT_CUBIC,
+ REVEAL_DURATION,
+ gtk_widget_get_frame_clock (child),
+ "value", reveal ? 0.0 : 1.0,
+ NULL);
+
+ gtk_container_child_notify_by_pspec (GTK_CONTAINER (self),
+ child,
+ child_properties [CHILD_PROP_REVEAL]);
+ }
+
+}
+
+static void
+pnl_dock_overlay_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PnlDockOverlay *self = PNL_DOCK_OVERLAY (object);
+
+ switch (prop_id)
+ {
+ case PROP_MANAGER:
+ g_value_set_object (value, pnl_dock_item_get_manager (PNL_DOCK_ITEM (self)));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pnl_dock_overlay_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PnlDockOverlay *self = PNL_DOCK_OVERLAY (object);
+
+ switch (prop_id)
+ {
+ case PROP_MANAGER:
+ pnl_dock_item_set_manager (PNL_DOCK_ITEM (self), g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pnl_dock_overlay_get_child_property (GtkContainer *container,
+ GtkWidget *widget,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PnlDockOverlay *self = PNL_DOCK_OVERLAY (container);
+
+ switch (prop_id)
+ {
+ case CHILD_PROP_REVEAL:
+ g_value_set_boolean (value, pnl_dock_overlay_get_child_reveal (self, widget));
+ break;
+
+ default:
+ GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec);
+ }
+}
+
+static void
+pnl_dock_overlay_set_child_property (GtkContainer *container,
+ GtkWidget *widget,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PnlDockOverlay *self = PNL_DOCK_OVERLAY (container);
+
+ switch (prop_id)
+ {
+ case CHILD_PROP_REVEAL:
+ pnl_dock_overlay_set_child_reveal (self, widget, g_value_get_boolean (value));
+ break;
+
+ default:
+ GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec);
+ }
+}
+
+static void
+pnl_dock_overlay_class_init (PnlDockOverlayClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+ GtkBindingSet *binding_set;
+
+ object_class->get_property = pnl_dock_overlay_get_property;
+ object_class->set_property = pnl_dock_overlay_set_property;
+
+ widget_class->destroy = pnl_dock_overlay_destroy;
+ widget_class->hierarchy_changed = pnl_dock_overlay_hierarchy_changed;
+
+ container_class->add = pnl_dock_overlay_add;
+ container_class->get_child_property = pnl_dock_overlay_get_child_property;
+ container_class->set_child_property = pnl_dock_overlay_set_child_property;
+
+ klass->hide_edges = pnl_dock_overlay_hide_edges;
+
+ g_object_class_override_property (object_class, PROP_MANAGER, "manager");
+
+ child_properties [CHILD_PROP_REVEAL] =
+ g_param_spec_boolean ("reveal",
+ "Reveal",
+ "If the panel edge should be revealed",
+ FALSE,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gtk_container_class_install_child_properties (container_class, N_CHILD_PROPS, child_properties);
+
+ gtk_widget_class_set_css_name (widget_class, "dockoverlay");
+
+ signals [HIDE_EDGES] =
+ g_signal_new ("hide-edges",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (PnlDockOverlayClass, hide_edges),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ binding_set = gtk_binding_set_by_class (klass);
+
+ gtk_binding_entry_add_signal (binding_set,
+ GDK_KEY_Escape,
+ 0,
+ "hide-edges",
+ 0);
+}
+
+static void
+pnl_dock_overlay_init (PnlDockOverlay *self)
+{
+ PnlDockOverlayPrivate *priv = pnl_dock_overlay_get_instance_private (self);
+ guint i;
+
+ priv->overlay = g_object_new (GTK_TYPE_OVERLAY,
+ "visible", TRUE,
+ NULL);
+
+ GTK_CONTAINER_CLASS (pnl_dock_overlay_parent_class)->add (GTK_CONTAINER (self),
+ GTK_WIDGET (priv->overlay));
+
+ g_signal_connect_object (priv->overlay,
+ "get-child-position",
+ G_CALLBACK (pnl_dock_overlay_get_child_position),
+ self,
+ G_CONNECT_SWAPPED);
+
+ for (i = 0; i <= GTK_POS_BOTTOM; i++)
+ {
+ PnlDockOverlayEdge *edge;
+
+ edge = g_object_new (PNL_TYPE_DOCK_OVERLAY_EDGE,
+ "edge", (GtkPositionType)i,
+ "visible", TRUE,
+ NULL);
+
+ pnl_set_weak_pointer (&priv->edges[i], edge);
+
+ gtk_overlay_add_overlay (priv->overlay, GTK_WIDGET (priv->edges [i]));
+
+ priv->edge_adj [i] = gtk_adjustment_new (1, 0, 1, 0, 0, 0);
+
+ g_signal_connect_swapped (priv->edge_adj [i],
+ "value-changed",
+ G_CALLBACK (gtk_widget_queue_allocate),
+ priv->overlay);
+
+ priv->edge_handle_adj [i] = gtk_adjustment_new (0, 0, 1000, 0, 0, 0);
+
+ g_signal_connect_swapped (priv->edge_handle_adj [i],
+ "value-changed",
+ G_CALLBACK (gtk_widget_queue_allocate),
+ priv->overlay);
+ }
+}
+
+static void
+pnl_dock_overlay_init_dock_iface (PnlDockInterface *iface)
+{
+}
+
+GtkWidget *
+pnl_dock_overlay_new (void)
+{
+ return g_object_new (PNL_TYPE_DOCK_OVERLAY, NULL);
+}
+
+static void
+pnl_dock_overlay_add_child (GtkBuildable *buildable,
+ GtkBuilder *builder,
+ GObject *child,
+ const gchar *type)
+{
+ PnlDockOverlay *self = (PnlDockOverlay *)buildable;
+ PnlDockOverlayPrivate *priv = pnl_dock_overlay_get_instance_private (self);
+ PnlDockOverlayEdge *parent = NULL;
+
+ g_assert (PNL_IS_DOCK_OVERLAY (self));
+ g_assert (GTK_IS_BUILDER (builder));
+ g_assert (G_IS_OBJECT (child));
+
+ if (!GTK_IS_WIDGET (child))
+ {
+ g_warning ("Attempt to add a child of type \"%s\" to a \"%s\"",
+ G_OBJECT_TYPE_NAME (child), G_OBJECT_TYPE_NAME (self));
+ return;
+ }
+
+ if ((type == NULL) || (g_strcmp0 ("center", type) == 0))
+ {
+ gtk_container_add (GTK_CONTAINER (priv->overlay), GTK_WIDGET (child));
+ goto adopt;
+ }
+
+ if (g_strcmp0 ("top", type) == 0)
+ parent = priv->edges [GTK_POS_TOP];
+ else if (g_strcmp0 ("bottom", type) == 0)
+ parent = priv->edges [GTK_POS_BOTTOM];
+ else if (g_strcmp0 ("right", type) == 0)
+ parent = priv->edges [GTK_POS_RIGHT];
+ else
+ parent = priv->edges [GTK_POS_LEFT];
+
+ gtk_container_add (GTK_CONTAINER (parent), GTK_WIDGET (child));
+
+adopt:
+ if (PNL_IS_DOCK_ITEM (child))
+ pnl_dock_item_adopt (PNL_DOCK_ITEM (self), PNL_DOCK_ITEM (child));
+}
+
+static void
+pnl_dock_overlay_init_buildable_iface (GtkBuildableIface *iface)
+{
+ iface->add_child = pnl_dock_overlay_add_child;
+}
+
+static void
+pnl_dock_overlay_present_child (PnlDockItem *item,
+ PnlDockItem *child)
+{
+ PnlDockOverlay *self = (PnlDockOverlay *)item;
+
+ g_assert (PNL_IS_DOCK_OVERLAY (self));
+ g_assert (PNL_IS_DOCK_ITEM (child));
+
+ gtk_container_child_set (GTK_CONTAINER (self), GTK_WIDGET (child),
+ "reveal", TRUE,
+ NULL);
+}
+
+static void
+pnl_dock_overlay_update_visibility (PnlDockItem *item)
+{
+ PnlDockOverlay *self = (PnlDockOverlay *)item;
+ PnlDockOverlayPrivate *priv = pnl_dock_overlay_get_instance_private (self);
+ guint i;
+
+ g_assert (PNL_IS_DOCK_OVERLAY (self));
+
+ for (i = 0; i < G_N_ELEMENTS (priv->edges); i++)
+ {
+ PnlDockOverlayEdge *edge = priv->edges [i];
+ gboolean has_widgets;
+
+ if (edge == NULL)
+ continue;
+
+ has_widgets = pnl_dock_item_has_widgets (PNL_DOCK_ITEM (edge));
+
+ gtk_widget_set_child_visible (GTK_WIDGET (edge), has_widgets);
+ }
+
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+}
+
+static void
+pnl_dock_overlay_init_dock_item_iface (PnlDockItemInterface *iface)
+{
+ iface->present_child = pnl_dock_overlay_present_child;
+ iface->update_visibility = pnl_dock_overlay_update_visibility;
+}
diff --git a/contrib/pnl/pnl-dock-overlay.h b/contrib/pnl/pnl-dock-overlay.h
new file mode 100644
index 0000000..5038456
--- /dev/null
+++ b/contrib/pnl/pnl-dock-overlay.h
@@ -0,0 +1,50 @@
+/* pnl-dock-overlay.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_DOCK_OVERLAY_H
+#define PNL_DOCK_OVERLAY_H
+
+#include "pnl-dock.h"
+
+G_BEGIN_DECLS
+
+struct _PnlDockOverlayClass
+{
+ GtkEventBoxClass parent;
+
+ void (*hide_edges) (PnlDockOverlay *self);
+
+ void (*padding1) (void);
+ void (*padding2) (void);
+ void (*padding3) (void);
+ void (*padding4) (void);
+ void (*padding5) (void);
+ void (*padding6) (void);
+ void (*padding7) (void);
+ void (*padding8) (void);
+};
+
+GtkWidget *pnl_dock_overlay_new (void);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_OVERLAY_H */
diff --git a/contrib/pnl/pnl-dock-paned-private.h b/contrib/pnl/pnl-dock-paned-private.h
new file mode 100644
index 0000000..a120a0a
--- /dev/null
+++ b/contrib/pnl/pnl-dock-paned-private.h
@@ -0,0 +1,31 @@
+/* pnl-dock-paned-private.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PNL_DOCK_PANED_PRIVATE_H
+#define PNL_DOCK_PANED_PRIVATE_H
+
+#include "pnl-dock-paned.h"
+
+G_BEGIN_DECLS
+
+void pnl_dock_paned_set_child_edge (PnlDockPaned *self,
+ GtkPositionType child_edge);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_PANED_PRIVATE_H */
diff --git a/contrib/pnl/pnl-dock-paned.c b/contrib/pnl/pnl-dock-paned.c
new file mode 100644
index 0000000..36cd6ba
--- /dev/null
+++ b/contrib/pnl/pnl-dock-paned.c
@@ -0,0 +1,134 @@
+/* pnl-dock-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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-dock-item.h"
+#include "pnl-dock-paned.h"
+#include "pnl-dock-paned-private.h"
+#include "pnl-dock-stack.h"
+
+typedef struct
+{
+ GtkPositionType child_edge : 2;
+} PnlDockPanedPrivate;
+
+G_DEFINE_TYPE_EXTENDED (PnlDockPaned, pnl_dock_paned, PNL_TYPE_MULTI_PANED, 0,
+ G_ADD_PRIVATE (PnlDockPaned)
+ G_IMPLEMENT_INTERFACE (PNL_TYPE_DOCK_ITEM, NULL))
+
+static void
+pnl_dock_paned_add (GtkContainer *container,
+ GtkWidget *widget)
+{
+ PnlDockPaned *self = (PnlDockPaned *)container;
+ PnlDockPanedPrivate *priv = pnl_dock_paned_get_instance_private (self);
+
+ g_assert (PNL_IS_DOCK_PANED (self));
+
+ if (PNL_IS_DOCK_STACK (widget))
+ pnl_dock_stack_set_edge (PNL_DOCK_STACK (widget), priv->child_edge);
+
+ GTK_CONTAINER_CLASS (pnl_dock_paned_parent_class)->add (container, widget);
+
+ if (PNL_IS_DOCK_ITEM (widget))
+ pnl_dock_item_adopt (PNL_DOCK_ITEM (self), PNL_DOCK_ITEM (widget));
+}
+
+static void
+pnl_dock_paned_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (prop_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pnl_dock_paned_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (prop_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pnl_dock_paned_class_init (PnlDockPanedClass *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 = pnl_dock_paned_get_property;
+ object_class->set_property = pnl_dock_paned_set_property;
+
+ container_class->add = pnl_dock_paned_add;
+
+ gtk_widget_class_set_css_name (widget_class, "dockpaned");
+}
+
+static void
+pnl_dock_paned_init (PnlDockPaned *self)
+{
+ PnlDockPanedPrivate *priv = pnl_dock_paned_get_instance_private (self);
+
+ priv->child_edge = GTK_POS_TOP;
+}
+
+GtkWidget *
+pnl_dock_paned_new (void)
+{
+ return g_object_new (PNL_TYPE_DOCK_PANED, NULL);
+}
+
+static void
+pnl_dock_paned_update_child_edge (GtkWidget *widget,
+ gpointer user_data)
+{
+ GtkPositionType child_edge = GPOINTER_TO_INT (user_data);
+
+ g_assert (GTK_IS_WIDGET (widget));
+
+ if (PNL_IS_DOCK_STACK (widget))
+ pnl_dock_stack_set_edge (PNL_DOCK_STACK (widget), child_edge);
+}
+
+void
+pnl_dock_paned_set_child_edge (PnlDockPaned *self,
+ GtkPositionType child_edge)
+{
+ PnlDockPanedPrivate *priv = pnl_dock_paned_get_instance_private (self);
+
+ g_return_if_fail (PNL_IS_DOCK_PANED (self));
+
+ if (priv->child_edge != child_edge)
+ {
+ priv->child_edge = child_edge;
+
+ gtk_container_foreach (GTK_CONTAINER (self),
+ pnl_dock_paned_update_child_edge,
+ GINT_TO_POINTER (child_edge));
+ }
+}
diff --git a/contrib/pnl/pnl-dock-paned.h b/contrib/pnl/pnl-dock-paned.h
new file mode 100644
index 0000000..4889e00
--- /dev/null
+++ b/contrib/pnl/pnl-dock-paned.h
@@ -0,0 +1,48 @@
+/* pnl-dock-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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_DOCK_PANED_H
+#define PNL_DOCK_PANED_H
+
+#include "pnl-dock-types.h"
+
+G_BEGIN_DECLS
+
+struct _PnlDockPanedClass
+{
+ PnlMultiPanedClass parent;
+
+ void (*padding1) (void);
+ void (*padding2) (void);
+ void (*padding3) (void);
+ void (*padding4) (void);
+ void (*padding5) (void);
+ void (*padding6) (void);
+ void (*padding7) (void);
+ void (*padding8) (void);
+};
+
+GtkWidget *pnl_dock_paned_new (void);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_PANED_H */
diff --git a/contrib/pnl/pnl-dock-revealer.c b/contrib/pnl/pnl-dock-revealer.c
new file mode 100644
index 0000000..9200592
--- /dev/null
+++ b/contrib/pnl/pnl-dock-revealer.c
@@ -0,0 +1,779 @@
+/* pnl-dock-revealer.c
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-animation.h"
+#include "pnl-dock-revealer.h"
+#include "pnl-util-private.h"
+
+/**
+ * SECTION:pnl-dock-revealer
+ * @title: PnlDockRevealer
+ * @short_description: A panel revealer
+ *
+ * This widget is a bit like #GtkRevealer with a couple of important
+ * differences. First, it only supports a couple transition types
+ * (the direction to slide reveal). Additionally, the size of the
+ * child allocation will not change during the animation. This is not
+ * as generally useful as an upstream GTK+ widget, but is extremely
+ * important for the panel case to avoid things looking strange while
+ * animating into and out of view.
+ */
+
+#define IS_HORIZONTAL(type) \
+ (((type) == PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_RIGHT) || \
+ ((type) == PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_LEFT))
+
+#define IS_VERTICAL(type) \
+ (((type) == PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_UP) || \
+ ((type) == PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN))
+
+typedef struct
+{
+ PnlAnimation *animation;
+ GtkAdjustment *adjustment;
+ GdkWindow *window;
+ gint position;
+ guint transition_duration;
+ PnlDockRevealerTransitionType transition_type : 3;
+ guint position_set : 1;
+ guint reveal_child : 1;
+ guint child_revealed : 1;
+} PnlDockRevealerPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (PnlDockRevealer, pnl_dock_revealer, GTK_TYPE_BIN)
+
+enum {
+ PROP_0,
+ PROP_CHILD_REVEALED,
+ PROP_POSITION,
+ PROP_POSITION_SET,
+ PROP_REVEAL_CHILD,
+ PROP_TRANSITION_DURATION,
+ PROP_TRANSITION_TYPE,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+GtkWidget *
+pnl_dock_revealer_new (void)
+{
+ return g_object_new (PNL_TYPE_DOCK_REVEALER, NULL);
+}
+
+guint
+pnl_dock_revealer_get_transition_duration (PnlDockRevealer *self)
+{
+ PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+ g_return_val_if_fail (PNL_IS_DOCK_REVEALER (self), 0);
+
+ return priv->transition_duration;
+}
+
+void
+pnl_dock_revealer_set_transition_duration (PnlDockRevealer *self,
+ guint transition_duration)
+{
+ PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+ g_return_if_fail (PNL_IS_DOCK_REVEALER (self));
+
+ if (priv->transition_duration != transition_duration)
+ {
+ priv->transition_duration = transition_duration;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TRANSITION_DURATION]);
+ }
+}
+
+PnlDockRevealerTransitionType
+pnl_dock_revealer_get_transition_type (PnlDockRevealer *self)
+{
+ PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+ g_return_val_if_fail (PNL_IS_DOCK_REVEALER (self), 0);
+
+ return priv->transition_type;
+}
+
+void
+pnl_dock_revealer_set_transition_type (PnlDockRevealer *self,
+ PnlDockRevealerTransitionType transition_type)
+{
+ PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+ g_return_if_fail (PNL_IS_DOCK_REVEALER (self));
+ g_return_if_fail (transition_type >= PNL_DOCK_REVEALER_TRANSITION_TYPE_NONE);
+ g_return_if_fail (transition_type <= PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN);
+
+ if (priv->transition_type != transition_type)
+ {
+ priv->transition_type = transition_type;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TRANSITION_TYPE]);
+ }
+}
+
+gboolean
+pnl_dock_revealer_get_child_revealed (PnlDockRevealer *self)
+{
+ PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+ g_return_val_if_fail (PNL_IS_DOCK_REVEALER (self), FALSE);
+
+ return priv->child_revealed;
+}
+
+gboolean
+pnl_dock_revealer_get_reveal_child (PnlDockRevealer *self)
+{
+ PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+ g_return_val_if_fail (PNL_IS_DOCK_REVEALER (self), FALSE);
+
+ return priv->reveal_child;
+}
+
+static void
+pnl_dock_revealer_animation_done (gpointer user_data)
+{
+ g_autoptr(PnlDockRevealer) self = user_data;
+ PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+ g_assert (PNL_DOCK_REVEALER (self));
+
+ if (priv->adjustment != NULL)
+ {
+ gboolean child_revealed;
+
+ child_revealed = (gtk_adjustment_get_value (priv->adjustment) == 1.0);
+
+ if (priv->child_revealed != child_revealed)
+ {
+ GtkWidget *child = gtk_bin_get_child (GTK_BIN (self));
+
+ priv->child_revealed = child_revealed;
+ gtk_widget_set_child_visible (GTK_WIDGET (child),
+ gtk_adjustment_get_value (priv->adjustment) != 0.0);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CHILD_REVEALED]);
+ }
+
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+ }
+}
+
+static guint
+size_to_duration (gint size)
+{
+ return MAX (150, size * 1.2);
+}
+
+static guint
+pnl_dock_revealer_calculate_duration (PnlDockRevealer *self)
+{
+ PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+ GtkWidget *child;
+ GtkRequisition min_size;
+ GtkRequisition nat_size;
+
+ g_assert (PNL_IS_DOCK_REVEALER (self));
+
+ child = gtk_bin_get_child (GTK_BIN (self));
+
+ if (child == NULL)
+ return 0;
+
+ if (priv->transition_type == PNL_DOCK_REVEALER_TRANSITION_TYPE_NONE)
+ return 0;
+
+ if (priv->transition_duration != 0)
+ return priv->transition_duration;
+
+ gtk_widget_get_preferred_size (child, &min_size, &nat_size);
+
+ if (IS_HORIZONTAL (priv->transition_type))
+ {
+ if (priv->position_set)
+ {
+ if (priv->position_set && priv->position > min_size.width)
+ return size_to_duration (priv->position);
+ return size_to_duration (min_size.width);
+ }
+
+ return size_to_duration (nat_size.width);
+ }
+ else
+ {
+ if (priv->position_set)
+ {
+ if (priv->position_set && priv->position > min_size.height)
+ return size_to_duration (priv->position);
+ return size_to_duration (min_size.height);
+ }
+
+ return size_to_duration (nat_size.height);
+ }
+}
+
+void
+pnl_dock_revealer_set_reveal_child (PnlDockRevealer *self,
+ gboolean reveal_child)
+{
+ PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+ g_return_if_fail (PNL_IS_DOCK_REVEALER (self));
+
+ reveal_child = !!reveal_child;
+
+ if (reveal_child != priv->reveal_child)
+ {
+ PnlAnimation *animation;
+ GtkWidget *child;
+
+ priv->reveal_child = reveal_child;
+
+ child = gtk_bin_get_child (GTK_BIN (self));
+
+ if (child != NULL)
+ {
+ guint duration;
+
+ if (priv->animation != NULL)
+ {
+ pnl_animation_stop (priv->animation);
+ pnl_clear_weak_pointer (&priv->animation);
+ }
+
+ gtk_widget_set_child_visible (child, TRUE);
+
+ duration = pnl_dock_revealer_calculate_duration (self);
+
+ animation = pnl_object_animate_full (priv->adjustment,
+ PNL_ANIMATION_EASE_IN_OUT_CUBIC,
+ duration,
+ gtk_widget_get_frame_clock (GTK_WIDGET (self)),
+ pnl_dock_revealer_animation_done,
+ g_object_ref (self),
+ "value", reveal_child ? 1.0 : 0.0,
+ NULL);
+
+ pnl_set_weak_pointer (&priv->animation, animation);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_REVEAL_CHILD]);
+ }
+}
+
+gint
+pnl_dock_revealer_get_position (PnlDockRevealer *self)
+{
+ PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+ g_return_val_if_fail (PNL_IS_DOCK_REVEALER (self), 0);
+
+ return priv->position;
+}
+
+void
+pnl_dock_revealer_set_position (PnlDockRevealer *self,
+ gint position)
+{
+ PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+ g_return_if_fail (PNL_IS_DOCK_REVEALER (self));
+ g_return_if_fail (position >= 0);
+
+ if (priv->position != position)
+ {
+ priv->position = position;
+
+ if (!priv->position_set)
+ {
+ priv->position_set = TRUE;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_POSITION_SET]);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_POSITION]);
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+ }
+}
+
+gboolean
+pnl_dock_revealer_get_position_set (PnlDockRevealer *self)
+{
+ PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+ g_return_val_if_fail (PNL_IS_DOCK_REVEALER (self), FALSE);
+
+ return priv->position_set;
+}
+
+void
+pnl_dock_revealer_set_position_set (PnlDockRevealer *self,
+ gboolean position_set)
+{
+ PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+ g_return_if_fail (PNL_IS_DOCK_REVEALER (self));
+
+ position_set = !!position_set;
+
+ if (priv->position_set != position_set)
+ {
+ priv->position_set = position_set;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_POSITION_SET]);
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+ }
+}
+
+static void
+pnl_dock_revealer_get_child_preferred_width (PnlDockRevealer *self,
+ gint *min_width,
+ gint *nat_width)
+{
+ PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+ GtkWidget *child;
+
+ g_assert (PNL_IS_DOCK_REVEALER (self));
+ g_assert (min_width != NULL);
+ g_assert (nat_width != NULL);
+
+ *min_width = 0;
+ *nat_width = 0;
+
+ if (NULL == (child = gtk_bin_get_child (GTK_BIN (self))))
+ return;
+
+ if (!gtk_widget_get_child_visible (child) || !gtk_widget_get_visible (child))
+ return;
+
+ gtk_widget_get_preferred_width (child, min_width, nat_width);
+
+ if (IS_HORIZONTAL (priv->transition_type) && priv->position_set)
+ {
+ if (priv->position > *min_width)
+ *nat_width = priv->position;
+ else
+ *nat_width = *min_width;
+ }
+}
+
+static void
+pnl_dock_revealer_get_preferred_width (GtkWidget *widget,
+ gint *min_width,
+ gint *nat_width)
+{
+ PnlDockRevealer *self = (PnlDockRevealer *)widget;
+ PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+ g_assert (PNL_IS_DOCK_REVEALER (self));
+ g_assert (min_width != NULL);
+ g_assert (nat_width != NULL);
+
+ pnl_dock_revealer_get_child_preferred_width (self, min_width, nat_width);
+
+ if (IS_HORIZONTAL (priv->transition_type) && priv->animation != NULL)
+ {
+ /*
+ * We allow going smaller than the minimum size during animations
+ * and rely on clipping to hide the child.
+ */
+ *min_width = 0;
+
+ /*
+ * Our natural width is adjusted for the in-progress animation.
+ */
+ *nat_width *= gtk_adjustment_get_value (priv->adjustment);
+ }
+}
+
+static void
+pnl_dock_revealer_get_child_preferred_height (PnlDockRevealer *self,
+ gint *min_height,
+ gint *nat_height)
+{
+ PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+ GtkWidget *child;
+
+ g_assert (PNL_IS_DOCK_REVEALER (self));
+ g_assert (min_height != NULL);
+ g_assert (nat_height != NULL);
+
+ *min_height = 0;
+ *nat_height = 0;
+
+ if (NULL == (child = gtk_bin_get_child (GTK_BIN (self))))
+ return;
+
+ if (!gtk_widget_get_child_visible (child) || !gtk_widget_get_visible (child))
+ return;
+
+ gtk_widget_get_preferred_height (child, min_height, nat_height);
+
+ if (IS_VERTICAL (priv->transition_type) && priv->position_set)
+ {
+ if (priv->position > *min_height)
+ *nat_height = priv->position;
+ else
+ *nat_height = *min_height;
+ }
+}
+
+static void
+pnl_dock_revealer_get_preferred_height (GtkWidget *widget,
+ gint *min_height,
+ gint *nat_height)
+{
+ PnlDockRevealer *self = (PnlDockRevealer *)widget;
+ PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+ g_assert (PNL_IS_DOCK_REVEALER (self));
+ g_assert (min_height != NULL);
+ g_assert (nat_height != NULL);
+
+ pnl_dock_revealer_get_child_preferred_height (self, min_height, nat_height);
+
+ if (IS_VERTICAL (priv->transition_type) && priv->animation != NULL)
+ {
+ /*
+ * We allow going smaller than the minimum size during animations
+ * and rely on clipping to hide the child.
+ */
+ *min_height = 0;
+
+ /*
+ * Our natural height is adjusted for the in-progress animation.
+ */
+ *nat_height *= gtk_adjustment_get_value (priv->adjustment);
+ }
+}
+
+static void
+pnl_dock_revealer_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ PnlDockRevealer *self = (PnlDockRevealer *)widget;
+ PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+ GtkAllocation child_allocation;
+ GtkRequisition min_req;
+ GtkRequisition nat_req;
+ GtkWidget *child;
+
+ g_assert (PNL_IS_DOCK_REVEALER (self));
+
+ gtk_widget_set_allocation (widget, allocation);
+
+ if (gtk_widget_get_realized (GTK_WIDGET (self)))
+ gdk_window_move_resize (priv->window,
+ allocation->x,
+ allocation->y,
+ allocation->width,
+ allocation->height);
+
+ if (NULL == (child = gtk_bin_get_child (GTK_BIN (self))))
+ return;
+
+ if (!gtk_widget_get_child_visible (child))
+ return;
+
+ child_allocation.x = 0;
+ child_allocation.y = 0;
+ child_allocation.width = allocation->width;
+ child_allocation.height = allocation->height;
+
+ if (IS_HORIZONTAL (priv->transition_type))
+ {
+ pnl_dock_revealer_get_child_preferred_width (self, &min_req.width, &nat_req.width);
+ child_allocation.width = nat_req.width;
+
+ if (priv->transition_type == PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_RIGHT)
+ child_allocation.x = allocation->width - child_allocation.width;
+ }
+ else if (IS_VERTICAL (priv->transition_type))
+ {
+ pnl_dock_revealer_get_child_preferred_height (self, &min_req.height, &nat_req.height);
+ child_allocation.height = nat_req.height;
+
+ if (priv->transition_type == PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN)
+ child_allocation.y = allocation->height - child_allocation.height;
+ }
+
+ gtk_widget_size_allocate (child, &child_allocation);
+}
+
+static void
+pnl_dock_revealer_add (GtkContainer *container,
+ GtkWidget *widget)
+{
+ PnlDockRevealer *self = (PnlDockRevealer *)container;
+ PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+ g_assert (PNL_IS_DOCK_REVEALER (self));
+ g_assert (GTK_IS_WIDGET (widget));
+
+ GTK_CONTAINER_CLASS (pnl_dock_revealer_parent_class)->add (container, widget);
+
+ gtk_widget_set_child_visible (widget, priv->reveal_child);
+}
+
+static void
+pnl_dock_revealer_realize (GtkWidget *widget)
+{
+ PnlDockRevealer *self = (PnlDockRevealer *)widget;
+ PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+ GdkWindowAttr attributes = { 0 };
+ GdkWindow *parent;
+ GtkAllocation alloc;
+ gint attributes_mask = 0;
+
+ g_assert (PNL_IS_DOCK_REVEALER (widget));
+
+ gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
+
+ gtk_widget_set_realized (GTK_WIDGET (self), TRUE);
+
+ parent = gtk_widget_get_parent_window (GTK_WIDGET (self));
+
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.visual = gtk_widget_get_visual (GTK_WIDGET (self));
+ attributes.x = alloc.x;
+ attributes.y = alloc.y;
+ attributes.width = alloc.width;
+ attributes.height = alloc.height;
+ attributes.event_mask = 0;
+
+ attributes_mask = (GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL);
+
+ priv->window = gdk_window_new (parent, &attributes, attributes_mask);
+ gtk_widget_set_window (GTK_WIDGET (self), priv->window);
+ gtk_widget_register_window (GTK_WIDGET (self), priv->window);
+}
+
+static void
+pnl_dock_revealer_destroy (GtkWidget *widget)
+{
+ PnlDockRevealer *self = (PnlDockRevealer *)widget;
+ PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+ g_clear_object (&priv->adjustment);
+ pnl_clear_weak_pointer (&priv->animation);
+
+ GTK_WIDGET_CLASS (pnl_dock_revealer_parent_class)->destroy (widget);
+}
+
+static void
+pnl_dock_revealer_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PnlDockRevealer *self = PNL_DOCK_REVEALER (object);
+
+ switch (prop_id)
+ {
+ case PROP_CHILD_REVEALED:
+ g_value_set_boolean (value, pnl_dock_revealer_get_child_revealed (self));
+ break;
+
+ case PROP_POSITION:
+ g_value_set_int (value, pnl_dock_revealer_get_position (self));
+ break;
+
+ case PROP_POSITION_SET:
+ g_value_set_boolean (value, pnl_dock_revealer_get_position_set (self));
+ break;
+
+ case PROP_REVEAL_CHILD:
+ g_value_set_boolean (value, pnl_dock_revealer_get_reveal_child (self));
+ break;
+
+ case PROP_TRANSITION_DURATION:
+ g_value_set_uint (value, pnl_dock_revealer_get_transition_duration (self));
+ break;
+
+ case PROP_TRANSITION_TYPE:
+ g_value_set_enum (value, pnl_dock_revealer_get_transition_type (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pnl_dock_revealer_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PnlDockRevealer *self = PNL_DOCK_REVEALER (object);
+
+ switch (prop_id)
+ {
+ case PROP_REVEAL_CHILD:
+ pnl_dock_revealer_set_reveal_child (self, g_value_get_boolean (value));
+ break;
+
+ case PROP_POSITION:
+ pnl_dock_revealer_set_position (self, g_value_get_int (value));
+ break;
+
+ case PROP_POSITION_SET:
+ pnl_dock_revealer_set_position_set (self, g_value_get_boolean (value));
+ break;
+
+ case PROP_TRANSITION_DURATION:
+ pnl_dock_revealer_set_transition_duration (self, g_value_get_uint (value));
+ break;
+
+ case PROP_TRANSITION_TYPE:
+ pnl_dock_revealer_set_transition_type (self, g_value_get_enum (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pnl_dock_revealer_class_init (PnlDockRevealerClass *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 = pnl_dock_revealer_get_property;
+ object_class->set_property = pnl_dock_revealer_set_property;
+
+ widget_class->destroy = pnl_dock_revealer_destroy;
+ widget_class->get_preferred_width = pnl_dock_revealer_get_preferred_width;
+ widget_class->get_preferred_height = pnl_dock_revealer_get_preferred_height;
+ widget_class->realize = pnl_dock_revealer_realize;
+ widget_class->size_allocate = pnl_dock_revealer_size_allocate;
+
+ container_class->add = pnl_dock_revealer_add;
+
+ properties [PROP_CHILD_REVEALED] =
+ g_param_spec_boolean ("child-revealed",
+ "Child Revealed",
+ "If the child is fully revealed",
+ TRUE,
+ (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_POSITION] =
+ g_param_spec_int ("position",
+ "Position",
+ "Position",
+ 0,
+ G_MAXINT,
+ 0,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_POSITION_SET] =
+ g_param_spec_boolean ("position-set",
+ "Position Set",
+ "If the position has been set",
+ FALSE,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_REVEAL_CHILD] =
+ g_param_spec_boolean ("reveal-child",
+ "Reveal Child",
+ "If the child should be revealed",
+ FALSE,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_TRANSITION_DURATION] =
+ g_param_spec_uint ("transition-duration",
+ "Transition Duration",
+ "Length of duration in milliseconds",
+ 0,
+ G_MAXUINT,
+ 0,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_TRANSITION_TYPE] =
+ g_param_spec_enum ("transition-type",
+ "Transition Type",
+ "Transition Type",
+ PNL_TYPE_DOCK_REVEALER_TRANSITION_TYPE,
+ PNL_DOCK_REVEALER_TRANSITION_TYPE_NONE,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+pnl_dock_revealer_init (PnlDockRevealer *self)
+{
+ PnlDockRevealerPrivate *priv = pnl_dock_revealer_get_instance_private (self);
+
+ gtk_widget_set_has_window (GTK_WIDGET (self), TRUE);
+
+ priv->reveal_child = FALSE;
+ priv->child_revealed = FALSE;
+
+ priv->transition_duration = 0;
+
+ priv->adjustment = g_object_new (GTK_TYPE_ADJUSTMENT,
+ "lower", 0.0,
+ "upper", 1.0,
+ "value", 0.0,
+ NULL);
+
+ g_signal_connect_object (priv->adjustment,
+ "value-changed",
+ G_CALLBACK (gtk_widget_queue_resize),
+ self,
+ G_CONNECT_SWAPPED);
+}
+
+GType
+pnl_dock_revealer_transition_type_get_type (void)
+{
+ static GType type_id;
+
+ if (g_once_init_enter (&type_id))
+ {
+ GType _type_id;
+ static const GEnumValue values[] = {
+ { PNL_DOCK_REVEALER_TRANSITION_TYPE_NONE,
+ "PNL_DOCK_REVEALER_TRANSITION_TYPE_NONE",
+ "none" },
+ { PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_RIGHT,
+ "PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_RIGHT",
+ "slide-right" },
+ { PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_LEFT,
+ "PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_LEFT",
+ "slide-left" },
+ { PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_UP,
+ "PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_UP",
+ "slide-up" },
+ { PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN,
+ "PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN",
+ "slide-down" },
+ { 0 }
+ };
+
+ _type_id = g_enum_register_static ("PnlDockRevealerTransitionType", values);
+
+ g_once_init_leave (&type_id, _type_id);
+ }
+
+ return type_id;
+}
diff --git a/contrib/pnl/pnl-dock-revealer.h b/contrib/pnl/pnl-dock-revealer.h
new file mode 100644
index 0000000..0347d35
--- /dev/null
+++ b/contrib/pnl/pnl-dock-revealer.h
@@ -0,0 +1,76 @@
+/* pnl-dock-revealer.h
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_DOCK_REVEALER_H
+#define PNL_DOCK_REVEALER_H
+
+#include "pnl-dock-types.h"
+
+G_BEGIN_DECLS
+
+#define PNL_TYPE_DOCK_REVEALER_TRANSITION_TYPE (pnl_dock_revealer_transition_type_get_type())
+
+typedef enum
+{
+ PNL_DOCK_REVEALER_TRANSITION_TYPE_NONE,
+ PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_RIGHT,
+ PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_LEFT,
+ PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_UP,
+ PNL_DOCK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN,
+} PnlDockRevealerTransitionType;
+
+struct _PnlDockRevealerClass
+{
+ GtkBinClass parent;
+
+ void (*padding1) (void);
+ void (*padding2) (void);
+ void (*padding3) (void);
+ void (*padding4) (void);
+ void (*padding5) (void);
+ void (*padding6) (void);
+ void (*padding7) (void);
+ void (*padding8) (void);
+};
+
+GType pnl_dock_revealer_transition_type_get_type (void);
+GtkWidget *pnl_dock_revealer_new (void);
+PnlDockRevealerTransitionType pnl_dock_revealer_get_transition_type (PnlDockRevealer *self);
+void pnl_dock_revealer_set_transition_type (PnlDockRevealer
*self,
+ PnlDockRevealerTransitionType
transition_type);
+gboolean pnl_dock_revealer_get_child_revealed (PnlDockRevealer
*self);
+void pnl_dock_revealer_set_reveal_child (PnlDockRevealer
*self,
+ gboolean
reveal_child);
+gboolean pnl_dock_revealer_get_reveal_child (PnlDockRevealer
*self);
+gint pnl_dock_revealer_get_position (PnlDockRevealer
*self);
+void pnl_dock_revealer_set_position (PnlDockRevealer
*self,
+ gint
position);
+gboolean pnl_dock_revealer_get_position_set (PnlDockRevealer
*self);
+void pnl_dock_revealer_set_position_set (PnlDockRevealer
*self,
+ gboolean
position_set);
+guint pnl_dock_revealer_get_transition_duration (PnlDockRevealer
*self);
+void pnl_dock_revealer_set_transition_duration (PnlDockRevealer
*self,
+ guint
transition_duration);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_REVEALER_H */
diff --git a/contrib/pnl/pnl-dock-stack.c b/contrib/pnl/pnl-dock-stack.c
new file mode 100644
index 0000000..97cf410
--- /dev/null
+++ b/contrib/pnl/pnl-dock-stack.c
@@ -0,0 +1,320 @@
+/* pnl-dock-stack.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-dock-item.h"
+#include "pnl-dock-stack.h"
+#include "pnl-dock-widget.h"
+#include "pnl-dock-tab-strip.h"
+
+typedef struct
+{
+ GtkStack *stack;
+ PnlTabStrip *tab_strip;
+ GtkPositionType edge : 2;
+} PnlDockStackPrivate;
+
+static void pnl_dock_stack_init_dock_item_iface (PnlDockItemInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (PnlDockStack, pnl_dock_stack, GTK_TYPE_BOX, 0,
+ G_ADD_PRIVATE (PnlDockStack)
+ G_IMPLEMENT_INTERFACE (PNL_TYPE_DOCK_ITEM,
+ pnl_dock_stack_init_dock_item_iface))
+
+enum {
+ PROP_0,
+ PROP_EDGE,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+pnl_dock_stack_add (GtkContainer *container,
+ GtkWidget *widget)
+{
+ PnlDockStack *self = (PnlDockStack *)container;
+ PnlDockStackPrivate *priv = pnl_dock_stack_get_instance_private (self);
+ const gchar *title = NULL;
+
+ g_assert (PNL_IS_DOCK_STACK (self));
+
+ if (PNL_IS_DOCK_WIDGET (widget))
+ title = pnl_dock_widget_get_title (PNL_DOCK_WIDGET (widget));
+
+ gtk_container_add_with_properties (GTK_CONTAINER (priv->stack), widget,
+ "title", title,
+ NULL);
+
+ if (PNL_IS_DOCK_ITEM (widget))
+ pnl_dock_item_adopt (PNL_DOCK_ITEM (self), PNL_DOCK_ITEM (widget));
+}
+
+static void
+pnl_dock_stack_grab_focus (GtkWidget *widget)
+{
+ PnlDockStack *self = (PnlDockStack *)widget;
+ PnlDockStackPrivate *priv = pnl_dock_stack_get_instance_private (self);
+ GtkWidget *child;
+
+ g_assert (PNL_IS_DOCK_STACK (self));
+
+ child = gtk_stack_get_visible_child (priv->stack);
+
+ if (child != NULL)
+ gtk_widget_grab_focus (GTK_WIDGET (priv->stack));
+ else
+ GTK_WIDGET_CLASS (pnl_dock_stack_parent_class)->grab_focus (widget);
+}
+
+static void
+pnl_dock_stack_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PnlDockStack *self = PNL_DOCK_STACK (object);
+
+ switch (prop_id)
+ {
+ case PROP_EDGE:
+ g_value_set_enum (value, pnl_dock_stack_get_edge (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pnl_dock_stack_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PnlDockStack *self = PNL_DOCK_STACK (object);
+
+ switch (prop_id)
+ {
+ case PROP_EDGE:
+ pnl_dock_stack_set_edge (self, g_value_get_enum (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pnl_dock_stack_class_init (PnlDockStackClass *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 = pnl_dock_stack_get_property;
+ object_class->set_property = pnl_dock_stack_set_property;
+
+ widget_class->grab_focus = pnl_dock_stack_grab_focus;
+
+ container_class->add = pnl_dock_stack_add;
+
+ properties [PROP_EDGE] =
+ g_param_spec_enum ("edge",
+ "Edge",
+ "The edge for the tab strip",
+ GTK_TYPE_POSITION_TYPE,
+ GTK_POS_TOP,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ gtk_widget_class_set_css_name (widget_class, "dockstack");
+}
+
+static void
+pnl_dock_stack_init (PnlDockStack *self)
+{
+ PnlDockStackPrivate *priv = pnl_dock_stack_get_instance_private (self);
+
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_VERTICAL);
+
+ priv->edge = GTK_POS_TOP;
+
+ priv->stack = g_object_new (GTK_TYPE_STACK,
+ "homogeneous", FALSE,
+ "visible", TRUE,
+ NULL);
+
+ priv->tab_strip = g_object_new (PNL_TYPE_DOCK_TAB_STRIP,
+ "edge", GTK_POS_TOP,
+ "stack", priv->stack,
+ "visible", TRUE,
+ NULL);
+
+ GTK_CONTAINER_CLASS (pnl_dock_stack_parent_class)->add (GTK_CONTAINER (self),
+ GTK_WIDGET (priv->tab_strip));
+ GTK_CONTAINER_CLASS (pnl_dock_stack_parent_class)->add (GTK_CONTAINER (self),
+ GTK_WIDGET (priv->stack));
+}
+
+GtkWidget *
+pnl_dock_stack_new (void)
+{
+ return g_object_new (PNL_TYPE_DOCK_STACK, NULL);
+}
+
+GtkPositionType
+pnl_dock_stack_get_edge (PnlDockStack *self)
+{
+ PnlDockStackPrivate *priv = pnl_dock_stack_get_instance_private (self);
+
+ g_return_val_if_fail (PNL_IS_DOCK_STACK (self), 0);
+
+ return priv->edge;
+}
+
+void
+pnl_dock_stack_set_edge (PnlDockStack *self,
+ GtkPositionType edge)
+{
+ PnlDockStackPrivate *priv = pnl_dock_stack_get_instance_private (self);
+
+ g_return_if_fail (PNL_IS_DOCK_STACK (self));
+ g_return_if_fail (edge >= 0);
+ g_return_if_fail (edge <= 3);
+
+ if (edge != priv->edge)
+ {
+ priv->edge = edge;
+
+ pnl_tab_strip_set_edge (priv->tab_strip, edge);
+
+ switch (edge)
+ {
+ case GTK_POS_TOP:
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (self),
+ GTK_ORIENTATION_VERTICAL);
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->tab_strip),
+ GTK_ORIENTATION_HORIZONTAL);
+ gtk_container_child_set (GTK_CONTAINER (self), GTK_WIDGET (priv->tab_strip),
+ "position", 0,
+ NULL);
+ break;
+
+ case GTK_POS_BOTTOM:
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (self),
+ GTK_ORIENTATION_VERTICAL);
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->tab_strip),
+ GTK_ORIENTATION_HORIZONTAL);
+ gtk_container_child_set (GTK_CONTAINER (self), GTK_WIDGET (priv->tab_strip),
+ "position", 1,
+ NULL);
+ break;
+
+ case GTK_POS_LEFT:
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (self),
+ GTK_ORIENTATION_HORIZONTAL);
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->tab_strip),
+ GTK_ORIENTATION_VERTICAL);
+ gtk_container_child_set (GTK_CONTAINER (self), GTK_WIDGET (priv->tab_strip),
+ "position", 0,
+ NULL);
+ break;
+
+ case GTK_POS_RIGHT:
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (self),
+ GTK_ORIENTATION_HORIZONTAL);
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (priv->tab_strip),
+ GTK_ORIENTATION_VERTICAL);
+ gtk_container_child_set (GTK_CONTAINER (self), GTK_WIDGET (priv->tab_strip),
+ "position", 1,
+ NULL);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_EDGE]);
+ }
+}
+
+static void
+pnl_dock_stack_present_child (PnlDockItem *item,
+ PnlDockItem *child)
+{
+ PnlDockStack *self = (PnlDockStack *)item;
+ PnlDockStackPrivate *priv = pnl_dock_stack_get_instance_private (self);
+
+ g_assert (PNL_IS_DOCK_STACK (self));
+ g_assert (PNL_IS_DOCK_ITEM (child));
+
+ gtk_stack_set_visible_child (priv->stack, GTK_WIDGET (child));
+}
+
+static gboolean
+pnl_dock_stack_get_child_visible (PnlDockItem *item,
+ PnlDockItem *child)
+{
+ PnlDockStack *self = (PnlDockStack *)item;
+ PnlDockStackPrivate *priv = pnl_dock_stack_get_instance_private (self);
+ GtkWidget *visible_child;
+
+ g_assert (PNL_IS_DOCK_STACK (self));
+ g_assert (PNL_IS_DOCK_ITEM (child));
+
+ visible_child = gtk_stack_get_visible_child (priv->stack);
+
+ if (visible_child != NULL)
+ return gtk_widget_is_ancestor (GTK_WIDGET (child), visible_child);
+
+ return FALSE;
+}
+
+static void
+pnl_dock_stack_set_child_visible (PnlDockItem *item,
+ PnlDockItem *child,
+ gboolean child_visible)
+{
+ PnlDockStack *self = (PnlDockStack *)item;
+ PnlDockStackPrivate *priv = pnl_dock_stack_get_instance_private (self);
+ GtkWidget *parent;
+ GtkWidget *last_parent = (GtkWidget *)child;
+
+ g_assert (PNL_IS_DOCK_STACK (self));
+ g_assert (PNL_IS_DOCK_ITEM (child));
+
+ for (parent = gtk_widget_get_parent (GTK_WIDGET (child));
+ parent != NULL;
+ last_parent = parent, parent = gtk_widget_get_parent (parent))
+ {
+ if (parent == (GtkWidget *)priv->stack)
+ {
+ gtk_stack_set_visible_child (priv->stack, last_parent);
+ return;
+ }
+ }
+}
+
+static void
+pnl_dock_stack_init_dock_item_iface (PnlDockItemInterface *iface)
+{
+ iface->present_child = pnl_dock_stack_present_child;
+ iface->get_child_visible = pnl_dock_stack_get_child_visible;
+ iface->set_child_visible = pnl_dock_stack_set_child_visible;
+}
diff --git a/contrib/pnl/pnl-dock-stack.h b/contrib/pnl/pnl-dock-stack.h
new file mode 100644
index 0000000..fb19286
--- /dev/null
+++ b/contrib/pnl/pnl-dock-stack.h
@@ -0,0 +1,42 @@
+/* pnl-dock-stack.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_DOCK_STACK_H
+#define PNL_DOCK_STACK_H
+
+#include "pnl-dock-types.h"
+
+G_BEGIN_DECLS
+
+struct _PnlDockStackClass
+{
+ GtkBoxClass parent;
+};
+
+GtkWidget *pnl_dock_stack_new (void);
+GtkPositionType pnl_dock_stack_get_edge (PnlDockStack *self);
+void pnl_dock_stack_set_edge (PnlDockStack *self,
+ GtkPositionType edge);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_STACK_H */
diff --git a/contrib/pnl/pnl-dock-tab-strip.c b/contrib/pnl/pnl-dock-tab-strip.c
new file mode 100644
index 0000000..3f0b9a6
--- /dev/null
+++ b/contrib/pnl/pnl-dock-tab-strip.c
@@ -0,0 +1,41 @@
+/* pnl-dock-tab-strip.c
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-dock-tab-strip.h"
+
+struct _PnlDockTabStrip
+{
+ PnlTabStrip parent;
+};
+
+enum {
+ PROP_0,
+ N_PROPS
+};
+
+G_DEFINE_TYPE (PnlDockTabStrip, pnl_dock_tab_strip, PNL_TYPE_TAB_STRIP)
+
+static void
+pnl_dock_tab_strip_class_init (PnlDockTabStripClass *klass)
+{
+}
+
+static void
+pnl_dock_tab_strip_init (PnlDockTabStrip *strip)
+{
+}
diff --git a/contrib/pnl/pnl-dock-tab-strip.h b/contrib/pnl/pnl-dock-tab-strip.h
new file mode 100644
index 0000000..6cf04d6
--- /dev/null
+++ b/contrib/pnl/pnl-dock-tab-strip.h
@@ -0,0 +1,34 @@
+/* pnl-dock-tab-strip.h
+ *
+ * Copyright (C) 2016 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PNL_DOCK_TAB_STRIP_H
+#define PNL_DOCK_TAB_STRIP_H
+
+#include "pnl-tab-strip.h"
+
+G_BEGIN_DECLS
+
+#define PNL_TYPE_DOCK_TAB_STRIP (pnl_dock_tab_strip_get_type())
+
+G_DECLARE_FINAL_TYPE (PnlDockTabStrip, pnl_dock_tab_strip, PNL, DOCK_TAB_STRIP, PnlTabStrip)
+
+GtkWidget *pnl_dock_tab_strip_new (void);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_TAB_STRIP_H */
diff --git a/contrib/pnl/pnl-dock-transient-grab.c b/contrib/pnl/pnl-dock-transient-grab.c
new file mode 100644
index 0000000..9f85727
--- /dev/null
+++ b/contrib/pnl/pnl-dock-transient-grab.c
@@ -0,0 +1,322 @@
+/* pnl-dock-transient-grab.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-dock-transient-grab.h"
+
+struct _PnlDockTransientGrab
+{
+ GObject parent_instance;
+
+ GPtrArray *items;
+ GHashTable *hidden;
+
+ guint timeout;
+
+ guint acquired : 1;
+};
+
+G_DEFINE_TYPE (PnlDockTransientGrab, pnl_dock_transient_grab, G_TYPE_OBJECT)
+
+enum {
+ PROP_0,
+ PROP_TIMEOUT,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+pnl_dock_transient_grab_weak_notify (gpointer data,
+ GObject *where_object_was)
+{
+ PnlDockTransientGrab *self = data;
+
+ g_assert (PNL_IS_DOCK_TRANSIENT_GRAB (self));
+
+ g_ptr_array_remove (self->items, where_object_was);
+}
+
+static void
+pnl_dock_transient_grab_finalize (GObject *object)
+{
+ PnlDockTransientGrab *self = (PnlDockTransientGrab *)object;
+ guint i;
+
+ for (i = 0; i < self->items->len; i++)
+ g_object_weak_unref (g_ptr_array_index (self->items, i),
+ pnl_dock_transient_grab_weak_notify,
+ self);
+
+ g_clear_pointer (&self->items, g_ptr_array_unref);
+ g_clear_pointer (&self->hidden, g_hash_table_unref);
+
+ G_OBJECT_CLASS (pnl_dock_transient_grab_parent_class)->finalize (object);
+}
+
+static void
+pnl_dock_transient_grab_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PnlDockTransientGrab *self = PNL_DOCK_TRANSIENT_GRAB (object);
+
+ switch (prop_id)
+ {
+ case PROP_TIMEOUT:
+ g_value_set_uint (value, pnl_dock_transient_grab_get_timeout (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pnl_dock_transient_grab_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PnlDockTransientGrab *self = PNL_DOCK_TRANSIENT_GRAB (object);
+
+ switch (prop_id)
+ {
+ case PROP_TIMEOUT:
+ pnl_dock_transient_grab_set_timeout (self, g_value_get_uint (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pnl_dock_transient_grab_class_init (PnlDockTransientGrabClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = pnl_dock_transient_grab_finalize;
+ object_class->get_property = pnl_dock_transient_grab_get_property;
+ object_class->set_property = pnl_dock_transient_grab_set_property;
+
+ properties [PROP_TIMEOUT] =
+ g_param_spec_uint ("timeout",
+ "Timeout",
+ "Timeout",
+ 0,
+ G_MAXUINT,
+ 0,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+pnl_dock_transient_grab_init (PnlDockTransientGrab *self)
+{
+ self->items = g_ptr_array_new ();
+ self->hidden = g_hash_table_new (NULL, NULL);
+}
+
+PnlDockTransientGrab *
+pnl_dock_transient_grab_new (void)
+{
+ return g_object_new (PNL_TYPE_DOCK_TRANSIENT_GRAB, NULL);
+}
+
+guint
+pnl_dock_transient_grab_get_timeout (PnlDockTransientGrab *self)
+{
+ g_return_val_if_fail (PNL_IS_DOCK_TRANSIENT_GRAB (self), 0);
+
+ return self->timeout;
+}
+
+void
+pnl_dock_transient_grab_set_timeout (PnlDockTransientGrab *self,
+ guint timeout)
+{
+ g_return_if_fail (PNL_IS_DOCK_TRANSIENT_GRAB (self));
+
+ if (timeout != self->timeout)
+ {
+ self->timeout = timeout;
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TIMEOUT]);
+ }
+}
+
+gboolean
+pnl_dock_transient_grab_contains (PnlDockTransientGrab *self,
+ PnlDockItem *item)
+{
+ guint i;
+
+ g_return_val_if_fail (PNL_IS_DOCK_TRANSIENT_GRAB (self), FALSE);
+ g_return_val_if_fail (PNL_IS_DOCK_ITEM (item), FALSE);
+
+ for (i = 0; i < self->items->len; i++)
+ if (g_ptr_array_index (self->items, i) == item)
+ return TRUE;
+
+ return FALSE;
+}
+
+void
+pnl_dock_transient_grab_add_item (PnlDockTransientGrab *self,
+ PnlDockItem *item)
+{
+ g_return_if_fail (PNL_IS_DOCK_TRANSIENT_GRAB (self));
+ g_return_if_fail (PNL_IS_DOCK_ITEM (item));
+
+ g_ptr_array_add (self->items, item);
+
+ g_object_weak_ref (G_OBJECT (item),
+ pnl_dock_transient_grab_weak_notify,
+ self);
+}
+
+static void
+pnl_dock_transient_grab_remove_index (PnlDockTransientGrab *self,
+ guint index)
+{
+ PnlDockItem *item;
+
+ g_return_if_fail (PNL_IS_DOCK_TRANSIENT_GRAB (self));
+ g_return_if_fail (index < self->items->len);
+
+ item = g_ptr_array_index (self->items, index);
+ g_object_weak_unref (G_OBJECT (item),
+ pnl_dock_transient_grab_weak_notify,
+ self);
+ g_ptr_array_remove_index (self->items, index);
+ g_hash_table_remove (self->hidden, item);
+}
+
+void
+pnl_dock_transient_grab_remove_item (PnlDockTransientGrab *self,
+ PnlDockItem *item)
+{
+ guint i;
+
+ g_return_if_fail (PNL_IS_DOCK_TRANSIENT_GRAB (self));
+ g_return_if_fail (PNL_IS_DOCK_ITEM (item));
+
+ for (i = 0; i < self->items->len; i++)
+ {
+ PnlDockItem *iter = g_ptr_array_index (self->items, i);
+
+ if (item == iter)
+ {
+ pnl_dock_transient_grab_remove_index (self, i);
+ return;
+ }
+ }
+}
+
+void
+pnl_dock_transient_grab_acquire (PnlDockTransientGrab *self)
+{
+ guint i;
+
+ g_return_if_fail (PNL_IS_DOCK_TRANSIENT_GRAB (self));
+ g_return_if_fail (self->acquired == FALSE);
+
+ self->acquired = TRUE;
+
+ for (i = self->items->len; i > 1; i--)
+ {
+ PnlDockItem *parent = g_ptr_array_index (self->items, i - 1);
+ PnlDockItem *child = g_ptr_array_index (self->items, i - 2);
+
+ if (!pnl_dock_item_get_child_visible (parent, child))
+ {
+ pnl_dock_item_set_child_visible (parent, child, TRUE);
+ g_hash_table_insert (self->hidden, child, NULL);
+ }
+ }
+}
+
+void
+pnl_dock_transient_grab_release (PnlDockTransientGrab *self)
+{
+ guint i;
+
+ g_return_if_fail (PNL_IS_DOCK_TRANSIENT_GRAB (self));
+ g_return_if_fail (self->acquired == TRUE);
+
+ for (i = 0; i < self->items->len; i++)
+ {
+ PnlDockItem *item = g_ptr_array_index (self->items, i);
+
+ if (g_hash_table_contains (self->hidden, item))
+ {
+ PnlDockItem *parent = pnl_dock_item_get_parent (item);
+
+ if (parent != NULL)
+ pnl_dock_item_set_child_visible (parent, item, FALSE);
+ }
+ }
+}
+
+gboolean
+pnl_dock_transient_grab_is_descendant (PnlDockTransientGrab *self,
+ GtkWidget *widget)
+{
+ g_return_val_if_fail (PNL_IS_DOCK_TRANSIENT_GRAB (self), FALSE);
+
+ if (self->items->len > 0)
+ {
+ GtkWidget *item = g_ptr_array_index (self->items, 0);
+ GtkWidget *ancestor;
+
+ ancestor = gtk_widget_get_ancestor (widget, PNL_TYPE_DOCK_ITEM);
+
+ return (item == ancestor);
+ }
+
+ return FALSE;
+}
+
+void
+pnl_dock_transient_grab_steal_common_ancestors (PnlDockTransientGrab *self,
+ PnlDockTransientGrab *other)
+{
+ guint i;
+
+ g_return_if_fail (PNL_IS_DOCK_TRANSIENT_GRAB (self));
+ g_return_if_fail (PNL_IS_DOCK_TRANSIENT_GRAB (other));
+
+ for (i = other->items->len; i > 0; i--)
+ {
+ PnlDockItem *item = g_ptr_array_index (other->items, i - 1);
+
+ if (pnl_dock_transient_grab_contains (self, item))
+ {
+ /*
+ * Since we are stealing the common ancestors, we don't want the
+ * previous grab to hide them when releasing, so clear the items
+ * from the hash of children it wants to hide.
+ */
+ g_hash_table_remove (other->hidden, item);
+
+ pnl_dock_transient_grab_add_item (self, item);
+ pnl_dock_transient_grab_remove_index (other, i - 1);
+ }
+ }
+}
diff --git a/contrib/pnl/pnl-dock-transient-grab.h b/contrib/pnl/pnl-dock-transient-grab.h
new file mode 100644
index 0000000..1c603d1
--- /dev/null
+++ b/contrib/pnl/pnl-dock-transient-grab.h
@@ -0,0 +1,50 @@
+/* pnl-dock-transient-grab.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PNL_DOCK_TRANSIENT_GRAB_H
+#define PNL_DOCK_TRANSIENT_GRAB_H
+
+#include "pnl-dock-item.h"
+
+G_BEGIN_DECLS
+
+#define PNL_TYPE_DOCK_TRANSIENT_GRAB (pnl_dock_transient_grab_get_type())
+
+G_DECLARE_FINAL_TYPE (PnlDockTransientGrab, pnl_dock_transient_grab, PNL, DOCK_TRANSIENT_GRAB, GObject)
+
+PnlDockTransientGrab *pnl_dock_transient_grab_new (void);
+void pnl_dock_transient_grab_add_item (PnlDockTransientGrab *self,
+ PnlDockItem *item);
+void pnl_dock_transient_grab_remove_item (PnlDockTransientGrab *self,
+ PnlDockItem *item);
+void pnl_dock_transient_grab_acquire (PnlDockTransientGrab *self);
+void pnl_dock_transient_grab_release (PnlDockTransientGrab *self);
+guint pnl_dock_transient_grab_get_timeout (PnlDockTransientGrab *self);
+void pnl_dock_transient_grab_set_timeout (PnlDockTransientGrab *self,
+ guint timeout);
+gboolean pnl_dock_transient_grab_contains (PnlDockTransientGrab *self,
+ PnlDockItem *item);
+gboolean pnl_dock_transient_grab_is_descendant (PnlDockTransientGrab *self,
+ GtkWidget *widget);
+void pnl_dock_transient_grab_steal_common_ancestors (PnlDockTransientGrab *self,
+ PnlDockTransientGrab *other);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_TRANSIENT_GRAB_H */
+
diff --git a/contrib/pnl/pnl-dock-types.h b/contrib/pnl/pnl-dock-types.h
new file mode 100644
index 0000000..33f9c7b
--- /dev/null
+++ b/contrib/pnl/pnl-dock-types.h
@@ -0,0 +1,56 @@
+/* pnl-dock-types.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_TYPES_H
+#define PNL_TYPES_H
+
+#include <gtk/gtk.h>
+
+#include "pnl-multi-paned.h"
+
+G_BEGIN_DECLS
+
+#define PNL_TYPE_DOCK (pnl_dock_get_type ())
+#define PNL_TYPE_DOCK_BIN (pnl_dock_bin_get_type())
+#define PNL_TYPE_DOCK_ITEM (pnl_dock_item_get_type())
+#define PNL_TYPE_DOCK_MANAGER (pnl_dock_manager_get_type())
+#define PNL_TYPE_DOCK_OVERLAY (pnl_dock_overlay_get_type())
+#define PNL_TYPE_DOCK_PANED (pnl_dock_paned_get_type())
+#define PNL_TYPE_DOCK_REVEALER (pnl_dock_revealer_get_type())
+#define PNL_TYPE_DOCK_STACK (pnl_dock_stack_get_type())
+#define PNL_TYPE_DOCK_WIDGET (pnl_dock_widget_get_type())
+#define PNL_TYPE_DOCK_WINDOW (pnl_dock_window_get_type())
+
+G_DECLARE_INTERFACE (PnlDock, pnl_dock, PNL, DOCK, GtkContainer)
+G_DECLARE_DERIVABLE_TYPE (PnlDockBin, pnl_dock_bin, PNL, DOCK_BIN, GtkContainer)
+G_DECLARE_INTERFACE (PnlDockItem, pnl_dock_item, PNL, DOCK_ITEM, GtkWidget)
+G_DECLARE_DERIVABLE_TYPE (PnlDockManager, pnl_dock_manager, PNL, DOCK_MANAGER, GObject)
+G_DECLARE_DERIVABLE_TYPE (PnlDockOverlay, pnl_dock_overlay, PNL, DOCK_OVERLAY, GtkEventBox)
+G_DECLARE_DERIVABLE_TYPE (PnlDockPaned, pnl_dock_paned, PNL, DOCK_PANED, PnlMultiPaned)
+G_DECLARE_DERIVABLE_TYPE (PnlDockRevealer, pnl_dock_revealer, PNL, DOCK_REVEALER, GtkBin)
+G_DECLARE_DERIVABLE_TYPE (PnlDockStack, pnl_dock_stack, PNL, DOCK_STACK, GtkBox)
+G_DECLARE_DERIVABLE_TYPE (PnlDockWidget, pnl_dock_widget, PNL, DOCK_WIDGET, GtkBin)
+G_DECLARE_DERIVABLE_TYPE (PnlDockWindow, pnl_dock_window, PNL, DOCK_WINDOW, GtkWindow)
+
+G_END_DECLS
+
+#endif /* PNL_TYPES_H */
diff --git a/contrib/pnl/pnl-dock-widget.c b/contrib/pnl/pnl-dock-widget.c
new file mode 100644
index 0000000..738618b
--- /dev/null
+++ b/contrib/pnl/pnl-dock-widget.c
@@ -0,0 +1,184 @@
+/* pnl-dock-widget.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-dock-item.h"
+#include "pnl-dock-widget.h"
+#include "pnl-util-private.h"
+
+typedef struct
+{
+ gchar *title;
+} PnlDockWidgetPrivate;
+
+G_DEFINE_TYPE_EXTENDED (PnlDockWidget, pnl_dock_widget, GTK_TYPE_BIN, 0,
+ G_ADD_PRIVATE (PnlDockWidget)
+ G_IMPLEMENT_INTERFACE (PNL_TYPE_DOCK_ITEM, NULL))
+
+enum {
+ PROP_0,
+ PROP_MANAGER,
+ PROP_TITLE,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+pnl_dock_widget_grab_focus (GtkWidget *widget)
+{
+ PnlDockWidget *self = (PnlDockWidget *)widget;
+ GtkWidget *child;
+
+ g_assert (PNL_IS_DOCK_WIDGET (self));
+
+ pnl_dock_item_present (PNL_DOCK_ITEM (self));
+
+ child = gtk_bin_get_child (GTK_BIN (self));
+
+ if (child != NULL)
+ gtk_widget_child_focus (child, GTK_DIR_TAB_FORWARD);
+}
+
+static void
+pnl_dock_widget_finalize (GObject *object)
+{
+ PnlDockWidget *self = (PnlDockWidget *)object;
+ PnlDockWidgetPrivate *priv = pnl_dock_widget_get_instance_private (self);
+
+ g_clear_pointer (&priv->title, g_free);
+
+ G_OBJECT_CLASS (pnl_dock_widget_parent_class)->finalize (object);
+}
+
+static void
+pnl_dock_widget_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PnlDockWidget *self = PNL_DOCK_WIDGET (object);
+
+ switch (prop_id)
+ {
+ case PROP_MANAGER:
+ g_value_set_object (value, pnl_dock_item_get_manager (PNL_DOCK_ITEM (self)));
+ break;
+
+ case PROP_TITLE:
+ g_value_set_string (value, pnl_dock_widget_get_title (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pnl_dock_widget_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PnlDockWidget *self = PNL_DOCK_WIDGET (object);
+
+ switch (prop_id)
+ {
+ case PROP_MANAGER:
+ pnl_dock_item_set_manager (PNL_DOCK_ITEM (self), g_value_get_object (value));
+ break;
+
+ case PROP_TITLE:
+ pnl_dock_widget_set_title (self, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pnl_dock_widget_class_init (PnlDockWidgetClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = pnl_dock_widget_finalize;
+ object_class->get_property = pnl_dock_widget_get_property;
+ object_class->set_property = pnl_dock_widget_set_property;
+
+ widget_class->draw = pnl_gtk_bin_draw;
+ widget_class->grab_focus = pnl_dock_widget_grab_focus;
+ widget_class->size_allocate = pnl_gtk_bin_size_allocate;
+
+ properties [PROP_MANAGER] =
+ g_param_spec_object ("manager",
+ "Manager",
+ "The panel manager",
+ PNL_TYPE_DOCK_MANAGER,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_TITLE] =
+ g_param_spec_string ("title",
+ "Title",
+ "Title",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ gtk_widget_class_set_css_name (widget_class, "dockwidget");
+}
+
+static void
+pnl_dock_widget_init (PnlDockWidget *self)
+{
+ gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
+ gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE);
+}
+
+GtkWidget *
+pnl_dock_widget_new (void)
+{
+ return g_object_new (PNL_TYPE_DOCK_WIDGET, NULL);
+}
+
+const gchar *
+pnl_dock_widget_get_title (PnlDockWidget *self)
+{
+ PnlDockWidgetPrivate *priv = pnl_dock_widget_get_instance_private (self);
+
+ g_return_val_if_fail (PNL_IS_DOCK_WIDGET (self), NULL);
+
+ return priv->title;
+}
+
+void
+pnl_dock_widget_set_title (PnlDockWidget *self,
+ const gchar *title)
+{
+ PnlDockWidgetPrivate *priv = pnl_dock_widget_get_instance_private (self);
+
+ g_return_if_fail (PNL_IS_DOCK_WIDGET (self));
+
+ if (g_strcmp0 (title, priv->title) != 0)
+ {
+ g_free (priv->title);
+ priv->title = g_strdup (title);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TITLE]);
+ }
+}
diff --git a/contrib/pnl/pnl-dock-widget.h b/contrib/pnl/pnl-dock-widget.h
new file mode 100644
index 0000000..7141ad4
--- /dev/null
+++ b/contrib/pnl/pnl-dock-widget.h
@@ -0,0 +1,51 @@
+/* pnl-dock-widget.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_DOCK_WIDGET_H
+#define PNL_DOCK_WIDGET_H
+
+#include "pnl-dock-types.h"
+
+G_BEGIN_DECLS
+
+struct _PnlDockWidgetClass
+{
+ GtkBinClass parent;
+
+ void (*padding1) (void);
+ void (*padding2) (void);
+ void (*padding3) (void);
+ void (*padding4) (void);
+ void (*padding5) (void);
+ void (*padding6) (void);
+ void (*padding7) (void);
+ void (*padding8) (void);
+};
+
+GtkWidget *pnl_dock_widget_new (void);
+const gchar *pnl_dock_widget_get_title (PnlDockWidget *self);
+void pnl_dock_widget_set_title (PnlDockWidget *self,
+ const gchar *title);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_WIDGET_H */
diff --git a/contrib/pnl/pnl-dock-window.c b/contrib/pnl/pnl-dock-window.c
new file mode 100644
index 0000000..4aa7d85
--- /dev/null
+++ b/contrib/pnl/pnl-dock-window.c
@@ -0,0 +1,113 @@
+/* pnl-dock-window.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-dock-item.h"
+#include "pnl-dock-window.h"
+
+typedef struct
+{
+ void *foo;
+} PnlDockWindowPrivate;
+
+static void pnl_dock_window_init_dock_iface (PnlDockInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (PnlDockWindow, pnl_dock_window, GTK_TYPE_WINDOW, 0,
+ G_ADD_PRIVATE (PnlDockWindow)
+ G_IMPLEMENT_INTERFACE (PNL_TYPE_DOCK_ITEM, NULL)
+ G_IMPLEMENT_INTERFACE (PNL_TYPE_DOCK, pnl_dock_window_init_dock_iface))
+
+enum {
+ PROP_0,
+ PROP_MANAGER,
+ N_PROPS
+};
+
+static void
+pnl_dock_window_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (pnl_dock_window_parent_class)->finalize (object);
+}
+
+static void
+pnl_dock_window_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PnlDockWindow *self = PNL_DOCK_WINDOW (object);
+
+ switch (prop_id)
+ {
+ case PROP_MANAGER:
+ g_value_set_object (value, pnl_dock_item_get_manager (PNL_DOCK_ITEM (self)));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pnl_dock_window_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PnlDockWindow *self = PNL_DOCK_WINDOW (object);
+
+ switch (prop_id)
+ {
+ case PROP_MANAGER:
+ pnl_dock_item_set_manager (PNL_DOCK_ITEM (self), g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pnl_dock_window_class_init (PnlDockWindowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = pnl_dock_window_finalize;
+ object_class->get_property = pnl_dock_window_get_property;
+ object_class->set_property = pnl_dock_window_set_property;
+
+ g_object_class_override_property (object_class, PROP_MANAGER, "manager");
+
+ gtk_widget_class_set_css_name (widget_class, "dockwindow");
+}
+
+static void
+pnl_dock_window_init (PnlDockWindow *self)
+{
+}
+
+GtkWidget *
+pnl_dock_window_new (void)
+{
+ return g_object_new (PNL_TYPE_DOCK_WINDOW, NULL);
+}
+
+static void
+pnl_dock_window_init_dock_iface (PnlDockInterface *iface)
+{
+}
diff --git a/contrib/pnl/pnl-dock-window.h b/contrib/pnl/pnl-dock-window.h
new file mode 100644
index 0000000..b45fb2d
--- /dev/null
+++ b/contrib/pnl/pnl-dock-window.h
@@ -0,0 +1,39 @@
+/* pnl-dock-window.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_DOCK_WINDOW_H
+#define PNL_DOCK_WINDOW_H
+
+#include "pnl-dock.h"
+
+G_BEGIN_DECLS
+
+struct _PnlDockWindowClass
+{
+ GtkWindowClass parent;
+};
+
+GtkWidget *pnl_dock_window_new (void);
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_WINDOW_H */
diff --git a/contrib/pnl/pnl-dock.c b/contrib/pnl/pnl-dock.c
new file mode 100644
index 0000000..ae47861
--- /dev/null
+++ b/contrib/pnl/pnl-dock.c
@@ -0,0 +1,43 @@
+/* pnl-dock.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-dock.h"
+#include "pnl-resources.h"
+
+G_DEFINE_INTERFACE (PnlDock, pnl_dock, GTK_TYPE_CONTAINER)
+
+static void
+pnl_dock_default_init (PnlDockInterface *iface)
+{
+ GdkScreen *screen;
+
+ g_resources_register (pnl_get_resource ());
+
+ screen = gdk_screen_get_default ();
+
+ if (screen != NULL)
+ gtk_icon_theme_add_resource_path (gtk_icon_theme_get_default (),
+ "/org/gnome/panel-gtk/icons");
+
+ g_object_interface_install_property (iface,
+ g_param_spec_object ("manager",
+ "Manager",
+ "Manager",
+ PNL_TYPE_DOCK_MANAGER,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
+}
diff --git a/contrib/pnl/pnl-dock.h b/contrib/pnl/pnl-dock.h
new file mode 100644
index 0000000..f2b63c7
--- /dev/null
+++ b/contrib/pnl/pnl-dock.h
@@ -0,0 +1,37 @@
+/* pnl-dock.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_DOCK_H
+#define PNL_DOCK_H
+
+#include "pnl-dock-types.h"
+
+G_BEGIN_DECLS
+
+struct _PnlDockInterface
+{
+ GTypeInterface parent;
+};
+
+G_END_DECLS
+
+#endif /* PNL_DOCK_H */
diff --git a/contrib/pnl/pnl-frame-source.c b/contrib/pnl/pnl-frame-source.c
new file mode 100644
index 0000000..e70f5e6
--- /dev/null
+++ b/contrib/pnl/pnl-frame-source.c
@@ -0,0 +1,130 @@
+/* pnl-frame-source.c
+ *
+ * Copyright (C) 2010-2016 Christian Hergert <christian hergert me>
+ *
+ * This file 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.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file 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 General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-frame-source.h"
+
+typedef struct
+{
+ GSource parent;
+ guint fps;
+ guint frame_count;
+ gint64 start_time;
+} PnlFrameSource;
+
+static gboolean
+pnl_frame_source_prepare (GSource *source,
+ gint *timeout_)
+{
+ PnlFrameSource *fsource = (PnlFrameSource *)(gpointer)source;
+ gint64 current_time;
+ guint elapsed_time;
+ guint new_frame_num;
+ guint frame_time;
+
+ current_time = g_source_get_time(source) / 1000;
+ elapsed_time = current_time - fsource->start_time;
+ new_frame_num = elapsed_time * fsource->fps / 1000;
+
+ /* If time has gone backwards or the time since the last frame is
+ * greater than the two frames worth then reset the time and do a
+ * frame now */
+ if (new_frame_num < fsource->frame_count ||
+ new_frame_num - fsource->frame_count > 2) {
+ /* Get the frame time rounded up to the nearest ms */
+ frame_time = (1000 + fsource->fps - 1) / fsource->fps;
+
+ /* Reset the start time */
+ fsource->start_time = current_time;
+
+ /* Move the start time as if one whole frame has elapsed */
+ fsource->start_time -= frame_time;
+ fsource->frame_count = 0;
+ *timeout_ = 0;
+ return TRUE;
+ } else if (new_frame_num > fsource->frame_count) {
+ *timeout_ = 0;
+ return TRUE;
+ } else {
+ *timeout_ = (fsource->frame_count + 1) * 1000 / fsource->fps - elapsed_time;
+ return FALSE;
+ }
+}
+
+static gboolean
+pnl_frame_source_check (GSource *source)
+{
+ gint timeout_;
+ return pnl_frame_source_prepare(source, &timeout_);
+}
+
+static gboolean
+pnl_frame_source_dispatch (GSource *source,
+ GSourceFunc source_func,
+ gpointer user_data)
+{
+ PnlFrameSource *fsource = (PnlFrameSource *)(gpointer)source;
+ gboolean ret;
+
+ if ((ret = source_func(user_data)))
+ fsource->frame_count++;
+ return ret;
+}
+
+static GSourceFuncs source_funcs = {
+ pnl_frame_source_prepare,
+ pnl_frame_source_check,
+ pnl_frame_source_dispatch,
+};
+
+/**
+ * pnl_frame_source_add:
+ * @frames_per_sec: (in): Target frames per second.
+ * @callback: (in) (scope notified): A #GSourceFunc to execute.
+ * @user_data: (in): User data for @callback.
+ *
+ * Creates a new frame source that will execute when the timeout interval
+ * for the source has elapsed. The timing will try to synchronize based
+ * on the end time of the animation.
+ *
+ * Returns: A source id that can be removed with g_source_remove().
+ */
+guint
+pnl_frame_source_add (guint frames_per_sec,
+ GSourceFunc callback,
+ gpointer user_data)
+{
+ PnlFrameSource *fsource;
+ GSource *source;
+ guint ret;
+
+ g_return_val_if_fail (frames_per_sec > 0, 0);
+ g_return_val_if_fail (frames_per_sec <= 120, 0);
+
+ source = g_source_new(&source_funcs, sizeof(PnlFrameSource));
+ fsource = (PnlFrameSource *)(gpointer)source;
+ fsource->fps = frames_per_sec;
+ fsource->frame_count = 0;
+ fsource->start_time = g_get_monotonic_time() / 1000;
+ g_source_set_callback(source, callback, user_data, NULL);
+ g_source_set_name(source, "PnlFrameSource");
+
+ ret = g_source_attach(source, NULL);
+ g_source_unref(source);
+
+ return ret;
+}
diff --git a/contrib/pnl/pnl-frame-source.h b/contrib/pnl/pnl-frame-source.h
new file mode 100644
index 0000000..043209d
--- /dev/null
+++ b/contrib/pnl/pnl-frame-source.h
@@ -0,0 +1,36 @@
+/* pnl-frame-source.h
+ *
+ * Copyright (C) 2010-2016 Christian Hergert <christian hergert me>
+ *
+ * This file 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.1 of the License, or (at your option)
+ * any later version.
+ *
+ * This file 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 General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_FRAME_SOURCE_H
+#define PNL_FRAME_SOURCE_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+guint pnl_frame_source_add (guint frames_per_sec,
+ GSourceFunc callback,
+ gpointer user_data);
+
+G_END_DECLS
+
+#endif /* PNL_FRAME_SOURCE_H */
diff --git a/contrib/pnl/pnl-multi-paned.c b/contrib/pnl/pnl-multi-paned.c
new file mode 100644
index 0000000..b18ae7f
--- /dev/null
+++ b/contrib/pnl/pnl-multi-paned.c
@@ -0,0 +1,1863 @@
+/* pnl-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 "pnl-multi-paned.h"
+
+#define HANDLE_WIDTH 10
+#define HANDLE_HEIGHT 10
+
+#define IS_HORIZONTAL(o) (o == GTK_ORIENTATION_HORIZONTAL)
+
+/**
+ * SECTION:pnl-multi-paned
+ * @title: PnlMultiPaned
+ * @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
+ * #PnlMultiPaned, 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;
+} PnlMultiPanedChild;
+
+typedef struct
+{
+ /*
+ * A GArray of PnlMultiPanedChild 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.
+ */
+ PnlMultiPanedChild *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;
+} PnlMultiPanedPrivate;
+
+typedef struct
+{
+ PnlMultiPanedChild **children;
+ guint n_children;
+ GtkOrientation orientation;
+ GtkAllocation top_alloc;
+ gint avail_width;
+ gint avail_height;
+ gint handle_size;
+} AllocationState;
+
+typedef void (*AllocationStage) (PnlMultiPaned *self,
+ AllocationState *state);
+
+static void allocation_stage_allocate (PnlMultiPaned *self,
+ AllocationState *state);
+static void allocation_stage_borders (PnlMultiPaned *self,
+ AllocationState *state);
+static void allocation_stage_cache_request (PnlMultiPaned *self,
+ AllocationState *state);
+static void allocation_stage_drag_overflow (PnlMultiPaned *self,
+ AllocationState *state);
+static void allocation_stage_expand (PnlMultiPaned *self,
+ AllocationState *state);
+static void allocation_stage_handles (PnlMultiPaned *self,
+ AllocationState *state);
+static void allocation_stage_minimums (PnlMultiPaned *self,
+ AllocationState *state);
+static void allocation_stage_naturals (PnlMultiPaned *self,
+ AllocationState *state);
+static void allocation_stage_positions (PnlMultiPaned *self,
+ AllocationState *state);
+
+G_DEFINE_TYPE_EXTENDED (PnlMultiPaned, pnl_multi_paned, GTK_TYPE_CONTAINER, 0,
+ G_ADD_PRIVATE (PnlMultiPaned)
+ 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
+pnl_multi_paned_reset_positions (PnlMultiPaned *self)
+{
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+ guint i;
+
+ g_assert (PNL_IS_MULTI_PANED (self));
+
+ for (i = 0; i < priv->children->len; i++)
+ {
+ PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, 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 PnlMultiPanedChild *
+pnl_multi_paned_get_next_visible_child (PnlMultiPaned *self,
+ PnlMultiPanedChild *child)
+{
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+ guint i;
+
+ g_assert (PNL_IS_MULTI_PANED (self));
+ g_assert (child != NULL);
+ g_assert (priv->children != NULL);
+ g_assert (priv->children->len > 0);
+
+ i = child - ((PnlMultiPanedChild *)(gpointer)priv->children->data);
+
+ for (++i; i < priv->children->len; i++)
+ {
+ PnlMultiPanedChild *next = &g_array_index (priv->children, PnlMultiPanedChild, i);
+
+ if (gtk_widget_get_visible (next->widget))
+ return next;
+ }
+
+ return NULL;
+}
+
+static gboolean
+pnl_multi_paned_is_last_visible_child (PnlMultiPaned *self,
+ PnlMultiPanedChild *child)
+{
+ g_assert (PNL_IS_MULTI_PANED (self));
+ g_assert (child != NULL);
+
+ return !pnl_multi_paned_get_next_visible_child (self, child);
+}
+
+static void
+pnl_multi_paned_get_handle_rect (PnlMultiPaned *self,
+ PnlMultiPanedChild *child,
+ GdkRectangle *handle_rect)
+{
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+ GtkAllocation alloc;
+
+ g_assert (PNL_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 (pnl_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
+pnl_multi_paned_create_child_handle (PnlMultiPaned *self,
+ PnlMultiPanedChild *child)
+{
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+ GdkWindowAttr attributes = { 0 };
+ GdkDisplay *display;
+ GdkWindow *parent;
+ GdkCursorType cursor_type;
+ GdkRectangle handle_rect;
+
+ g_assert (PNL_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_type = (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ ? GDK_SB_H_DOUBLE_ARROW
+ : GDK_SB_V_DOUBLE_ARROW;
+
+ pnl_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.x = -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_for_display (display, cursor_type);
+
+ 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
+pnl_multi_paned_calc_handle_size (PnlMultiPaned *self)
+{
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+ gint visible_children = 0;
+ gint handle_size = 1;
+ guint i;
+
+ g_assert (PNL_IS_MULTI_PANED (self));
+
+ gtk_widget_style_get (GTK_WIDGET (self), "handle-size", &handle_size, NULL);
+
+ for (i = 0; i < priv->children->len; i++)
+ {
+ PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, i);
+
+ if (gtk_widget_get_visible (child->widget))
+ visible_children++;
+ }
+
+ return MAX (0, (visible_children - 1) * handle_size);
+}
+
+static void
+pnl_multi_paned_destroy_child_handle (PnlMultiPaned *self,
+ PnlMultiPanedChild *child)
+{
+ g_assert (PNL_IS_MULTI_PANED (self));
+ g_assert (child != NULL);
+
+ if (child->handle != NULL)
+ {
+ gdk_window_destroy (child->handle);
+ child->handle = NULL;
+ }
+}
+
+static PnlMultiPanedChild *
+pnl_multi_paned_get_child (PnlMultiPaned *self,
+ GtkWidget *widget)
+{
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+ guint i;
+
+ g_assert (PNL_IS_MULTI_PANED (self));
+ g_assert (GTK_IS_WIDGET (widget));
+
+ for (i = 0; i < priv->children->len; i++)
+ {
+ PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, i);
+
+ if (child->widget == widget)
+ return child;
+ }
+
+ g_assert_not_reached ();
+
+ return NULL;
+}
+
+static gint
+pnl_multi_paned_get_child_position (PnlMultiPaned *self,
+ GtkWidget *widget)
+{
+ PnlMultiPanedChild *child;
+
+ g_assert (PNL_IS_MULTI_PANED (self));
+ g_assert (GTK_IS_WIDGET (widget));
+
+ child = pnl_multi_paned_get_child (self, widget);
+
+ return child->position;
+}
+
+static void
+pnl_multi_paned_set_child_position (PnlMultiPaned *self,
+ GtkWidget *widget,
+ gint position)
+{
+ PnlMultiPanedChild *child;
+
+ g_assert (PNL_IS_MULTI_PANED (self));
+ g_assert (GTK_IS_WIDGET (widget));
+ g_assert (position >= -1);
+
+ child = pnl_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
+pnl_multi_paned_add (GtkContainer *container,
+ GtkWidget *widget)
+{
+ PnlMultiPaned *self = (PnlMultiPaned *)container;
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+ PnlMultiPanedChild child = { 0 };
+
+ g_assert (PNL_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)))
+ pnl_multi_paned_create_child_handle (self, &child);
+
+ gtk_widget_set_parent (widget, GTK_WIDGET (self));
+
+ g_array_append_val (priv->children, child);
+
+ pnl_multi_paned_reset_positions (self);
+
+ gtk_gesture_set_state (GTK_GESTURE (priv->gesture), GTK_EVENT_SEQUENCE_DENIED);
+}
+
+static void
+pnl_multi_paned_remove (GtkContainer *container,
+ GtkWidget *widget)
+{
+ PnlMultiPaned *self = (PnlMultiPaned *)container;
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+ guint i;
+
+ g_assert (PNL_IS_MULTI_PANED (self));
+ g_assert (GTK_IS_WIDGET (widget));
+
+ for (i = 0; i < priv->children->len; i++)
+ {
+ PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, i);
+
+ if (child->widget == widget)
+ {
+ pnl_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;
+ }
+ }
+
+ pnl_multi_paned_reset_positions (self);
+
+ gtk_gesture_set_state (GTK_GESTURE (priv->gesture), GTK_EVENT_SEQUENCE_DENIED);
+}
+
+static void
+pnl_multi_paned_forall (GtkContainer *container,
+ gboolean include_internals,
+ GtkCallback callback,
+ gpointer user_data)
+{
+ PnlMultiPaned *self = (PnlMultiPaned *)container;
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+ gint i;
+
+ g_assert (PNL_IS_MULTI_PANED (self));
+ g_assert (callback != NULL);
+
+ for (i = priv->children->len; i > 0; i--)
+ {
+ PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, i - 1);
+
+ callback (child->widget, user_data);
+ }
+}
+
+static GtkSizeRequestMode
+pnl_multi_paned_get_request_mode (GtkWidget *widget)
+{
+ PnlMultiPaned *self = (PnlMultiPaned *)widget;
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+
+ g_assert (PNL_IS_MULTI_PANED (self));
+
+ return (priv->orientation == GTK_ORIENTATION_HORIZONTAL) ? GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT
+ : GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
+}
+
+static void
+pnl_multi_paned_get_preferred_height (GtkWidget *widget,
+ gint *min_height,
+ gint *nat_height)
+{
+ PnlMultiPaned *self = (PnlMultiPaned *)widget;
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+ guint i;
+ gint real_min_height = 0;
+ gint real_nat_height = 0;
+
+ g_assert (PNL_IS_MULTI_PANED (self));
+ g_assert (min_height != NULL);
+ g_assert (nat_height != NULL);
+
+ for (i = 0; i < priv->children->len; i++)
+ {
+ PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, 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 = pnl_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
+pnl_multi_paned_get_child_preferred_height_for_width (PnlMultiPaned *self,
+ PnlMultiPanedChild *children,
+ gint n_children,
+ gint width,
+ gint *min_height,
+ gint *nat_height)
+{
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+ PnlMultiPanedChild *child = children;
+ gint child_min_height = 0;
+ gint child_nat_height = 0;
+ gint neighbor_min_height = 0;
+ gint neighbor_nat_height = 0;
+
+ g_assert (PNL_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);
+
+ pnl_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
+pnl_multi_paned_get_preferred_height_for_width (GtkWidget *widget,
+ gint width,
+ gint *min_height,
+ gint *nat_height)
+{
+ PnlMultiPaned *self = (PnlMultiPaned *)widget;
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+
+ g_assert (PNL_IS_MULTI_PANED (self));
+ g_assert (min_height != NULL);
+ g_assert (nat_height != NULL);
+
+ *min_height = 0;
+ *nat_height = 0;
+
+ pnl_multi_paned_get_child_preferred_height_for_width (self,
+ (PnlMultiPanedChild *)(gpointer)priv->children->data,
+ priv->children->len,
+ width,
+ min_height,
+ nat_height);
+
+ if (priv->orientation == GTK_ORIENTATION_VERTICAL)
+ {
+ gint handle_size = pnl_multi_paned_calc_handle_size (self);
+
+ *min_height += handle_size;
+ *nat_height += handle_size;
+ }
+}
+
+static void
+pnl_multi_paned_get_preferred_width (GtkWidget *widget,
+ gint *min_width,
+ gint *nat_width)
+{
+ PnlMultiPaned *self = (PnlMultiPaned *)widget;
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+ guint i;
+ gint real_min_width = 0;
+ gint real_nat_width = 0;
+
+ g_assert (PNL_IS_MULTI_PANED (self));
+ g_assert (min_width != NULL);
+ g_assert (nat_width != NULL);
+
+ for (i = 0; i < priv->children->len; i++)
+ {
+ PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, 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 = pnl_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
+pnl_multi_paned_get_child_preferred_width_for_height (PnlMultiPaned *self,
+ PnlMultiPanedChild *children,
+ gint n_children,
+ gint height,
+ gint *min_width,
+ gint *nat_width)
+{
+ PnlMultiPanedChild *child = children;
+ PnlMultiPanedPrivate *priv = pnl_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 (PNL_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);
+
+ pnl_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
+pnl_multi_paned_get_preferred_width_for_height (GtkWidget *widget,
+ gint height,
+ gint *min_width,
+ gint *nat_width)
+{
+ PnlMultiPaned *self = (PnlMultiPaned *)widget;
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+
+ g_assert (PNL_IS_MULTI_PANED (self));
+ g_assert (min_width != NULL);
+ g_assert (nat_width != NULL);
+
+ pnl_multi_paned_get_child_preferred_width_for_height (self,
+ (PnlMultiPanedChild *)(gpointer)priv->children->data,
+ priv->children->len,
+ height,
+ min_width,
+ nat_width);
+
+ if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ {
+ gint handle_size = pnl_multi_paned_calc_handle_size (self);
+
+ *min_width += handle_size;
+ *nat_width += handle_size;
+ }
+}
+
+static void
+allocation_stage_handles (PnlMultiPaned *self,
+ AllocationState *state)
+{
+ guint i;
+
+ g_assert (PNL_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++)
+ {
+ PnlMultiPanedChild *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 (PnlMultiPaned *self,
+ AllocationState *state)
+{
+ gint next_x;
+ gint next_y;
+ guint i;
+
+ g_assert (PNL_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++)
+ {
+ PnlMultiPanedChild *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 (PnlMultiPaned *self,
+ AllocationState *state)
+{
+ gint x_adjust = 0;
+ gint y_adjust = 0;
+ guint i;
+
+ g_assert (PNL_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++)
+ {
+ PnlMultiPanedChild *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 (PnlMultiPaned *self,
+ AllocationState *state)
+{
+ gint border_width;
+
+ g_assert (PNL_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 (PnlMultiPaned *self,
+ AllocationState *state)
+{
+ guint i;
+
+ g_assert (PNL_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++)
+ {
+ PnlMultiPanedChild *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 (PnlMultiPaned *self,
+ AllocationState *state)
+{
+ gint x_adjust = 0;
+ gint y_adjust = 0;
+ guint i;
+
+ g_assert (PNL_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++)
+ {
+ PnlMultiPanedChild *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 (PnlMultiPaned *self,
+ AllocationState *state)
+{
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+ guint drag_index;
+ gint j;
+ gint drag_overflow;
+
+ g_assert (PNL_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--)
+ {
+ PnlMultiPanedChild *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++)
+ {
+ PnlMultiPanedChild *neighbor = state->children [k];
+
+ if (IS_HORIZONTAL (state->orientation))
+ neighbor->alloc.x -= adjust;
+ else
+ neighbor->alloc.y -= adjust;
+ }
+ }
+}
+
+static void
+allocation_stage_expand (PnlMultiPaned *self,
+ AllocationState *state)
+{
+ gint x_adjust = 0;
+ gint y_adjust = 0;
+ gint n_expand = 0;
+ gint adjust;
+ guint i;
+
+ g_assert (PNL_IS_MULTI_PANED (self));
+ g_assert (state != NULL);
+ g_assert (state->children != NULL);
+ g_assert (state->n_children > 0);
+
+ if (state->n_children == 1)
+ {
+ PnlMultiPanedChild *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++)
+ {
+ PnlMultiPanedChild *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++)
+ {
+ PnlMultiPanedChild *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 (PnlMultiPaned *self,
+ AllocationState *state)
+{
+ guint i;
+
+ g_assert (PNL_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++)
+ {
+ PnlMultiPanedChild *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
+pnl_multi_paned_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ PnlMultiPaned *self = (PnlMultiPaned *)widget;
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+ AllocationState state = { 0 };
+ GPtrArray *children;
+ guint i;
+
+ g_assert (PNL_IS_MULTI_PANED (self));
+ g_assert (allocation != NULL);
+
+ GTK_WIDGET_CLASS (pnl_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++)
+ {
+ PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, 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 = (PnlMultiPanedChild **)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
+pnl_multi_paned_realize (GtkWidget *widget)
+{
+ PnlMultiPaned *self = (PnlMultiPaned *)widget;
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+ guint i;
+
+ g_assert (PNL_IS_MULTI_PANED (self));
+
+ GTK_WIDGET_CLASS (pnl_multi_paned_parent_class)->realize (widget);
+
+ for (i = 0; i < priv->children->len; i++)
+ {
+ PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, i);
+
+ pnl_multi_paned_create_child_handle (self, child);
+ }
+}
+
+static void
+pnl_multi_paned_unrealize (GtkWidget *widget)
+{
+ PnlMultiPaned *self = (PnlMultiPaned *)widget;
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+ guint i;
+
+ g_assert (PNL_IS_MULTI_PANED (self));
+
+ for (i = 0; i < priv->children->len; i++)
+ {
+ PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, i);
+
+ pnl_multi_paned_destroy_child_handle (self, child);
+ }
+
+ GTK_WIDGET_CLASS (pnl_multi_paned_parent_class)->unrealize (widget);
+}
+
+static void
+pnl_multi_paned_map (GtkWidget *widget)
+{
+ PnlMultiPaned *self = (PnlMultiPaned *)widget;
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+ guint i;
+
+ g_assert (PNL_IS_MULTI_PANED (self));
+
+ GTK_WIDGET_CLASS (pnl_multi_paned_parent_class)->map (widget);
+
+ for (i = 0; i < priv->children->len; i++)
+ {
+ PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, i);
+
+ gdk_window_show (child->handle);
+ }
+}
+
+static void
+pnl_multi_paned_unmap (GtkWidget *widget)
+{
+ PnlMultiPaned *self = (PnlMultiPaned *)widget;
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+ guint i;
+
+ g_assert (PNL_IS_MULTI_PANED (self));
+
+ for (i = 0; i < priv->children->len; i++)
+ {
+ PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, i);
+
+ gdk_window_hide (child->handle);
+ }
+
+ GTK_WIDGET_CLASS (pnl_multi_paned_parent_class)->unmap (widget);
+}
+
+static gboolean
+pnl_multi_paned_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ PnlMultiPaned *self = (PnlMultiPaned *)widget;
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+ gboolean ret;
+
+ g_assert (PNL_IS_MULTI_PANED (self));
+ g_assert (cr != NULL);
+
+ ret = GTK_WIDGET_CLASS (pnl_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++)
+ {
+ PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, 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 (!pnl_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
+pnl_multi_paned_pan_gesture_drag_begin (PnlMultiPaned *self,
+ gdouble x,
+ gdouble y,
+ GtkGesturePan *gesture)
+{
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+ GdkEventSequence *sequence;
+ const GdkEvent *event;
+ guint i;
+
+ g_assert (PNL_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++)
+ {
+ PnlMultiPanedChild *child = &g_array_index (priv->children, PnlMultiPanedChild, 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
+pnl_multi_paned_pan_gesture_drag_end (PnlMultiPaned *self,
+ gdouble x,
+ gdouble y,
+ GtkGesturePan *gesture)
+{
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+ GdkEventSequence *sequence;
+ GtkEventSequenceState state;
+
+ g_assert (PNL_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
+pnl_multi_paned_pan_gesture_pan (PnlMultiPaned *self,
+ GtkPanDirection direction,
+ gdouble offset,
+ GtkGesturePan *gesture)
+{
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+ GtkAllocation alloc;
+
+ g_assert (PNL_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
+pnl_multi_paned_create_pan_gesture (PnlMultiPaned *self)
+{
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+ GtkGesture *gesture;
+
+ g_assert (PNL_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 (pnl_multi_paned_pan_gesture_drag_begin),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (gesture,
+ "drag-end",
+ G_CALLBACK (pnl_multi_paned_pan_gesture_drag_end),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (gesture,
+ "pan",
+ G_CALLBACK (pnl_multi_paned_pan_gesture_pan),
+ self,
+ G_CONNECT_SWAPPED);
+
+ priv->gesture = GTK_GESTURE_PAN (gesture);
+}
+
+static void
+pnl_multi_paned_resize_drag_begin (PnlMultiPaned *self,
+ GtkWidget *child)
+{
+ g_assert (PNL_IS_MULTI_PANED (self));
+ g_assert (GTK_IS_WIDGET (child));
+
+}
+
+static void
+pnl_multi_paned_resize_drag_end (PnlMultiPaned *self,
+ GtkWidget *child)
+{
+ g_assert (PNL_IS_MULTI_PANED (self));
+ g_assert (GTK_IS_WIDGET (child));
+
+}
+
+static void
+pnl_multi_paned_get_child_property (GtkContainer *container,
+ GtkWidget *widget,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PnlMultiPaned *self = PNL_MULTI_PANED (container);
+
+ switch (prop_id)
+ {
+ case CHILD_PROP_POSITION:
+ g_value_set_int (value, pnl_multi_paned_get_child_position (self, widget));
+ break;
+
+ default:
+ GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec);
+ }
+}
+
+static void
+pnl_multi_paned_set_child_property (GtkContainer *container,
+ GtkWidget *widget,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PnlMultiPaned *self = PNL_MULTI_PANED (container);
+
+ switch (prop_id)
+ {
+ case CHILD_PROP_POSITION:
+ pnl_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
+pnl_multi_paned_finalize (GObject *object)
+{
+ PnlMultiPaned *self = (PnlMultiPaned *)object;
+ PnlMultiPanedPrivate *priv = pnl_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 (pnl_multi_paned_parent_class)->finalize (object);
+}
+
+static void
+pnl_multi_paned_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PnlMultiPaned *self = PNL_MULTI_PANED (object);
+ PnlMultiPanedPrivate *priv = pnl_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
+pnl_multi_paned_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PnlMultiPaned *self = PNL_MULTI_PANED (object);
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+
+ switch (prop_id)
+ {
+ case PROP_ORIENTATION:
+ priv->orientation = g_value_get_enum (value);
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pnl_multi_paned_class_init (PnlMultiPanedClass *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 = pnl_multi_paned_get_property;
+ object_class->set_property = pnl_multi_paned_set_property;
+ object_class->finalize = pnl_multi_paned_finalize;
+
+ widget_class->get_request_mode = pnl_multi_paned_get_request_mode;
+ widget_class->get_preferred_width = pnl_multi_paned_get_preferred_width;
+ widget_class->get_preferred_height = pnl_multi_paned_get_preferred_height;
+ widget_class->get_preferred_width_for_height = pnl_multi_paned_get_preferred_width_for_height;
+ widget_class->get_preferred_height_for_width = pnl_multi_paned_get_preferred_height_for_width;
+ widget_class->size_allocate = pnl_multi_paned_size_allocate;
+ widget_class->realize = pnl_multi_paned_realize;
+ widget_class->unrealize = pnl_multi_paned_unrealize;
+ widget_class->map = pnl_multi_paned_map;
+ widget_class->unmap = pnl_multi_paned_unmap;
+ widget_class->draw = pnl_multi_paned_draw;
+
+ container_class->add = pnl_multi_paned_add;
+ container_class->remove = pnl_multi_paned_remove;
+ container_class->get_child_property = pnl_multi_paned_get_child_property;
+ container_class->set_child_property = pnl_multi_paned_set_child_property;
+ container_class->forall = pnl_multi_paned_forall;
+
+ klass->resize_drag_begin = pnl_multi_paned_resize_drag_begin;
+ klass->resize_drag_end = pnl_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 (PnlMultiPanedClass, 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 (PnlMultiPanedClass, resize_drag_end),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1, GTK_TYPE_WIDGET);
+}
+
+static void
+pnl_multi_paned_init (PnlMultiPaned *self)
+{
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+
+ gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
+
+ priv->children = g_array_new (FALSE, TRUE, sizeof (PnlMultiPanedChild));
+
+ pnl_multi_paned_create_pan_gesture (self);
+}
+
+GtkWidget *
+pnl_multi_paned_new (void)
+{
+ return g_object_new (PNL_TYPE_MULTI_PANED, NULL);
+}
+
+guint
+pnl_multi_paned_get_n_children (PnlMultiPaned *self)
+{
+ PnlMultiPanedPrivate *priv = pnl_multi_paned_get_instance_private (self);
+
+ g_return_val_if_fail (PNL_IS_MULTI_PANED (self), 0);
+
+ return priv->children ? priv->children->len : 0;
+}
diff --git a/contrib/pnl/pnl-multi-paned.h b/contrib/pnl/pnl-multi-paned.h
new file mode 100644
index 0000000..2dae9cd
--- /dev/null
+++ b/contrib/pnl/pnl-multi-paned.h
@@ -0,0 +1,59 @@
+/* pnl-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
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_MULTI_PANED_H
+#define PNL_MULTI_PANED_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define PNL_TYPE_MULTI_PANED (pnl_multi_paned_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (PnlMultiPaned, pnl_multi_paned, PNL, MULTI_PANED, GtkContainer)
+
+struct _PnlMultiPanedClass
+{
+ GtkContainerClass parent;
+
+ void (*resize_drag_begin) (PnlMultiPaned *self,
+ GtkWidget *child);
+ void (*resize_drag_end) (PnlMultiPaned *self,
+ GtkWidget *child);
+
+ void (*padding1) (void);
+ void (*padding2) (void);
+ void (*padding3) (void);
+ void (*padding4) (void);
+ void (*padding5) (void);
+ void (*padding6) (void);
+ void (*padding7) (void);
+ void (*padding8) (void);
+};
+
+GtkWidget *pnl_multi_paned_new (void);
+guint pnl_multi_paned_get_n_children (PnlMultiPaned *self);
+
+G_END_DECLS
+
+#endif /* PNL_MULTI_PANED_H */
diff --git a/contrib/pnl/pnl-tab-strip.c b/contrib/pnl/pnl-tab-strip.c
new file mode 100644
index 0000000..95d524e
--- /dev/null
+++ b/contrib/pnl/pnl-tab-strip.c
@@ -0,0 +1,538 @@
+/* pnl-tab-strip.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 "pnl-tab.h"
+#include "pnl-tab-strip.h"
+
+typedef struct
+{
+ GAction *action;
+ GtkStack *stack;
+ GtkPositionType edge : 2;
+} PnlTabStripPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (PnlTabStrip, pnl_tab_strip, GTK_TYPE_BOX)
+
+enum {
+ PROP_0,
+ PROP_EDGE,
+ PROP_STACK,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+set_tab_state (GSimpleAction *action,
+ GVariant *state,
+ gpointer user_data)
+{
+ PnlTabStrip *self = user_data;
+ PnlTabStripPrivate *priv = pnl_tab_strip_get_instance_private (self);
+ PnlTab *tab = NULL;
+ const GList *iter;
+ GList *list;
+ gint stateval;
+
+ g_assert (G_IS_SIMPLE_ACTION (action));
+ g_assert (PNL_IS_TAB_STRIP (self));
+ g_assert (state != NULL);
+ g_assert (g_variant_is_of_type (state, G_VARIANT_TYPE_INT32));
+
+ g_simple_action_set_state (action, state);
+
+ stateval = g_variant_get_int32 (state);
+
+ list = gtk_container_get_children (GTK_CONTAINER (priv->stack));
+
+ for (iter = list; iter != NULL; iter = iter->next)
+ {
+ GtkWidget *child = iter->data;
+ gint position = 0;
+
+ gtk_container_child_get (GTK_CONTAINER (priv->stack), GTK_WIDGET (child),
+ "position", &position,
+ NULL);
+
+ if (position == stateval)
+ {
+ tab = g_object_get_data (G_OBJECT (child), "PNL_TAB");
+ gtk_stack_set_visible_child (priv->stack, child);
+ break;
+ }
+ }
+
+ /*
+ * When clicking an active toggle button, we get the state callback but then
+ * the toggle button disables the checked state. So ensure it stays on by
+ * manually setting the state.
+ */
+ if (PNL_IS_TAB (tab))
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (tab), TRUE);
+
+ g_list_free (list);
+}
+
+static void
+pnl_tab_strip_add (GtkContainer *container,
+ GtkWidget *widget)
+{
+ PnlTabStrip *self = (PnlTabStrip *)container;
+ PnlTabStripPrivate *priv = pnl_tab_strip_get_instance_private (self);
+
+ g_assert (PNL_IS_TAB_STRIP (self));
+ g_assert (GTK_IS_WIDGET (widget));
+
+ if (PNL_IS_TAB (widget))
+ pnl_tab_set_edge (PNL_TAB (widget), priv->edge);
+
+ GTK_CONTAINER_CLASS (pnl_tab_strip_parent_class)->add (container, widget);
+}
+
+static void
+pnl_tab_strip_destroy (GtkWidget *widget)
+{
+ PnlTabStrip *self = (PnlTabStrip *)widget;
+ PnlTabStripPrivate *priv = pnl_tab_strip_get_instance_private (self);
+
+ g_assert (PNL_IS_TAB_STRIP (self));
+
+ pnl_tab_strip_set_stack (self, NULL);
+
+ g_clear_object (&priv->action);
+ g_clear_object (&priv->stack);
+
+ GTK_WIDGET_CLASS (pnl_tab_strip_parent_class)->destroy (widget);
+}
+
+static void
+pnl_tab_strip_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PnlTabStrip *self = PNL_TAB_STRIP (object);
+
+ switch (prop_id)
+ {
+ case PROP_EDGE:
+ g_value_set_enum (value, pnl_tab_strip_get_edge (self));
+ break;
+
+ case PROP_STACK:
+ g_value_set_object (value, pnl_tab_strip_get_stack (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pnl_tab_strip_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PnlTabStrip *self = PNL_TAB_STRIP (object);
+
+ switch (prop_id)
+ {
+ case PROP_EDGE:
+ pnl_tab_strip_set_edge (self, g_value_get_enum (value));
+ break;
+
+ case PROP_STACK:
+ pnl_tab_strip_set_stack (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pnl_tab_strip_class_init (PnlTabStripClass *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 = pnl_tab_strip_get_property;
+ object_class->set_property = pnl_tab_strip_set_property;
+
+ widget_class->destroy = pnl_tab_strip_destroy;
+
+ container_class->add = pnl_tab_strip_add;
+
+ properties [PROP_EDGE] =
+ g_param_spec_enum ("edge",
+ "Edge",
+ "The edge for the tab-strip",
+ GTK_TYPE_POSITION_TYPE,
+ GTK_POS_TOP,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_STACK] =
+ g_param_spec_object ("stack",
+ "Stack",
+ "The stack of items to manage.",
+ GTK_TYPE_STACK,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ gtk_widget_class_set_css_name (widget_class, "tabstrip");
+}
+
+static void
+pnl_tab_strip_init (PnlTabStrip *self)
+{
+ PnlTabStripPrivate *priv = pnl_tab_strip_get_instance_private (self);
+ GSimpleActionGroup *group;
+ static const GActionEntry entries[] = {
+ { "tab", NULL, "i", "0", set_tab_state },
+ };
+
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_HORIZONTAL);
+
+ group = g_simple_action_group_new ();
+ g_action_map_add_action_entries (G_ACTION_MAP (group), entries, G_N_ELEMENTS (entries), self);
+ priv->action = g_object_ref (g_action_map_lookup_action (G_ACTION_MAP (group), "tab"));
+ gtk_widget_insert_action_group (GTK_WIDGET (self), "tab-strip", G_ACTION_GROUP (group));
+ g_object_unref (group);
+
+ pnl_tab_strip_set_edge (self, GTK_POS_TOP);
+}
+
+static void
+pnl_tab_strip_child_position_changed (PnlTabStrip *self,
+ GParamSpec *pspec,
+ GtkWidget *child)
+{
+ GVariant *state;
+ GtkWidget *parent;
+ PnlTab *tab;
+ guint position;
+
+ g_assert (PNL_IS_TAB_STRIP (self));
+ g_assert (GTK_IS_WIDGET (child));
+
+ tab = g_object_get_data (G_OBJECT (child), "PNL_TAB");
+
+ if (!tab || !PNL_IS_TAB (tab))
+ return;
+
+ parent = gtk_widget_get_parent (child);
+
+ gtk_container_child_get (GTK_CONTAINER (parent), child,
+ "position", &position,
+ NULL);
+
+ gtk_container_child_set (GTK_CONTAINER (self), GTK_WIDGET (tab),
+ "position", position,
+ NULL);
+
+ state = g_variant_new_int32 (position);
+ gtk_actionable_set_action_target_value (GTK_ACTIONABLE (tab), state);
+}
+
+static void
+pnl_tab_strip_child_title_changed (PnlTabStrip *self,
+ GParamSpec *pspec,
+ GtkWidget *child)
+{
+ g_autofree gchar *title = NULL;
+ GtkWidget *parent;
+ PnlTab *tab;
+
+ g_assert (PNL_IS_TAB_STRIP (self));
+ g_assert (GTK_IS_WIDGET (child));
+
+ tab = g_object_get_data (G_OBJECT (child), "PNL_TAB");
+
+ if (!PNL_IS_TAB (tab))
+ return;
+
+ parent = gtk_widget_get_parent (child);
+
+ gtk_container_child_get (GTK_CONTAINER (parent), child,
+ "title", &title,
+ NULL);
+
+ pnl_tab_set_title (tab, title);
+}
+
+static void
+pnl_tab_strip_stack_notify_visible_child (PnlTabStrip *self,
+ GParamSpec *pspec,
+ GtkStack *stack)
+{
+ GtkWidget *visible;
+
+ g_assert (PNL_IS_TAB_STRIP (self));
+ g_assert (GTK_IS_STACK (stack));
+
+ visible = gtk_stack_get_visible_child (stack);
+
+ if (visible != NULL)
+ {
+ PnlTab *tab = g_object_get_data (G_OBJECT (visible), "PNL_TAB");
+
+ if (PNL_IS_TAB (tab))
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (tab), TRUE);
+ }
+}
+
+static void
+pnl_tab_strip_stack_add (PnlTabStrip *self,
+ GtkWidget *widget,
+ GtkStack *stack)
+{
+ PnlTabStripPrivate *priv = pnl_tab_strip_get_instance_private (self);
+ GVariant *target;
+ PnlTab *tab;
+ gint position = 0;
+
+ g_assert (PNL_IS_TAB_STRIP (self));
+ g_assert (GTK_IS_WIDGET (widget));
+ g_assert (GTK_IS_STACK (stack));
+
+ gtk_container_child_get (GTK_CONTAINER (stack), widget,
+ "position", &position,
+ NULL);
+
+ target = g_variant_new_int32 (position);
+
+ tab = g_object_new (PNL_TYPE_TAB,
+ "action-name", "tab-strip.tab",
+ "action-target", target,
+ "edge", priv->edge,
+ "widget", widget,
+ NULL);
+
+ g_object_set_data (G_OBJECT (widget), "PNL_TAB", tab);
+
+ g_signal_connect_object (widget,
+ "child-notify::position",
+ G_CALLBACK (pnl_tab_strip_child_position_changed),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (widget,
+ "child-notify::title",
+ G_CALLBACK (pnl_tab_strip_child_title_changed),
+ self,
+ G_CONNECT_SWAPPED);
+
+ gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (tab));
+
+ g_object_bind_property (widget, "visible", tab, "visible", G_BINDING_SYNC_CREATE);
+
+ pnl_tab_strip_child_title_changed (self, NULL, widget);
+ pnl_tab_strip_stack_notify_visible_child (self, NULL, stack);
+}
+
+static void
+pnl_tab_strip_stack_remove (PnlTabStrip *self,
+ GtkWidget *widget,
+ GtkStack *stack)
+{
+ PnlTab *tab;
+
+ g_assert (PNL_IS_TAB_STRIP (self));
+ g_assert (GTK_IS_WIDGET (widget));
+ g_assert (GTK_IS_STACK (stack));
+
+ tab = g_object_get_data (G_OBJECT (widget), "PNL_TAB");
+
+ if (PNL_IS_TAB (tab))
+ gtk_container_remove (GTK_CONTAINER (self), GTK_WIDGET (tab));
+}
+
+GtkWidget *
+pnl_tab_strip_new (void)
+{
+ return g_object_new (PNL_TYPE_TAB_STRIP, NULL);
+}
+
+/**
+ * pnl_tab_strip_get_stack:
+ *
+ * Returns: (transfer none) (nullable): A #GtkStack or %NULL.
+ */
+GtkStack *
+pnl_tab_strip_get_stack (PnlTabStrip *self)
+{
+ PnlTabStripPrivate *priv = pnl_tab_strip_get_instance_private (self);
+
+ g_return_val_if_fail (PNL_IS_TAB_STRIP (self), NULL);
+
+ return priv->stack;
+}
+
+static void
+pnl_tab_strip_cold_plug (GtkWidget *widget,
+ gpointer user_data)
+{
+ PnlTabStrip *self = user_data;
+ PnlTabStripPrivate *priv = pnl_tab_strip_get_instance_private (self);
+
+ g_assert (PNL_IS_TAB_STRIP (self));
+ g_assert (GTK_IS_WIDGET (widget));
+
+ pnl_tab_strip_stack_add (self, widget, priv->stack);
+}
+
+void
+pnl_tab_strip_set_stack (PnlTabStrip *self,
+ GtkStack *stack)
+{
+ PnlTabStripPrivate *priv = pnl_tab_strip_get_instance_private (self);
+
+ g_return_if_fail (PNL_IS_TAB_STRIP (self));
+ g_return_if_fail (!stack || GTK_IS_STACK (stack));
+
+ if (stack != priv->stack)
+ {
+ if (priv->stack != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (priv->stack,
+ G_CALLBACK (pnl_tab_strip_stack_notify_visible_child),
+ self);
+
+ g_signal_handlers_disconnect_by_func (priv->stack,
+ G_CALLBACK (pnl_tab_strip_stack_add),
+ self);
+
+ g_signal_handlers_disconnect_by_func (priv->stack,
+ G_CALLBACK (pnl_tab_strip_stack_remove),
+ self);
+
+ gtk_container_foreach (GTK_CONTAINER (self), (GtkCallback)gtk_widget_destroy, NULL);
+
+ g_clear_object (&priv->stack);
+ }
+
+ if (stack != NULL)
+ {
+ priv->stack = g_object_ref (stack);
+
+ g_signal_connect_object (priv->stack,
+ "notify::visible-child",
+ G_CALLBACK (pnl_tab_strip_stack_notify_visible_child),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (priv->stack,
+ "add",
+ G_CALLBACK (pnl_tab_strip_stack_add),
+ self,
+ G_CONNECT_SWAPPED);
+
+ g_signal_connect_object (priv->stack,
+ "remove",
+ G_CALLBACK (pnl_tab_strip_stack_remove),
+ self,
+ G_CONNECT_SWAPPED);
+
+ gtk_container_foreach (GTK_CONTAINER (priv->stack),
+ pnl_tab_strip_cold_plug,
+ self);
+ }
+ }
+}
+
+static void
+pnl_tab_strip_update_edge (GtkWidget *widget,
+ gpointer user_data)
+{
+ GtkPositionType edge = GPOINTER_TO_INT (user_data);
+
+ g_assert (GTK_IS_WIDGET (widget));
+
+ if (PNL_IS_TAB (widget))
+ pnl_tab_set_edge (PNL_TAB (widget), edge);
+}
+
+GtkPositionType
+pnl_tab_strip_get_edge (PnlTabStrip *self)
+{
+ PnlTabStripPrivate *priv = pnl_tab_strip_get_instance_private (self);
+
+ g_return_val_if_fail (PNL_IS_TAB_STRIP (self), 0);
+
+ return priv->edge;
+}
+
+void
+pnl_tab_strip_set_edge (PnlTabStrip *self,
+ GtkPositionType edge)
+{
+ PnlTabStripPrivate *priv = pnl_tab_strip_get_instance_private (self);
+
+ g_return_if_fail (PNL_IS_TAB_STRIP (self));
+ g_return_if_fail (edge >= 0);
+ g_return_if_fail (edge <= 3);
+
+ if (priv->edge != edge)
+ {
+ GtkStyleContext *style_context;
+ const gchar *class_name = NULL;
+
+ priv->edge = edge;
+
+ gtk_container_foreach (GTK_CONTAINER (self),
+ pnl_tab_strip_update_edge,
+ GINT_TO_POINTER (edge));
+
+ style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
+
+ gtk_style_context_remove_class (style_context, "left-edge");
+ gtk_style_context_remove_class (style_context, "top-edge");
+ gtk_style_context_remove_class (style_context, "right-edge");
+ gtk_style_context_remove_class (style_context, "bottom-edge");
+
+ switch (edge)
+ {
+ case GTK_POS_LEFT:
+ class_name = "left";
+ break;
+
+ case GTK_POS_RIGHT:
+ class_name = "right";
+ break;
+
+ case GTK_POS_TOP:
+ class_name = "top";
+ break;
+
+ case GTK_POS_BOTTOM:
+ class_name = "bottom";
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ gtk_style_context_add_class (style_context, class_name);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_EDGE]);
+ }
+}
diff --git a/contrib/pnl/pnl-tab-strip.h b/contrib/pnl/pnl-tab-strip.h
new file mode 100644
index 0000000..1aba919
--- /dev/null
+++ b/contrib/pnl/pnl-tab-strip.h
@@ -0,0 +1,53 @@
+/* pnl-tab-strip.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
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_TAB_STRIP_H
+#define PNL_TAB_STRIP_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define PNL_TYPE_TAB_STRIP (pnl_tab_strip_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (PnlTabStrip, pnl_tab_strip, PNL, TAB_STRIP, GtkBox)
+
+struct _PnlTabStripClass
+{
+ GtkBoxClass parent;
+};
+
+GtkWidget *pnl_tab_strip_new (void);
+GtkStack *pnl_tab_strip_get_stack (PnlTabStrip *self);
+void pnl_tab_strip_set_stack (PnlTabStrip *self,
+ GtkStack *stack);
+GtkPositionType pnl_tab_strip_get_edge (PnlTabStrip *self);
+void pnl_tab_strip_set_edge (PnlTabStrip *self,
+ GtkPositionType edge);
+gboolean pnl_tab_strip_get_show_labels (PnlTabStrip *self);
+void pnl_tab_strip_set_show_labels (PnlTabStrip *self,
+ gboolean show_labels);
+
+G_END_DECLS
+
+#endif /* PNL_TAB_STRIP_H */
diff --git a/contrib/pnl/pnl-tab.c b/contrib/pnl/pnl-tab.c
new file mode 100644
index 0000000..1340392
--- /dev/null
+++ b/contrib/pnl/pnl-tab.c
@@ -0,0 +1,275 @@
+/* pnl-tab.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 "pnl-tab.h"
+
+struct _PnlTab
+{
+ GtkToggleButton parent;
+ GtkPositionType edge : 2;
+ GtkLabel *title;
+ GtkWidget *widget;
+};
+
+G_DEFINE_TYPE (PnlTab, pnl_tab, GTK_TYPE_TOGGLE_BUTTON)
+
+enum {
+ PROP_0,
+ PROP_EDGE,
+ PROP_TITLE,
+ PROP_WIDGET,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+pnl_tab_destroy (GtkWidget *widget)
+{
+ PnlTab *self = (PnlTab *)widget;
+
+ if (self->widget)
+ {
+ g_object_remove_weak_pointer (G_OBJECT (self->widget), (gpointer *)&self->widget);
+ self->widget = NULL;
+ }
+
+ GTK_WIDGET_CLASS (pnl_tab_parent_class)->destroy (widget);
+}
+
+static void
+pnl_tab_update_edge (PnlTab *self)
+{
+ g_assert (PNL_IS_TAB (self));
+
+ switch (self->edge)
+ {
+ case GTK_POS_TOP:
+ gtk_label_set_angle (self->title, 0.0);
+ gtk_widget_set_hexpand (GTK_WIDGET (self), TRUE);
+ gtk_widget_set_vexpand (GTK_WIDGET (self), FALSE);
+ break;
+
+ case GTK_POS_BOTTOM:
+ gtk_label_set_angle (self->title, 0.0);
+ gtk_widget_set_hexpand (GTK_WIDGET (self), TRUE);
+ gtk_widget_set_vexpand (GTK_WIDGET (self), FALSE);
+ break;
+
+ case GTK_POS_LEFT:
+ gtk_label_set_angle (self->title, -90.0);
+ gtk_widget_set_hexpand (GTK_WIDGET (self), FALSE);
+ gtk_widget_set_vexpand (GTK_WIDGET (self), TRUE);
+ break;
+
+ case GTK_POS_RIGHT:
+ gtk_label_set_angle (self->title, 90.0);
+ gtk_widget_set_hexpand (GTK_WIDGET (self), FALSE);
+ gtk_widget_set_vexpand (GTK_WIDGET (self), TRUE);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+pnl_tab_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PnlTab *self = PNL_TAB (object);
+
+ switch (prop_id)
+ {
+ case PROP_EDGE:
+ g_value_set_enum (value, pnl_tab_get_edge (self));
+ break;
+
+ case PROP_TITLE:
+ g_value_set_string (value, pnl_tab_get_title (self));
+ break;
+
+ case PROP_WIDGET:
+ g_value_set_object (value, pnl_tab_get_widget (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pnl_tab_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PnlTab *self = PNL_TAB (object);
+
+ switch (prop_id)
+ {
+ case PROP_EDGE:
+ pnl_tab_set_edge (self, g_value_get_enum (value));
+ break;
+
+ case PROP_TITLE:
+ pnl_tab_set_title (self, g_value_get_string (value));
+ break;
+
+ case PROP_WIDGET:
+ pnl_tab_set_widget (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+pnl_tab_class_init (PnlTabClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->get_property = pnl_tab_get_property;
+ object_class->set_property = pnl_tab_set_property;
+
+ widget_class->destroy = pnl_tab_destroy;
+
+ gtk_widget_class_set_css_name (widget_class, "tab");
+
+ properties [PROP_EDGE] =
+ g_param_spec_enum ("edge",
+ "Edge",
+ "Edge",
+ GTK_TYPE_POSITION_TYPE,
+ GTK_POS_TOP,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_TITLE] =
+ g_param_spec_string ("title",
+ "Title",
+ "Title",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_WIDGET] =
+ g_param_spec_object ("widget",
+ "Widget",
+ "The widget the tab represents",
+ GTK_TYPE_WIDGET,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+pnl_tab_init (PnlTab *self)
+{
+ self->edge = GTK_POS_TOP;
+
+ gtk_widget_set_hexpand (GTK_WIDGET (self), TRUE);
+ gtk_widget_set_vexpand (GTK_WIDGET (self), FALSE);
+
+ self->title = g_object_new (GTK_TYPE_LABEL,
+ "ellipsize", PANGO_ELLIPSIZE_END,
+ "use-underline", TRUE,
+ "visible", TRUE,
+ NULL);
+
+ gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (self->title));
+}
+
+const gchar *
+pnl_tab_get_title (PnlTab *self)
+{
+ g_return_val_if_fail (PNL_IS_TAB (self), NULL);
+
+ return gtk_label_get_label (self->title);
+}
+
+void
+pnl_tab_set_title (PnlTab *self,
+ const gchar *title)
+{
+ g_return_if_fail (PNL_IS_TAB (self));
+
+ gtk_label_set_label (self->title, title);
+}
+
+GtkPositionType
+pnl_tab_get_edge (PnlTab *self)
+{
+ g_return_val_if_fail (PNL_IS_TAB (self), 0);
+
+ return self->edge;
+}
+
+void
+pnl_tab_set_edge (PnlTab *self,
+ GtkPositionType edge)
+{
+ g_return_if_fail (PNL_IS_TAB (self));
+ g_return_if_fail (edge >= 0);
+ g_return_if_fail (edge <= 3);
+
+ if (self->edge != edge)
+ {
+ self->edge = edge;
+ pnl_tab_update_edge (self);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_EDGE]);
+ }
+}
+
+/**
+ * pnl_tab_get_widget:
+ *
+ * Returns: (transfer none) (nullable): A #GtkWidget or %NULL.
+ */
+GtkWidget *
+pnl_tab_get_widget (PnlTab *self)
+{
+ g_return_val_if_fail (PNL_IS_TAB (self), NULL);
+
+ return self->widget;
+}
+
+void
+pnl_tab_set_widget (PnlTab *self,
+ GtkWidget *widget)
+{
+ g_return_if_fail (PNL_IS_TAB (self));
+
+ if (self->widget != widget)
+ {
+ if (self->widget)
+ g_object_remove_weak_pointer (G_OBJECT (self->widget), (gpointer *)&self->widget);
+
+ self->widget = widget;
+
+ if (widget)
+ g_object_add_weak_pointer (G_OBJECT (self->widget), (gpointer *)&self->widget);
+
+ gtk_label_set_mnemonic_widget (self->title, widget);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_WIDGET]);
+ }
+}
diff --git a/contrib/pnl/pnl-tab.h b/contrib/pnl/pnl-tab.h
new file mode 100644
index 0000000..a59bfd5
--- /dev/null
+++ b/contrib/pnl/pnl-tab.h
@@ -0,0 +1,47 @@
+/* pnl-tab.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
+ */
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+#ifndef PNL_TAB_H
+#define PNL_TAB_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define PNL_TYPE_TAB (pnl_tab_get_type())
+
+G_DECLARE_FINAL_TYPE (PnlTab, pnl_tab, PNL, TAB, GtkToggleButton)
+
+const gchar *pnl_tab_get_title (PnlTab *self);
+void pnl_tab_set_title (PnlTab *self,
+ const gchar *title);
+GtkPositionType pnl_tab_get_edge (PnlTab *self);
+void pnl_tab_set_edge (PnlTab *self,
+ GtkPositionType edge);
+GtkWidget *pnl_tab_get_widget (PnlTab *self);
+void pnl_tab_set_widget (PnlTab *self,
+ GtkWidget *widget);
+
+G_END_DECLS
+
+#endif /* PNL_TAB_H */
diff --git a/contrib/pnl/pnl-util-private.h b/contrib/pnl/pnl-util-private.h
new file mode 100644
index 0000000..36e6845
--- /dev/null
+++ b/contrib/pnl/pnl-util-private.h
@@ -0,0 +1,39 @@
+/* pnl-util-private.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PNL_UTIL_PRIVATE_H
+#define PNL_UTIL_PRIVATE_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define pnl_clear_weak_pointer(ptr) \
+ (*(ptr) ? (g_object_remove_weak_pointer((GObject*)*(ptr), (gpointer*)ptr),*(ptr)=NULL,1) : 0)
+
+#define pnl_set_weak_pointer(ptr,obj) \
+
((obj!=*(ptr))?(pnl_clear_weak_pointer(ptr),*(ptr)=obj,((obj)?g_object_add_weak_pointer((GObject*)obj,(gpointer*)ptr),NULL:NULL),1):0)
+
+gboolean pnl_gtk_bin_draw (GtkWidget *widget,
+ cairo_t *cr);
+void pnl_gtk_bin_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation);
+
+G_END_DECLS
+
+#endif /* PNL_UTIL_PRIVATE_H */
diff --git a/contrib/pnl/pnl-util.c b/contrib/pnl/pnl-util.c
new file mode 100644
index 0000000..5105be3
--- /dev/null
+++ b/contrib/pnl/pnl-util.c
@@ -0,0 +1,109 @@
+/* pnl-util.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pnl-util-private.h"
+
+static void
+pnl_gtk_border_sum (GtkBorder *one,
+ const GtkBorder *two)
+{
+ one->top += two->top;
+ one->right += two->right;
+ one->bottom += two->bottom;
+ one->left += two->left;
+}
+
+gboolean
+pnl_gtk_bin_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ GtkStyleContext *style_context;
+ GtkStateFlags state;
+ GtkAllocation alloc;
+ GtkBorder border;
+ GtkBorder padding;
+ GtkWidget *child;
+
+ g_assert (GTK_IS_WIDGET (widget));
+ g_assert (cr != NULL);
+
+ gtk_widget_get_allocation (widget, &alloc);
+
+ style_context = gtk_widget_get_style_context (widget);
+ state = gtk_style_context_get_state (style_context);
+ gtk_style_context_get_border (style_context, state, &border);
+ gtk_style_context_get_padding (style_context, state, &padding);
+
+ pnl_gtk_border_sum (&border, &padding);
+
+ gtk_render_background (gtk_widget_get_style_context (widget), cr,
+ border.left,
+ border.top,
+ alloc.width - border.left - border.right,
+ alloc.height - border.top - border.bottom);
+
+ child = gtk_bin_get_child (GTK_BIN (widget));
+
+ if (child != NULL)
+ gtk_container_propagate_draw (GTK_CONTAINER (widget), child, cr);
+
+ return GDK_EVENT_PROPAGATE;
+}
+
+void
+pnl_gtk_bin_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GtkStyleContext *style_context;
+ GtkStateFlags state;
+ GtkBorder border;
+ GtkBorder padding;
+ gint border_width;
+ GtkWidget *child;
+
+ g_return_if_fail (GTK_IS_BIN (widget));
+ g_return_if_fail (allocation != NULL);
+
+ gtk_widget_set_allocation (widget, allocation);
+
+ child = gtk_bin_get_child (GTK_BIN (widget));
+
+ if (child == NULL)
+ return;
+
+ style_context = gtk_widget_get_style_context (widget);
+ state = gtk_style_context_get_state (style_context);
+ gtk_style_context_get_border (style_context, state, &border);
+ gtk_style_context_get_padding (style_context, state, &padding);
+
+ pnl_gtk_border_sum (&border, &padding);
+
+ allocation->x += border.left;
+ allocation->y += border.top;
+ allocation->width -= border.left + border.right;
+ allocation->height -= border.top + border.bottom;
+
+ border_width = gtk_container_get_border_width (GTK_CONTAINER (widget));
+
+ allocation->x += border_width;
+ allocation->y += border_width;
+ allocation->width -= border_width * 2;
+ allocation->height -= border_width * 2;
+
+ gtk_widget_size_allocate (child, allocation);
+}
diff --git a/contrib/pnl/pnl-version.h.in b/contrib/pnl/pnl-version.h.in
new file mode 100644
index 0000000..a11acaa
--- /dev/null
+++ b/contrib/pnl/pnl-version.h.in
@@ -0,0 +1,97 @@
+/* pnl-version.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PNL_VERSION_H
+#define PNL_VERSION_H
+
+#if !defined(PNL_INSIDE) && !defined(PNL_COMPILATION)
+# error "Only <pnl.h> can be included directly."
+#endif
+
+/**
+ * SECTION:pnl-version
+ * @short_description: pnl version checking
+ *
+ * pnl provides macros to check the version of the library
+ * at compile-time
+ */
+
+/**
+ * PNL_MAJOR_VERSION:
+ *
+ * pnl major version component (e.g. 1 if %PNL_VERSION is 1.2.3)
+ */
+#define PNL_MAJOR_VERSION (@MAJOR_VERSION@)
+
+/**
+ * PNL_MINOR_VERSION:
+ *
+ * pnl minor version component (e.g. 2 if %PNL_VERSION is 1.2.3)
+ */
+#define PNL_MINOR_VERSION (@MINOR_VERSION@)
+
+/**
+ * PNL_MICRO_VERSION:
+ *
+ * pnl micro version component (e.g. 3 if %PNL_VERSION is 1.2.3)
+ */
+#define PNL_MICRO_VERSION (@MICRO_VERSION@)
+
+/**
+ * PNL_VERSION
+ *
+ * pnl version.
+ */
+#define PNL_VERSION (@VERSION@)
+
+/**
+ * PNL_VERSION_S:
+ *
+ * pnl version, encoded as a string, useful for printing and
+ * concatenation.
+ */
+#define PNL_VERSION_S "@VERSION@"
+
+#define PNL_ENCODE_VERSION(major,minor,micro) \
+ ((major) << 24 | (minor) << 16 | (micro) << 8)
+
+/**
+ * PNL_VERSION_HEX:
+ *
+ * pnl version, encoded as an hexadecimal number, useful for
+ * integer comparisons.
+ */
+#define PNL_VERSION_HEX \
+ (PNL_ENCODE_VERSION (PNL_MAJOR_VERSION, PNL_MINOR_VERSION, PNL_MICRO_VERSION))
+
+/**
+ * PNL_CHECK_VERSION:
+ * @major: required major version
+ * @minor: required minor version
+ * @micro: required micro version
+ *
+ * Compile-time version checking. Evaluates to %TRUE if the version
+ * of pnl is greater than the required one.
+ */
+#define PNL_CHECK_VERSION(major,minor,micro) \
+ (PNL_MAJOR_VERSION > (major) || \
+ (PNL_MAJOR_VERSION == (major) && PNL_MINOR_VERSION > (minor)) || \
+ (PNL_MAJOR_VERSION == (major) && PNL_MINOR_VERSION == (minor) && \
+ PNL_MICRO_VERSION >= (micro)))
+
+#endif /* PNL_VERSION_H */
diff --git a/contrib/pnl/pnl.gresource.xml b/contrib/pnl/pnl.gresource.xml
new file mode 100644
index 0000000..676d36f
--- /dev/null
+++ b/contrib/pnl/pnl.gresource.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/panel-gtk/icons/16x16/actions/">
+ <file compressed="true"
alias="panel-bottom-pane-symbolic.svg">resources/panel-bottom-pane-symbolic.svg</file>
+ <file compressed="true"
alias="panel-left-pane-symbolic.svg">resources/panel-left-pane-symbolic.svg</file>
+ <file compressed="true"
alias="panel-right-pane-symbolic.svg">resources/panel-right-pane-symbolic.svg</file>
+ </gresource>
+</gresources>
diff --git a/contrib/pnl/pnl.h b/contrib/pnl/pnl.h
new file mode 100644
index 0000000..25ee5ef
--- /dev/null
+++ b/contrib/pnl/pnl.h
@@ -0,0 +1,52 @@
+/* pnl.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 General Public License as published by
+ * the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PNL_H
+#define PNL_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define PNL_INSIDE
+
+#include "pnl-dock.h"
+#include "pnl-dock-bin.h"
+#include "pnl-dock-bin-edge.h"
+#include "pnl-dock-item.h"
+#include "pnl-dock-manager.h"
+#include "pnl-dock-overlay.h"
+#include "pnl-dock-paned.h"
+#include "pnl-dock-revealer.h"
+#include "pnl-dock-stack.h"
+#include "pnl-dock-tab-strip.h"
+#include "pnl-dock-types.h"
+#include "pnl-dock-widget.h"
+#include "pnl-dock-window.h"
+
+#include "pnl-version.h"
+
+#include "pnl-tab.h"
+#include "pnl-tab-strip.h"
+#include "pnl-multi-paned.h"
+
+#undef PNL_INSIDE
+
+G_END_DECLS
+
+#endif /* PNL_H */
diff --git a/contrib/pnl/resources/panel-bottom-pane-symbolic.svg
b/contrib/pnl/resources/panel-bottom-pane-symbolic.svg
new file mode 100644
index 0000000..a13bb63
--- /dev/null
+++ b/contrib/pnl/resources/panel-bottom-pane-symbolic.svg
@@ -0,0 +1,26 @@
+<?xml version='1.0' encoding='UTF-8' standalone='no'?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:dc='http://purl.org/dc/elements/1.1/'
sodipodi:docname='builder-view-bottom-pane-symbolic.svg' height='16' id='svg7384'
xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape'
xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:svg='http://www.w3.org/2000/svg'
version='1.1' inkscape:version='0.91 r13725' width='16' xmlns='http://www.w3.org/2000/svg'>
+ <metadata id='metadata90'>
+ <rdf:RDF>
+ <cc:Work rdf:about=''>
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/>
+ <dc:title>Gnome Symbolic Icon Theme</dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview inkscape:bbox-nodes='true' inkscape:bbox-paths='true' bordercolor='#666666'
borderopacity='1' inkscape:current-layer='layer2' inkscape:cx='-22.41638' inkscape:cy='18.171243'
gridtolerance='10' inkscape:guide-bbox='true' guidetolerance='10' id='namedview88'
inkscape:object-nodes='false' inkscape:object-paths='false' objecttolerance='10' pagecolor='#555753'
inkscape:pageopacity='1' inkscape:pageshadow='2' showborder='false' showgrid='false' showguides='true'
inkscape:snap-bbox='true' inkscape:snap-bbox-midpoints='false' inkscape:snap-global='true'
inkscape:snap-grids='true' inkscape:snap-nodes='false' inkscape:snap-others='false'
inkscape:snap-to-guides='true' inkscape:window-height='1376' inkscape:window-maximized='1'
inkscape:window-width='2560' inkscape:window-x='0' inkscape:window-y='27' inkscape:zoom='1'>
+ <inkscape:grid empspacing='2' enabled='true' id='grid4866' originx='80.00005' originy='-72.98918'
snapvisiblegridlinesonly='true' spacingx='1px' spacingy='1px' type='xygrid' visible='true'/>
+ </sodipodi:namedview>
+ <title id='title9167'>Gnome Symbolic Icon Theme</title>
+ <defs id='defs7386'/>
+ <g inkscape:groupmode='layer' id='layer2' inkscape:label='actions' style='display:inline'
transform='translate(80.00005,72.98918)'>
+
+ <path inkscape:connector-curvature='0' d='m -79,-72 0,0.5 0,13.5 14,0 0,-14 -14,0 z m 1,1 12,0 0,12
-12,0 0,-12 z' id='rect8495'
style='color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.99999994;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;str
oke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate'/>
+ <rect height='2.032932' id='rect8512'
style='color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate'
width='10.032078' x='-77.050407' y='-62.002731'/>
+ </g>
+ <g inkscape:groupmode='layer' id='layer9' inkscape:label='apps' style='display:inline'
transform='translate(-161.00015,-144.01082)'/>
+ <g inkscape:groupmode='layer' id='layer1' inkscape:label='autocomplete'
transform='translate(80.00005,72.98918)'/>
+</svg>
diff --git a/contrib/pnl/resources/panel-left-pane-symbolic.svg
b/contrib/pnl/resources/panel-left-pane-symbolic.svg
new file mode 100644
index 0000000..116173d
--- /dev/null
+++ b/contrib/pnl/resources/panel-left-pane-symbolic.svg
@@ -0,0 +1,26 @@
+<?xml version='1.0' encoding='UTF-8' standalone='no'?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:dc='http://purl.org/dc/elements/1.1/'
sodipodi:docname='builder-view-left-pane-symbolic.svg' height='16' id='svg7384'
xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape'
xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:svg='http://www.w3.org/2000/svg'
version='1.1' inkscape:version='0.91 r13725' width='16' xmlns='http://www.w3.org/2000/svg'>
+ <metadata id='metadata90'>
+ <rdf:RDF>
+ <cc:Work rdf:about=''>
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/>
+ <dc:title>Gnome Symbolic Icon Theme</dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview inkscape:bbox-nodes='true' inkscape:bbox-paths='true' bordercolor='#666666'
borderopacity='1' inkscape:current-layer='layer2' inkscape:cx='-2.41639' inkscape:cy='18.171245'
gridtolerance='10' inkscape:guide-bbox='true' guidetolerance='10' id='namedview88'
inkscape:object-nodes='false' inkscape:object-paths='false' objecttolerance='10' pagecolor='#555753'
inkscape:pageopacity='1' inkscape:pageshadow='2' showborder='false' showgrid='false' showguides='true'
inkscape:snap-bbox='true' inkscape:snap-bbox-midpoints='false' inkscape:snap-global='true'
inkscape:snap-grids='true' inkscape:snap-nodes='false' inkscape:snap-others='false'
inkscape:snap-to-guides='true' inkscape:window-height='1376' inkscape:window-maximized='1'
inkscape:window-width='2560' inkscape:window-x='0' inkscape:window-y='27' inkscape:zoom='1'>
+ <inkscape:grid empspacing='2' enabled='true' id='grid4866' originx='100.00004' originy='-72.989178'
snapvisiblegridlinesonly='true' spacingx='1px' spacingy='1px' type='xygrid' visible='true'/>
+ </sodipodi:namedview>
+ <title id='title9167'>Gnome Symbolic Icon Theme</title>
+ <defs id='defs7386'/>
+ <g inkscape:groupmode='layer' id='layer2' inkscape:label='actions' style='display:inline'
transform='translate(100.00004,72.989178)'>
+
+ <path inkscape:connector-curvature='0' d='m -84.988281,-71.988281 -0.5,0 -13.5,0 0,14 14,0 0,-14 z m
-1,1 0,12 -12,0 0,-12 12,0 z' id='rect8530'
style='color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.99999994;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-da
sharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate'/>
+ <rect height='2.032932' id='rect8532'
style='color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate'
transform='matrix(0,1,-1,0,0,0)' width='10.032078' x='-70.039536' y='94.986488'/>
+ </g>
+ <g inkscape:groupmode='layer' id='layer9' inkscape:label='apps' style='display:inline'
transform='translate(-141.00016,-144.01082)'/>
+ <g inkscape:groupmode='layer' id='layer1' inkscape:label='autocomplete'
transform='translate(100.00004,72.989178)'/>
+</svg>
diff --git a/contrib/pnl/resources/panel-right-pane-symbolic.svg
b/contrib/pnl/resources/panel-right-pane-symbolic.svg
new file mode 100644
index 0000000..1c099a7
--- /dev/null
+++ b/contrib/pnl/resources/panel-right-pane-symbolic.svg
@@ -0,0 +1,26 @@
+<?xml version='1.0' encoding='UTF-8' standalone='no'?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:dc='http://purl.org/dc/elements/1.1/'
sodipodi:docname='builder-view-right-pane-symbolic.svg' height='16' id='svg7384'
xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape'
xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:svg='http://www.w3.org/2000/svg'
version='1.1' inkscape:version='0.91 r13725' width='16' xmlns='http://www.w3.org/2000/svg'>
+ <metadata id='metadata90'>
+ <rdf:RDF>
+ <cc:Work rdf:about=''>
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/>
+ <dc:title>Gnome Symbolic Icon Theme</dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview inkscape:bbox-nodes='true' inkscape:bbox-paths='true' bordercolor='#666666'
borderopacity='1' inkscape:current-layer='layer2' inkscape:cx='-42.41638' inkscape:cy='18.171247'
gridtolerance='10' inkscape:guide-bbox='true' guidetolerance='10' id='namedview88'
inkscape:object-nodes='false' inkscape:object-paths='false' objecttolerance='10' pagecolor='#555753'
inkscape:pageopacity='1' inkscape:pageshadow='2' showborder='false' showgrid='false' showguides='true'
inkscape:snap-bbox='true' inkscape:snap-bbox-midpoints='false' inkscape:snap-global='true'
inkscape:snap-grids='true' inkscape:snap-nodes='false' inkscape:snap-others='false'
inkscape:snap-to-guides='true' inkscape:window-height='1376' inkscape:window-maximized='1'
inkscape:window-width='2560' inkscape:window-x='0' inkscape:window-y='27' inkscape:zoom='1'>
+ <inkscape:grid empspacing='2' enabled='true' id='grid4866' originx='60.00005' originy='-72.989176'
snapvisiblegridlinesonly='true' spacingx='1px' spacingy='1px' type='xygrid' visible='true'/>
+ </sodipodi:namedview>
+ <title id='title9167'>Gnome Symbolic Icon Theme</title>
+ <defs id='defs7386'/>
+ <g inkscape:groupmode='layer' id='layer2' inkscape:label='actions' style='display:inline'
transform='translate(60.00005,72.989176)'>
+
+ <path inkscape:connector-curvature='0' d='m -59.011719,-57.988281 0.5,0 13.5,0 0,-14 -14,0 0,14 z m 1,-1
0,-12 12,0 0,12 -12,0 z' id='rect8520'
style='color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.99999994;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-das
harray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate'/>
+ <rect height='2.032932' id='rect8522'
style='color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate'
transform='matrix(0,-1,1,0,0,0)' width='10.032078' x='59.93882' y='-49.013599'/>
+ </g>
+ <g inkscape:groupmode='layer' id='layer9' inkscape:label='apps' style='display:inline'
transform='translate(-181.00015,-144.01082)'/>
+ <g inkscape:groupmode='layer' id='layer1' inkscape:label='autocomplete'
transform='translate(60.00005,72.989176)'/>
+</svg>
diff --git a/data/libide-1.0.pc.in b/data/libide-1.0.pc.in
index 0917ffc..ec6bfae 100644
--- a/data/libide-1.0.pc.in
+++ b/data/libide-1.0.pc.in
@@ -6,6 +6,6 @@ includedir=${exec_prefix}/include
Name: LibIDE
Description: LibIDE contains the components used to build the GNOME Builder IDE.
Version: @VERSION@
-Libs: -L${libdir}/gnome-builder -lide-1.0 -legg-private -Ltemplate-glib-1.0
-Cflags: -I${includedir}/gnome-builder- VERSION@/libide -I${includedir}/gnome-builder- VERSION@/egg
-I${includedir}/gnome-builder- VERSION@/template-glib
+Libs: -L${libdir}/gnome-builder -lide-1.0 -legg-private -Ltemplate-glib-1.0 -lpanel-gtk
+Cflags: -I${includedir}/gnome-builder- VERSION@/libide -I${includedir}/gnome-builder- VERSION@/egg
-I${includedir}/gnome-builder- VERSION@/template-glib -I${includedir}gnome-builder- VERSION@/pnl
Requires: gio-2.0 gtk+-3.0 gtksourceview-3.0
diff --git a/src/Makefile.am b/src/Makefile.am
index b2480e2..1e4e25a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -12,11 +12,14 @@ AM_CPPFLAGS = \
-I$(top_builddir)/data/icons/hicolor \
-I$(top_builddir)/libide \
-I$(top_srcdir)/libide \
+ -I$(top_srcdir)/contrib/pnl \
+ -I$(top_builddir)/contrib/pnl \
$(NULL)
gnome_builder_libs = \
$(LIBIDE_LIBS) \
$(top_builddir)/contrib/egg/libegg-private.la \
+ $(top_builddir)/contrib/pnl/libpanel-gtk.la \
$(top_builddir)/data/icons/hicolor/libicons.la \
$(top_builddir)/libide/libide-1.0.la \
$(NULL)
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 19097ad..a09c4f7 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -44,6 +44,8 @@ tests_cflags = \
$(LIBIDE_CFLAGS) \
-I$(top_srcdir)/libide \
-I$(top_builddir)/libide \
+ -I$(top_srcdir)/contrib/pnl \
+ -I$(top_builddir)/contrib/pnl \
$(NULL)
tests_libs = \
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]