[gnome-builder/wip/panel: 16/16] wip: panel-gtk
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/panel: 16/16] wip: panel-gtk
- Date: Mon, 21 Mar 2016 00:35:22 +0000 (UTC)
commit 6020295039f9d3c9f1da3c837f1da8efde97d6f6
Author: Christian Hergert <christian hergert me>
Date: Sun Mar 20 17:35:07 2016 -0700
wip: panel-gtk
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 | 226 +++
contrib/pnl/pnl-dock-bin-edge.h | 37 +
contrib/pnl/pnl-dock-bin.c | 1805 +++++++++++++++++++
contrib/pnl/pnl-dock-bin.h | 46 +
contrib/pnl/pnl-dock-item.c | 456 +++++
contrib/pnl/pnl-dock-item.h | 65 +
contrib/pnl/pnl-dock-manager.c | 295 ++++
contrib/pnl/pnl-dock-manager.h | 48 +
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 | 41 +
contrib/pnl/pnl-dock-paned-private.h | 31 +
contrib/pnl/pnl-dock-paned.c | 134 ++
contrib/pnl/pnl-dock-paned.h | 39 +
contrib/pnl/pnl-dock-revealer.c | 779 ++++++++
contrib/pnl/pnl-dock-revealer.h | 67 +
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 | 319 ++++
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 | 42 +
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 | 50 +
contrib/pnl/pnl-tab-strip.c | 528 ++++++
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 +-
data/ui/ide-editor-perspective.ui | 104 +-
data/ui/ide-layout-pane.ui | 3 +-
data/ui/ide-layout.ui | 30 -
doc/reference/libide/libide-sections.txt | 4 -
libide/Makefile.am | 8 +
libide/editor/ide-editor-perspective.c | 55 +-
libide/ide-application-actions.c | 6 +-
libide/ide-layout-pane.c | 211 +---
libide/ide-layout-pane.h | 5 +-
libide/ide-layout.c | 1375 +--------------
libide/ide-layout.h | 15 +-
libide/ide-workbench.c | 6 +-
libide/resources/libide.gresource.xml | 1 -
plugins/build-tools/gbp-build-workbench-addin.c | 6 +-
plugins/devhelp/gbp-devhelp-editor-view-addin.c | 2 +-
plugins/devhelp/gbp-devhelp-search-provider.c | 2 +-
plugins/devhelp/gbp-devhelp-workbench-addin.c | 4 +-
plugins/project-tree/gb-project-tree-addin.c | 8 +-
plugins/symbol-tree/symbol-tree.c | 4 +-
plugins/sysmon/gb-sysmon-addin.c | 2 +-
plugins/terminal/gb-terminal-workbench-addin.c | 2 +-
plugins/todo/todo_plugin/__init__.py | 2 +-
src/Makefile.am | 3 +
tests/Makefile.am | 2 +
78 files changed, 11339 insertions(+), 1739 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 9188bcd..e782fb2 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..06acd45
--- /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,
+ LAST_PROP
+};
+
+
+enum {
+ TICK,
+ LAST_SIGNAL
+};
+
+
+/*
+ * 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[LAST_PROP];
+static guint signals[LAST_SIGNAL];
+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, LAST_PROP, 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..0512607
--- /dev/null
+++ b/contrib/pnl/pnl-dock-bin-edge.c
@@ -0,0 +1,226 @@
+/* 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-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,
+ LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+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_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);
+
+ 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;
+
+ 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, LAST_PROP, properties);
+
+ 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..7153ff2
--- /dev/null
+++ b/contrib/pnl/pnl-dock-bin-edge.h
@@ -0,0 +1,37 @@
+/* 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;
+};
+
+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..68c1e7c
--- /dev/null
+++ b/contrib/pnl/pnl-dock-bin.c
@@ -0,0 +1,1805 @@
+/* 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 type of child. The PNL_DOCK_BIN_CHILD_CENTER is always
+ * the last child, and our sort function ensures that.
+ */
+ PnlDockBinChildType type;
+
+ /*
+ * 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;
+
+ /*
+ * If we animated in this panel during DnD, we want to restore
+ * it unless we dragged onto this panel.
+ */
+ guint hide_after_dnd : 1;
+} 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.
+ */
+ guint in_dnd : 1;
+ 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,
+ LAST_PROP
+};
+
+enum {
+ CHILD_PROP_0,
+ CHILD_PROP_POSITION,
+ CHILD_PROP_PRIORITY,
+ LAST_CHILD_PROP
+};
+
+static GParamSpec *child_properties [LAST_CHILD_PROP];
+
+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;
+
+ 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;
+
+ /*
+ * 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);
+ *nat_width = (child_nat_width + neighbor_nat_width);
+ break;
+
+ case PNL_DOCK_BIN_CHILD_TOP:
+ case PNL_DOCK_BIN_CHILD_BOTTOM:
+ *min_width = MAX (child_min_width, neighbor_min_width);
+ *nat_width = MAX (child_nat_width, neighbor_nat_width);
+ 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;
+
+ 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;
+
+ /*
+ * 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);
+ *nat_height = MAX (child_nat_height, neighbor_nat_height);
+ break;
+
+ case PNL_DOCK_BIN_CHILD_TOP:
+ case PNL_DOCK_BIN_CHILD_BOTTOM:
+ *min_height = (child_min_height + neighbor_min_height);
+ *nat_height = (child_nat_height + neighbor_nat_height);
+ 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;
+
+ 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;
+ }
+
+ 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;
+ allocation->x += child_alloc.width;
+ allocation->width -= child_alloc.width;
+ break;
+
+ case PNL_DOCK_BIN_CHILD_RIGHT:
+ 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;
+ break;
+
+ case PNL_DOCK_BIN_CHILD_TOP:
+ child_alloc.x = allocation->x;
+ child_alloc.y = allocation->y;
+ child_alloc.width = allocation->width;
+ allocation->y += child_alloc.height;
+ allocation->height -= child_alloc.height;
+ break;
+
+ case PNL_DOCK_BIN_CHILD_BOTTOM:
+ 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;
+ 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 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->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, LAST_CHILD_PROP, child_properties);
+
+ 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..352b3a6
--- /dev/null
+++ b/contrib/pnl/pnl-dock-bin.h
@@ -0,0 +1,46 @@
+/* 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);
+};
+
+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..25fc24f
--- /dev/null
+++ b/contrib/pnl/pnl-dock-manager.c
@@ -0,0 +1,295 @@
+/* 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;
+ GPtrArray *toplevels;
+ 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_watch_toplevel (PnlDockManager *self,
+ GtkWidget *widget)
+{
+ PnlDockManagerPrivate *priv = pnl_dock_manager_get_instance_private (self);
+ GtkWidget *toplevel;
+ guint i;
+
+ g_assert (PNL_IS_DOCK_MANAGER (self));
+ g_assert (GTK_IS_WIDGET (widget));
+
+ toplevel = gtk_widget_get_toplevel (widget);
+
+ for (i = 0; i < priv->toplevels->len; i++)
+ {
+ GtkWidget *iter = g_ptr_array_index (priv->toplevels, i);
+
+ if (iter == toplevel)
+ return;
+ }
+
+ 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_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 ();
+ priv->toplevels = 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..8e40cf7
--- /dev/null
+++ b/contrib/pnl/pnl-dock-manager.h
@@ -0,0 +1,48 @@
+/* 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);
+};
+
+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..3856bd8
--- /dev/null
+++ b/contrib/pnl/pnl-dock-overlay.h
@@ -0,0 +1,41 @@
+/* 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);
+};
+
+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..86e8028
--- /dev/null
+++ b/contrib/pnl/pnl-dock-paned.h
@@ -0,0 +1,39 @@
+/* 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;
+};
+
+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..2597024
--- /dev/null
+++ b/contrib/pnl/pnl-dock-revealer.h
@@ -0,0 +1,67 @@
+/* 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;
+};
+
+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..2e7b908
--- /dev/null
+++ b/contrib/pnl/pnl-dock-transient-grab.c
@@ -0,0 +1,319 @@
+/* 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)
+ {
+ PnlDockItem *item = g_ptr_array_index (self->items, 0);
+
+ return gtk_widget_is_ancestor (widget, GTK_WIDGET (item));
+ }
+
+ 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..eb6d890
--- /dev/null
+++ b/contrib/pnl/pnl-dock-widget.h
@@ -0,0 +1,42 @@
+/* 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;
+};
+
+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..ef5efd2
--- /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,
+ LAST_PROP
+};
+
+enum {
+ CHILD_PROP_0,
+ CHILD_PROP_POSITION,
+ LAST_CHILD_PROP
+};
+
+enum {
+ STYLE_PROP_0,
+ STYLE_PROP_HANDLE_SIZE,
+ LAST_STYLE_PROP
+};
+
+enum {
+ RESIZE_DRAG_BEGIN,
+ RESIZE_DRAG_END,
+ LAST_SIGNAL
+};
+
+/*
+ * TODO: An obvious optimization here would be to move the constant
+ * branches outside the loops.
+ */
+
+static GParamSpec *properties [LAST_PROP];
+static GParamSpec *child_properties [LAST_CHILD_PROP];
+static GParamSpec *style_properties [LAST_STYLE_PROP];
+static guint signals [LAST_SIGNAL];
+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, LAST_PROP, 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, LAST_CHILD_PROP, 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..dac83f3
--- /dev/null
+++ b/contrib/pnl/pnl-multi-paned.h
@@ -0,0 +1,50 @@
+/* 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);
+};
+
+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..09b59e4
--- /dev/null
+++ b/contrib/pnl/pnl-tab-strip.c
@@ -0,0 +1,528 @@
+/* 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);
+ 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)
+ {
+ gtk_stack_set_visible_child (priv->stack, child);
+ break;
+ }
+ }
+
+ 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/data/ui/ide-editor-perspective.ui b/data/ui/ide-editor-perspective.ui
index ceda61d..55bb06b 100644
--- a/data/ui/ide-editor-perspective.ui
+++ b/data/ui/ide-editor-perspective.ui
@@ -2,69 +2,61 @@
<interface>
<!-- interface-requires gtk+ 3.18 -->
<template class="IdeEditorPerspective" parent="IdeLayout">
- <child internal-child="content_pane">
- <object class="IdeLayoutPane">
- <child internal-child="header">
+ <child type="center">
+ <object class="GtkStack" id="content_stack">
+ <property name="visible">true</property>
+ <child>
<object class="GtkBox">
- <property name="visible">false</property>
- </object>
- </child>
- <child internal-child="stack">
- <object class="GtkStack" id="content_stack">
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <property name="visible">true</property>
+ <child>
+ <object class="GtkImage">
+ <property name="icon-name">text-editor-symbolic</property>
+ <property name="pixel-size">128</property>
+ <property name="visible">true</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
<child>
- <object class="GtkBox">
- <property name="halign">center</property>
- <property name="valign">center</property>
- <property name="orientation">vertical</property>
- <property name="spacing">12</property>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">No open files</property>
<property name="visible">true</property>
- <child>
- <object class="GtkImage">
- <property name="icon-name">text-editor-symbolic</property>
- <property name="pixel-size">128</property>
- <property name="visible">true</property>
- <style>
- <class name="dim-label"/>
- </style>
- </object>
- </child>
- <child>
- <object class="GtkLabel">
- <property name="label" translatable="yes">No open files</property>
- <property name="visible">true</property>
- <style>
- <class name="dim-label"/>
- </style>
- <attributes>
- <attribute name="scale" value="2.0"/>
- <attribute name="weight" value="bold"/>
- </attributes>
- </object>
- </child>
- <child>
- <object class="GtkLabel">
- <property name="label" translatable="yes">Try opening a file by typing in the search box
at the top</property>
- <property name="wrap">true</property>
- <property name="visible">true</property>
- <style>
- <class name="dim-label"/>
- </style>
- </object>
- </child>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ <attributes>
+ <attribute name="scale" value="2.0"/>
+ <attribute name="weight" value="bold"/>
+ </attributes>
</object>
- <packing>
- <property name="name">empty_state</property>
- </packing>
</child>
<child>
- <object class="IdeLayoutGrid" id="grid">
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Try opening a file by typing in the search box at
the top</property>
+ <property name="wrap">true</property>
<property name="visible">true</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
</object>
- <packing>
- <property name="name">grid</property>
- </packing>
</child>
</object>
+ <packing>
+ <property name="name">empty_state</property>
+ </packing>
+ </child>
+ <child>
+ <object class="IdeLayoutGrid" id="grid">
+ <property name="visible">true</property>
+ </object>
+ <packing>
+ <property name="name">grid</property>
+ </packing>
</child>
</object>
</child>
@@ -80,7 +72,7 @@
</style>
<child>
<object class="GtkToggleButton">
- <property name="action-name">panels.left</property>
+ <property name="action-name">dockbin.left-visible</property>
<property name="tooltip-text" translatable="yes">Enable / Disable left panel. Shortcut:
F9</property>
<property name="focus-on-click">false</property>
<property name="visible">true</property>
@@ -97,7 +89,7 @@
</child>
<child>
<object class="GtkToggleButton">
- <property name="action-name">panels.bottom</property>
+ <property name="action-name">dockbin.bottom-visible</property>
<property name="tooltip-text" translatable="yes">Enable / Disable bottom panel. Shortcut: Ctrl +
F9</property>
<property name="focus-on-click">false</property>
<property name="visible">true</property>
@@ -114,7 +106,7 @@
</child>
<child>
<object class="GtkToggleButton">
- <property name="action-name">panels.right</property>
+ <property name="action-name">dockbin.right-visible</property>
<property name="tooltip-text" translatable="yes">Enable / Disable right panel. Shortcut: Shift +
F9</property>
<property name="focus-on-click">false</property>
<property name="visible">true</property>
diff --git a/data/ui/ide-layout-pane.ui b/data/ui/ide-layout-pane.ui
index 7b7f3ea..b3edbe2 100644
--- a/data/ui/ide-layout-pane.ui
+++ b/data/ui/ide-layout-pane.ui
@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<!-- interface-requires gtk+ 3.16 -->
- <template class="IdeLayoutPane" parent="GtkBin">
+ <template class="IdeLayoutPane" parent="PnlDockBinEdge">
<child>
<object class="GtkBox" id="box">
+ <property name="expand">true</property>
<property name="orientation">vertical</property>
<property name="visible">true</property>
<child>
diff --git a/doc/reference/libide/libide-sections.txt b/doc/reference/libide/libide-sections.txt
index 49f5e6d..168dafc 100644
--- a/doc/reference/libide/libide-sections.txt
+++ b/doc/reference/libide/libide-sections.txt
@@ -976,10 +976,6 @@ ide_language_defaults_init_finish
IDE_TYPE_LAYOUT
IdeLayoutClass
ide_layout_new
-ide_layout_get_left_pane
-ide_layout_get_right_pane
-ide_layout_get_bottom_pane
-ide_layout_get_content_pane
ide_layout_get_active_view
IdeLayout
</SECTION>
diff --git a/libide/Makefile.am b/libide/Makefile.am
index bf6145b..4c204dc 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -446,9 +446,11 @@ libide_1_0_la_includes = \
-I$(top_srcdir)/contrib/egg \
-I$(top_srcdir)/contrib/gd \
-I$(top_srcdir)/contrib/nautilus \
+ -I$(top_srcdir)/contrib/pnl \
-I$(top_srcdir)/contrib/search \
-I$(top_srcdir)/contrib/tmpl \
-I$(top_srcdir)/contrib/xml \
+ -I$(top_builddir)/contrib/pnl \
-I$(top_builddir)/contrib/tmpl \
-I$(top_builddir)/data/icons/hicolor \
-I$(srcdir) \
@@ -500,6 +502,7 @@ libide_1_0_la_LIBADD = \
$(top_builddir)/contrib/egg/libegg-private.la \
$(top_builddir)/contrib/gd/libgd.la \
$(top_builddir)/contrib/nautilus/libnautilus.la \
+ $(top_builddir)/contrib/pnl/libpanel-gtk.la \
$(top_builddir)/contrib/search/libsearch.la \
$(top_builddir)/contrib/tmpl/libtemplate-glib-1.0.la \
$(top_builddir)/contrib/xml/libxml.la \
@@ -577,6 +580,7 @@ INTROSPECTION_SCANNER_ARGS = --add-include-path=$(srcdir) --warn-all
INTROSPECTION_COMPILER_ARGS = \
--includedir=$(srcdir) \
--includedir=$(top_builddir)/contrib/egg \
+ --includedir=$(top_builddir)/contrib/pnl \
--includedir=$(top_builddir)/contrib/tmpl
introspection_sources = \
@@ -596,6 +600,7 @@ Ide_1_0_gir_LIBS = \
Ide_1_0_gir_FILES = $(introspection_sources)
Ide_1_0_gir_SCANNERFLAGS = \
--include-uninstalled=$(top_builddir)/contrib/egg/Egg-1.0.gir \
+ --include-uninstalled=$(top_builddir)/contrib/pnl/Pnl-1.0.gir \
--include-uninstalled=$(top_builddir)/contrib/tmpl/Template-1.0.gir \
--c-include="ide.h" \
-n Ide \
@@ -624,15 +629,18 @@ libide_1_0_vapi_DEPS = \
gtk+-3.0 \
gtksourceview-3.0 \
libpeas-1.0 \
+ panel-gtk \
template-glib-1.0 \
egg-private
libide_1_0_vapi_METADATADIRS = $(srcdir)
libide_1_0_vapi_FILES = Ide-1.0.gir
libide_1_0_vapi_VAPIDIRS = \
$(top_builddir)/contrib/egg \
+ $(top_builddir)/contrib/pnl \
$(top_builddir)/contrib/tmpl
libide_1_0_vapi_GIRDIRS = \
$(top_builddir)/contrib/egg \
+ $(top_builddir)/contrib/pnl \
$(top_builddir)/contrib/tmpl
libide-1.0.deps: Makefile
diff --git a/libide/editor/ide-editor-perspective.c b/libide/editor/ide-editor-perspective.c
index e81dedf..73364e2 100644
--- a/libide/editor/ide-editor-perspective.c
+++ b/libide/editor/ide-editor-perspective.c
@@ -76,29 +76,26 @@ ide_editor_perspective_restore_panel_state (IdeEditorPerspective *self)
settings = g_settings_new ("org.gnome.builder.workbench");
- pane = ide_layout_get_left_pane (IDE_LAYOUT (self));
+ pane = pnl_dock_bin_get_left_edge (PNL_DOCK_BIN (self));
reveal = g_settings_get_boolean (settings, "left-visible");
position = g_settings_get_int (settings, "left-position");
- gtk_container_child_set (GTK_CONTAINER (self), pane,
- "position", position,
- "reveal", reveal,
- NULL);
+ pnl_dock_revealer_set_reveal_child (PNL_DOCK_REVEALER (pane), reveal);
+ pnl_dock_revealer_set_position (PNL_DOCK_REVEALER (pane), position);
+ pnl_dock_revealer_set_transition_duration (PNL_DOCK_REVEALER (pane), 350);
- pane = ide_layout_get_right_pane (IDE_LAYOUT (self));
+ pane = pnl_dock_bin_get_right_edge (PNL_DOCK_BIN (self));
reveal = g_settings_get_boolean (settings, "right-visible");
position = g_settings_get_int (settings, "right-position");
- gtk_container_child_set (GTK_CONTAINER (self), pane,
- "position", position,
- "reveal", reveal,
- NULL);
+ pnl_dock_revealer_set_reveal_child (PNL_DOCK_REVEALER (pane), reveal);
+ pnl_dock_revealer_set_position (PNL_DOCK_REVEALER (pane), position);
+ pnl_dock_revealer_set_transition_duration (PNL_DOCK_REVEALER (pane), 350);
- pane = ide_layout_get_bottom_pane (IDE_LAYOUT (self));
+ pane = pnl_dock_bin_get_bottom_edge (PNL_DOCK_BIN (self));
reveal = g_settings_get_boolean (settings, "bottom-visible");
position = g_settings_get_int (settings, "bottom-position");
- gtk_container_child_set (GTK_CONTAINER (self), pane,
- "position", position,
- "reveal", reveal,
- NULL);
+ pnl_dock_revealer_set_reveal_child (PNL_DOCK_REVEALER (pane), reveal);
+ pnl_dock_revealer_set_position (PNL_DOCK_REVEALER (pane), position);
+ pnl_dock_revealer_set_transition_duration (PNL_DOCK_REVEALER (pane), 350);
}
static void
@@ -113,27 +110,21 @@ ide_editor_perspective_save_panel_state (IdeEditorPerspective *self)
settings = g_settings_new ("org.gnome.builder.workbench");
- pane = ide_layout_get_left_pane (IDE_LAYOUT (self));
- gtk_container_child_get (GTK_CONTAINER (IDE_LAYOUT (self)), pane,
- "reveal", &reveal,
- "position", &position,
- NULL);
+ pane = pnl_dock_bin_get_left_edge (PNL_DOCK_BIN (self));
+ position = pnl_dock_revealer_get_position (PNL_DOCK_REVEALER (pane));
+ reveal = pnl_dock_revealer_get_reveal_child (PNL_DOCK_REVEALER (pane));
g_settings_set_boolean (settings, "left-visible", reveal);
g_settings_set_int (settings, "left-position", position);
- pane = ide_layout_get_right_pane (IDE_LAYOUT (self));
- gtk_container_child_get (GTK_CONTAINER (IDE_LAYOUT (self)), pane,
- "reveal", &reveal,
- "position", &position,
- NULL);
+ pane = pnl_dock_bin_get_right_edge (PNL_DOCK_BIN (self));
+ position = pnl_dock_revealer_get_position (PNL_DOCK_REVEALER (pane));
+ reveal = pnl_dock_revealer_get_reveal_child (PNL_DOCK_REVEALER (pane));
g_settings_set_boolean (settings, "right-visible", reveal);
g_settings_set_int (settings, "right-position", position);
- pane = ide_layout_get_bottom_pane (IDE_LAYOUT (self));
- gtk_container_child_get (GTK_CONTAINER (IDE_LAYOUT (self)), pane,
- "reveal", &reveal,
- "position", &position,
- NULL);
+ pane = pnl_dock_bin_get_bottom_edge (PNL_DOCK_BIN (self));
+ position = pnl_dock_revealer_get_position (PNL_DOCK_REVEALER (pane));
+ reveal = pnl_dock_revealer_get_reveal_child (PNL_DOCK_REVEALER (pane));
g_settings_set_boolean (settings, "bottom-visible", reveal);
g_settings_set_int (settings, "bottom-position", position);
}
@@ -441,8 +432,8 @@ ide_editor_perspective_init (IdeEditorPerspective *self)
g_action_map_add_action_entries (G_ACTION_MAP (self->actions), entries,
G_N_ELEMENTS (entries), self);
- actions = gtk_widget_get_action_group (GTK_WIDGET (self), "panels");
- gtk_widget_insert_action_group (GTK_WIDGET (self->titlebar), "panels", actions);
+ actions = gtk_widget_get_action_group (GTK_WIDGET (self), "dockbin");
+ gtk_widget_insert_action_group (GTK_WIDGET (self->titlebar), "dockbin", actions);
ide_editor_perspective_restore_panel_state (self);
diff --git a/libide/ide-application-actions.c b/libide/ide-application-actions.c
index 6f92ab6..e9cd7b0 100644
--- a/libide/ide-application-actions.c
+++ b/libide/ide-application-actions.c
@@ -309,9 +309,9 @@ ide_application_actions_init (IdeApplication *self)
/*
* FIXME: Once we get a new shortcuts engine, port these to that.
*/
- gtk_application_set_accels_for_action (GTK_APPLICATION (self), "panels.left", left);
- gtk_application_set_accels_for_action (GTK_APPLICATION (self), "panels.right", right);
- gtk_application_set_accels_for_action (GTK_APPLICATION (self), "panels.bottom", bottom);
+ gtk_application_set_accels_for_action (GTK_APPLICATION (self), "dockbin.left-visible", left);
+ gtk_application_set_accels_for_action (GTK_APPLICATION (self), "dockbin.right-visible", right);
+ gtk_application_set_accels_for_action (GTK_APPLICATION (self), "dockbin.bottom-visible", bottom);
gtk_application_set_accels_for_action (GTK_APPLICATION (self), "app.preferences", preferences);
gtk_application_set_accels_for_action (GTK_APPLICATION (self), "perspective.global-search", global_search);
gtk_application_set_accels_for_action (GTK_APPLICATION (self), "perspective.new-file", new_file);
diff --git a/libide/ide-layout-pane.c b/libide/ide-layout-pane.c
index fb54f07..57bfb8b 100644
--- a/libide/ide-layout-pane.c
+++ b/libide/ide-layout-pane.c
@@ -27,7 +27,7 @@
struct _IdeLayoutPane
{
- GtkBin parent_instance;
+ PnlDockBinEdge parent_instance;
GtkBox *box;
GtkBox *header;
@@ -43,112 +43,7 @@ struct _IdeLayoutPane
GtkPositionType position;
};
-G_DEFINE_TYPE (IdeLayoutPane, ide_layout_pane, GTK_TYPE_BIN)
-
-enum {
- PROP_0,
- PROP_POSITION,
- LAST_PROP
-};
-
-enum {
- STYLE_PROP_0,
- STYLE_PROP_HANDLE_SIZE,
- LAST_STYLE_PROP
-};
-
-static GParamSpec *properties [LAST_PROP];
-static GParamSpec *styleParamSpecs [LAST_STYLE_PROP];
-
-static gboolean
-ide_layout_pane_draw (GtkWidget *widget,
- cairo_t *cr)
-{
- IdeLayoutPane *self = (IdeLayoutPane *)widget;
- GtkStyleContext *style_context;
- gboolean ret;
-
- g_assert (IDE_IS_LAYOUT_PANE (self));
- g_assert (cr != NULL);
-
- ret = GTK_WIDGET_CLASS (ide_layout_pane_parent_class)->draw (widget, cr);
-
- style_context = gtk_widget_get_style_context (widget);
-
- gtk_style_context_save (style_context);
- gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_PANE_SEPARATOR);
- gtk_render_handle (style_context, cr,
- self->handle_pos.x,
- self->handle_pos.y,
- self->handle_pos.width,
- self->handle_pos.height);
- gtk_style_context_restore (style_context);
-
- return ret;
-}
-
-static void
-ide_layout_pane_size_allocate (GtkWidget *widget,
- GtkAllocation *alloc)
-{
- IdeLayoutPane *self = (IdeLayoutPane *)widget;
- GtkWidget *child;
- GtkAllocation child_alloc;
- gint handle_size;
-
- g_assert (IDE_IS_LAYOUT_PANE (self));
-
- gtk_widget_set_allocation (widget, alloc);
-
- child = gtk_bin_get_child (GTK_BIN (self));
- if (child == NULL || !gtk_widget_get_visible (child))
- return;
-
- gtk_widget_style_get (widget, "handle-size", &handle_size, NULL);
-
- child_alloc = *alloc;
-
- switch (self->position)
- {
- case GTK_POS_LEFT:
- child_alloc.width -= handle_size;
- self->handle_pos.x = child_alloc.x + child_alloc.width;
- self->handle_pos.width = handle_size;
- self->handle_pos.height = child_alloc.height;
- self->handle_pos.y = child_alloc.y;
- break;
-
- case GTK_POS_RIGHT:
- child_alloc.x += handle_size;
- child_alloc.width -= handle_size;
- self->handle_pos.x = alloc->x;
- self->handle_pos.width = handle_size;
- self->handle_pos.height = child_alloc.height;
- self->handle_pos.y = child_alloc.y;
- break;
-
- case GTK_POS_BOTTOM:
- child_alloc.y += handle_size;
- child_alloc.height -= handle_size;
- self->handle_pos.x = alloc->x;
- self->handle_pos.width = alloc->width;
- self->handle_pos.height = handle_size;
- self->handle_pos.y = alloc->y;
- break;
-
- case GTK_POS_TOP:
- self->handle_pos.x = 0;
- self->handle_pos.y = 0;
- self->handle_pos.width = 0;
- self->handle_pos.height = 0;
- break;
-
- default:
- break;
- }
-
- gtk_widget_size_allocate (child, &child_alloc);
-}
+G_DEFINE_TYPE (IdeLayoutPane, ide_layout_pane, PNL_TYPE_DOCK_BIN_EDGE)
static void
ide_layout_pane_grab_focus (GtkWidget *widget)
@@ -250,44 +145,6 @@ ide_layout_pane_finalize (GObject *object)
}
static void
-ide_layout_pane_get_property (GObject *object,
- guint prop_id,
- GValue *value,
- GParamSpec *pspec)
-{
- IdeLayoutPane *self = IDE_LAYOUT_PANE (object);
-
- switch (prop_id)
- {
- case PROP_POSITION:
- g_value_set_enum (value, ide_layout_pane_get_position (self));
- break;
-
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
- }
-}
-
-static void
-ide_layout_pane_set_property (GObject *object,
- guint prop_id,
- const GValue *value,
- GParamSpec *pspec)
-{
- IdeLayoutPane *self = IDE_LAYOUT_PANE (object);
-
- switch (prop_id)
- {
- case PROP_POSITION:
- ide_layout_pane_set_position (self, g_value_get_enum (value));
- break;
-
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
- }
-}
-
-static void
ide_layout_pane_class_init (IdeLayoutPaneClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
@@ -295,46 +152,12 @@ ide_layout_pane_class_init (IdeLayoutPaneClass *klass)
object_class->dispose = ide_layout_pane_dispose;
object_class->finalize = ide_layout_pane_finalize;
- object_class->get_property = ide_layout_pane_get_property;
- object_class->set_property = ide_layout_pane_set_property;
- widget_class->draw = ide_layout_pane_draw;
widget_class->grab_focus = ide_layout_pane_grab_focus;
widget_class->hierarchy_changed = ide_layout_pane_hierarchy_changed;
- widget_class->size_allocate = ide_layout_pane_size_allocate;
gtk_widget_class_set_css_name (widget_class, "layoutpane");
- /**
- * IdeLayoutPane:position:
- *
- * The position at which to place the pane. This also dictates which
- * direction that animations will occur.
- *
- * For example, setting to %GTK_POS_LEFT will result in the resize grip
- * being placed on the right, and animations to and from the leftmost
- * of the allocation.
- */
- properties [PROP_POSITION] =
- g_param_spec_enum ("position",
- "Position",
- "The position of the pane.",
- GTK_TYPE_POSITION_TYPE,
- GTK_POS_LEFT,
- (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
-
- g_object_class_install_properties (object_class, LAST_PROP, properties);
-
- styleParamSpecs [STYLE_PROP_HANDLE_SIZE] =
- g_param_spec_int ("handle-size",
- "Handle Size",
- "Width of handle.",
- 0, G_MAXINT,
- 1,
- (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
- gtk_widget_class_install_style_property (widget_class,
- styleParamSpecs [STYLE_PROP_HANDLE_SIZE]);
-
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/builder/ui/ide-layout-pane.ui");
gtk_widget_class_bind_template_child (widget_class, IdeLayoutPane, box);
gtk_widget_class_bind_template_child_internal (widget_class, IdeLayoutPane, stack);
@@ -360,36 +183,6 @@ ide_layout_pane_init (IdeLayoutPane *self)
NULL);
}
-GtkWidget *
-ide_layout_pane_new (void)
-{
- return g_object_new (IDE_TYPE_LAYOUT_PANE, NULL);
-}
-
-GtkPositionType
-ide_layout_pane_get_position (IdeLayoutPane *self)
-{
- g_return_val_if_fail (IDE_IS_LAYOUT_PANE (self), GTK_POS_LEFT);
-
- return self->position;
-}
-
-void
-ide_layout_pane_set_position (IdeLayoutPane *self,
- GtkPositionType position)
-{
- g_return_if_fail (IDE_IS_LAYOUT_PANE (self));
- g_return_if_fail (position >= GTK_POS_LEFT);
- g_return_if_fail (position <= GTK_POS_BOTTOM);
-
- if (position != self->position)
- {
- self->position = position;
- gtk_widget_queue_resize (GTK_WIDGET (self));
- g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_POSITION]);
- }
-}
-
/**
* ide_layout_pane_add_page:
* @self: An #IdeLayoutPane
diff --git a/libide/ide-layout-pane.h b/libide/ide-layout-pane.h
index ab944b9..407f319 100644
--- a/libide/ide-layout-pane.h
+++ b/libide/ide-layout-pane.h
@@ -19,15 +19,14 @@
#ifndef IDE_LAYOUT_PANE_H
#define IDE_LAYOUT_PANE_H
-#include <gtk/gtk.h>
+#include <pnl.h>
G_BEGIN_DECLS
#define IDE_TYPE_LAYOUT_PANE (ide_layout_pane_get_type())
-G_DECLARE_FINAL_TYPE (IdeLayoutPane, ide_layout_pane, IDE, LAYOUT_PANE, GtkBin)
+G_DECLARE_FINAL_TYPE (IdeLayoutPane, ide_layout_pane, IDE, LAYOUT_PANE, PnlDockBinEdge)
-GtkWidget *ide_layout_pane_new (void);
GtkPositionType ide_layout_pane_get_position (IdeLayoutPane *self);
void ide_layout_pane_set_position (IdeLayoutPane *self,
GtkPositionType position);
diff --git a/libide/ide-layout.c b/libide/ide-layout.c
index c530293..2178bc5 100644
--- a/libide/ide-layout.c
+++ b/libide/ide-layout.c
@@ -16,1120 +16,33 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#include <egg-animation.h>
-#include <glib/gi18n.h>
-#include <ide.h>
-#include <string.h>
-
#include "ide-layout.h"
#include "ide-layout-pane.h"
-#define ANIMATION_MODE EGG_ANIMATION_EASE_IN_OUT_QUAD
-#define ANIMATION_DURATION 250
-#define HORIZ_GRIP_EXTRA 5
-#define VERT_GRIP_EXTRA 5
-#define MIN_POSITION 100
-
-typedef struct
-{
- GtkWidget *widget;
- GtkAdjustment *adjustment;
- EggAnimation *animation;
- GdkWindow *handle;
- GtkAllocation handle_pos;
- GtkAllocation alloc;
- gint min_width;
- gint min_height;
- gint nat_width;
- gint nat_height;
- gint position;
- gint restore_position;
- GdkCursorType cursor_type;
- GtkPositionType type : 4;
- guint reveal : 1;
- guint hiding : 1;
- guint showing : 1;
-} IdeLayoutChild;
-
typedef struct
{
- IdeLayoutChild children[4];
-
- GActionMap *actions;
- GtkGesture *pan_gesture;
- IdeLayoutChild *drag_child;
- gdouble drag_position;
-
- GtkWidget *active_view;
- gulong focus_handler;
+ GtkWidget *active_view;
} IdeLayoutPrivate;
-static void buildable_init_iface (GtkBuildableIface *iface);
-static const gchar *action_names[] = { "left", "right", NULL, "bottom" };
-
-G_DEFINE_TYPE_WITH_CODE (IdeLayout, ide_layout, GTK_TYPE_OVERLAY,
- G_ADD_PRIVATE (IdeLayout)
- G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, buildable_init_iface))
+G_DEFINE_TYPE_WITH_PRIVATE (IdeLayout, ide_layout, PNL_TYPE_DOCK_BIN)
enum {
PROP_0,
PROP_ACTIVE_VIEW,
- PROP_BOTTOM_PANE,
- PROP_CONTENT_PANE,
- PROP_LEFT_PANE,
- PROP_RIGHT_PANE,
LAST_PROP
};
-enum {
- CHILD_PROP_0,
- CHILD_PROP_REVEAL,
- CHILD_PROP_POSITION,
- LAST_CHILD_PROP
-};
-
-static GtkBuildableIface *ide_layout_parent_buildable_iface;
-static GParamSpec *properties [LAST_PROP];
-static GParamSpec *child_properties [LAST_CHILD_PROP];
-
-static void
-ide_layout_move_resize_handle (IdeLayout *self,
- GtkPositionType type)
-{
- IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
- IdeLayoutChild *child;
- GtkAllocation alloc;
-
- g_assert (IDE_IS_LAYOUT (self));
- g_assert ((type == GTK_POS_LEFT) ||
- (type == GTK_POS_RIGHT) ||
- (type == GTK_POS_BOTTOM));
-
- child = &priv->children [type];
-
- if (child->handle == NULL)
- return;
-
- gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
-
- switch (type)
- {
- case GTK_POS_LEFT:
- child->handle_pos.x = alloc.x + child->alloc.x + child->alloc.width - HORIZ_GRIP_EXTRA;
- child->handle_pos.y = alloc.y + child->alloc.y;
- child->handle_pos.width = 2 * HORIZ_GRIP_EXTRA;
- child->handle_pos.height = child->alloc.height;
- break;
-
- case GTK_POS_RIGHT:
- child->handle_pos.x = alloc.x + child->alloc.x - HORIZ_GRIP_EXTRA;
- child->handle_pos.y = alloc.y + child->alloc.y;
- child->handle_pos.width = 2 * HORIZ_GRIP_EXTRA;
- child->handle_pos.height = child->alloc.height;
- break;
-
- case GTK_POS_BOTTOM:
- child->handle_pos.x = alloc.x + child->alloc.x;
- child->handle_pos.y = alloc.y + child->alloc.y - VERT_GRIP_EXTRA;
- child->handle_pos.width = child->alloc.width;
- child->handle_pos.height = 2 * VERT_GRIP_EXTRA;
- break;
-
- case GTK_POS_TOP:
- default:
- break;
- }
-
- if (!gtk_widget_get_child_visible (child->widget))
- memset (&child->handle_pos, 0, sizeof child->handle_pos);
-
- if (gtk_widget_get_mapped (GTK_WIDGET (self)))
- gdk_window_move_resize (child->handle,
- child->handle_pos.x,
- child->handle_pos.y,
- child->handle_pos.width,
- child->handle_pos.height);
-}
-
-static void
-ide_layout_create_handle_window (IdeLayout *self,
- GtkPositionType type)
-{
- IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
- IdeLayoutChild *child;
- GtkAllocation alloc;
- GdkWindowAttr attributes = { 0 };
- GdkWindow *parent;
- GdkDisplay *display;
-
- g_assert (IDE_IS_LAYOUT (self));
- g_assert ((type == GTK_POS_LEFT) ||
- (type == GTK_POS_RIGHT) ||
- (type == GTK_POS_BOTTOM));
-
- display = gtk_widget_get_display (GTK_WIDGET (self));
- parent = gtk_widget_get_window (GTK_WIDGET (self));
-
- g_assert (GDK_IS_DISPLAY (display));
- g_assert (GDK_IS_WINDOW (parent));
-
- gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
-
- child = &priv->children [type];
-
- attributes.window_type = GDK_WINDOW_CHILD;
- attributes.wclass = GDK_INPUT_ONLY;
- attributes.x = child->handle_pos.x;
- attributes.y = child->handle_pos.y;
- attributes.width = child->handle_pos.width;
- attributes.height = child->handle_pos.height;
- attributes.visual = gtk_widget_get_visual (GTK_WIDGET (self));
- attributes.event_mask = gtk_widget_get_events (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, child->cursor_type);
-
- child->handle = gdk_window_new (parent, &attributes, (GDK_WA_CURSOR | GDK_WA_X | GDK_WA_Y));
- gtk_widget_register_window (GTK_WIDGET (self), child->handle);
-
- g_clear_object (&attributes.cursor);
-}
-
-static void
-ide_layout_destroy_handle_window (IdeLayout *self,
- GtkPositionType type)
-{
- IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
- IdeLayoutChild *child;
-
- g_assert (IDE_IS_LAYOUT (self));
-
- child = &priv->children [type];
-
- if (child->handle)
- {
- gdk_window_hide (child->handle);
- gtk_widget_unregister_window (GTK_WIDGET (self), child->handle);
- gdk_window_destroy (child->handle);
- child->handle = NULL;
- }
-}
-
-static void
-ide_layout_child_get_preferred_size (IdeLayoutChild *child,
- const GtkAllocation *alloc)
-{
- GtkRequisition min_req = { 0 };
- GtkRequisition nat_req = { 0 };
-
- g_assert (child != NULL);
- g_assert (child->widget != NULL);
- g_assert (alloc != NULL);
-
- gtk_widget_get_preferred_size (child->widget, &min_req, &nat_req);
-
- child->min_height = min_req.height;
- child->nat_height = nat_req.height;
- child->min_width = min_req.width;
- child->nat_width = nat_req.width;
-}
-
-static void
-ide_layout_relayout (IdeLayout *self,
- const GtkAllocation *alloc)
-{
- IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
- IdeLayoutChild *left;
- IdeLayoutChild *right;
- IdeLayoutChild *content;
- IdeLayoutChild *bottom;
-
- g_assert (IDE_IS_LAYOUT (self));
- g_assert (alloc != NULL);
-
- left = &priv->children [GTK_POS_LEFT];
- right = &priv->children [GTK_POS_RIGHT];
- content = &priv->children [GTK_POS_TOP];
- bottom = &priv->children [GTK_POS_BOTTOM];
-
- ide_layout_child_get_preferred_size (left, alloc);
- ide_layout_child_get_preferred_size (right, alloc);
- ide_layout_child_get_preferred_size (content, alloc);
- ide_layout_child_get_preferred_size (bottom, alloc);
-
- /*
- * Determine everything as if we are animating in/out or the child is visible.
- */
-
- if (left->reveal)
- {
- left->alloc.x = 0;
- left->alloc.y = 0;
- left->alloc.width = left->position;
- left->alloc.height = alloc->height;
-
- left->alloc.x -= gtk_adjustment_get_value (left->adjustment) * left->position;
- }
- else
- {
- left->alloc.x = -left->position;
- left->alloc.y = 0;
- left->alloc.width = left->position;
- left->alloc.height = alloc->height;
- }
-
- if (right->reveal)
- {
- right->alloc.x = alloc->width - right->position;
- right->alloc.y = 0;
- right->alloc.width = right->position;
- right->alloc.height = alloc->height;
-
- right->alloc.x += gtk_adjustment_get_value (right->adjustment) * right->position;
- }
- else
- {
- right->alloc.x = alloc->width;
- right->alloc.y = 0;
- right->alloc.width = right->position;
- right->alloc.height = alloc->height;
- }
-
- if (bottom->reveal)
- {
- bottom->alloc.x = left->alloc.x + left->alloc.width;
- bottom->alloc.y = alloc->height - bottom->position;
- bottom->alloc.width = right->alloc.x - bottom->alloc.x;
- bottom->alloc.height = bottom->position;
-
- bottom->alloc.y += gtk_adjustment_get_value (bottom->adjustment) * bottom->position;
- }
- else
- {
- bottom->alloc.x = left->alloc.x + left->alloc.width;
- bottom->alloc.y = alloc->height;
- bottom->alloc.width = right->alloc.x - bottom->alloc.x;
- bottom->alloc.height = bottom->position;
- }
-
- if (content->reveal)
- {
- content->alloc.x = left->alloc.x + left->alloc.width;
- content->alloc.y = 0;
- content->alloc.width = right->alloc.x - content->alloc.x;
- content->alloc.height = bottom->alloc.y;
-
- content->alloc.y -= gtk_adjustment_get_value (content->adjustment) * content->alloc.height;
- }
- else
- {
- content->alloc.x = left->alloc.x + left->alloc.width;
- content->alloc.y = -bottom->alloc.y;
- content->alloc.width = right->alloc.x - content->alloc.x;
- content->alloc.height = bottom->alloc.y;
- }
-
- /*
- * Now adjust for child visibility.
- *
- * We need to ensure we don't give the non-visible children an allocation
- * as it will interfere with hit targets.
- */
- if (!gtk_widget_get_child_visible (content->widget))
- memset (&content->alloc, 0, sizeof content->alloc);
- if (!gtk_widget_get_child_visible (left->widget))
- memset (&left->alloc, 0, sizeof left->alloc);
- if (!gtk_widget_get_child_visible (right->widget))
- memset (&right->alloc, 0, sizeof right->alloc);
- if (!gtk_widget_get_child_visible (bottom->widget))
- memset (&bottom->alloc, 0, sizeof bottom->alloc);
-
- ide_layout_move_resize_handle (self, GTK_POS_LEFT);
- ide_layout_move_resize_handle (self, GTK_POS_RIGHT);
- ide_layout_move_resize_handle (self, GTK_POS_BOTTOM);
-}
-
-static void
-ide_layout_size_allocate (GtkWidget *widget,
- GtkAllocation *alloc)
-{
- IdeLayout *self = (IdeLayout *)widget;
- IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
- gint i;
-
- g_assert (IDE_IS_LAYOUT (self));
- g_assert (alloc != NULL);
-
- ide_layout_relayout (self, alloc);
-
- GTK_WIDGET_CLASS (ide_layout_parent_class)->size_allocate (widget, alloc);
-
- for (i = 0; i < G_N_ELEMENTS (priv->children); i++)
- {
- IdeLayoutChild *child = &priv->children [i];
-
- if ((child->handle != NULL) &&
- gtk_widget_get_visible (child->widget) &&
- gtk_widget_get_child_visible (child->widget))
- gdk_window_raise (child->handle);
- }
-}
-
-static IdeLayoutChild *
-ide_layout_child_find (IdeLayout *self,
- GtkWidget *child)
-{
- IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
- gint i;
-
- g_assert (IDE_IS_LAYOUT (self));
- g_assert (GTK_IS_WIDGET (child));
-
- for (i = 0; i < G_N_ELEMENTS (priv->children); i++)
- {
- IdeLayoutChild *item = &priv->children [i];
-
- if (item->widget == child)
- return item;
- }
-
- g_warning ("Child of type %s was not found in this IdeLayout.",
- g_type_name (G_OBJECT_TYPE (child)));
-
- return NULL;
-}
-
-static void
-ide_layout_animation_cb (gpointer data)
-{
- g_autoptr(GtkWidget) child = data;
- GtkWidget *parent;
- IdeLayout *self;
- IdeLayoutChild *item;
-
- g_assert (GTK_IS_WIDGET (child));
-
- parent = gtk_widget_get_parent (child);
- if (!IDE_IS_LAYOUT (parent))
- return;
-
- self = IDE_LAYOUT (parent);
-
- item = ide_layout_child_find (self, child);
- if (item == NULL)
- return;
-
- if (item->hiding)
- {
- gtk_widget_set_child_visible (item->widget, FALSE);
- if (item->restore_position > item->position)
- item->position = item->restore_position;
- }
-
- item->showing = FALSE;
- item->hiding = FALSE;
- item->reveal = gtk_adjustment_get_value (item->adjustment) == 0.0;
-
- gtk_widget_queue_allocate (GTK_WIDGET (self));
-
- gtk_container_child_notify (GTK_CONTAINER (self), child, "reveal");
-}
-
-static gboolean
-ide_layout_get_child_position (GtkOverlay *overlay,
- GtkWidget *child,
- GtkAllocation *alloc)
-{
- IdeLayout *self = (IdeLayout *)overlay;
- IdeLayoutChild *item;
-
- g_assert (IDE_IS_LAYOUT (self));
- g_assert (GTK_IS_WIDGET (child));
- g_assert (alloc != NULL);
-
- if (!(item = ide_layout_child_find (self, child)))
- return FALSE;
-
- *alloc = item->alloc;
-
- return TRUE;
-}
-
-static guint
-ide_layout_child_get_position (IdeLayout *self,
- GtkWidget *child)
-{
- IdeLayoutChild *item;
-
- g_assert (IDE_IS_LAYOUT (self));
- g_assert (GTK_IS_WIDGET (child));
-
- if (!(item = ide_layout_child_find (self, child)))
- return FALSE;
-
- return item->position;
-}
-
-static void
-ide_layout_child_set_position (IdeLayout *self,
- GtkWidget *child,
- guint position)
-{
- IdeLayoutChild *item;
-
- g_assert (IDE_IS_LAYOUT (self));
- g_assert (GTK_IS_WIDGET (child));
-
- if (!(item = ide_layout_child_find (self, child)))
- return;
-
- item->position = position;
-
- gtk_widget_queue_allocate (GTK_WIDGET (self));
-
- gtk_container_child_notify (GTK_CONTAINER (self), child, "position");
-}
-
-static gboolean
-ide_layout_child_get_reveal (IdeLayout *self,
- GtkWidget *child)
-{
- IdeLayoutChild *item;
-
- g_assert (IDE_IS_LAYOUT (self));
- g_assert (GTK_IS_WIDGET (child));
-
- if (!(item = ide_layout_child_find (self, child)))
- return FALSE;
-
- return item->reveal;
-}
-
-static void
-ide_layout_child_set_reveal (IdeLayout *self,
- GtkWidget *child,
- gboolean reveal)
-{
- IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
- IdeLayoutChild *item;
- GdkFrameClock *frame_clock;
-
- g_assert (IDE_IS_LAYOUT (self));
- g_assert (GTK_IS_WIDGET (child));
-
- reveal = !!reveal;
-
- if (!(item = ide_layout_child_find (self, child)) || (item->reveal == reveal))
- return;
-
- if (item->animation != NULL)
- {
- egg_animation_stop (item->animation);
- ide_clear_weak_pointer (&item->animation);
- }
-
- item->reveal = TRUE;
- item->showing = reveal;
- item->hiding = !reveal;
-
- if (item->position > MIN_POSITION)
- {
- item->restore_position = item->position;
- gtk_container_child_notify (GTK_CONTAINER (self), item->widget, "position");
- }
-
- gtk_widget_set_child_visible (child, TRUE);
-
- frame_clock = gtk_widget_get_frame_clock (child);
-
- if (gtk_widget_get_realized (GTK_WIDGET (self)))
- {
- item->animation = egg_object_animate_full (item->adjustment,
- ANIMATION_MODE,
- ANIMATION_DURATION,
- frame_clock,
- ide_layout_animation_cb,
- g_object_ref (child),
- "value", reveal ? 0.0 : 1.0,
- NULL);
- g_object_add_weak_pointer (G_OBJECT (item->animation), (gpointer *)&item->animation);
- }
- else
- {
- item->reveal = reveal;
- gtk_adjustment_set_value (item->adjustment, reveal ? 0.0 : 1.0);
- gtk_container_child_notify (GTK_CONTAINER (self), item->widget, "reveal");
- }
-
- if (action_names [item->type] != NULL)
- {
- GAction *action;
-
- action = g_action_map_lookup_action (priv->actions, action_names [item->type]);
-
- if (G_IS_SIMPLE_ACTION (action))
- g_simple_action_set_state (G_SIMPLE_ACTION (action),
- g_variant_new_boolean (reveal));
- }
-
- gtk_widget_queue_allocate (GTK_WIDGET (self));
-}
-
-static void
-ide_layout_get_child_property (GtkContainer *container,
- GtkWidget *child,
- guint prop_id,
- GValue *value,
- GParamSpec *pspec)
-{
- IdeLayout *self = (IdeLayout *)container;
-
- switch (prop_id)
- {
- case CHILD_PROP_REVEAL:
- g_value_set_boolean (value, ide_layout_child_get_reveal (self, child));
- break;
-
- case CHILD_PROP_POSITION:
- g_value_set_uint (value, ide_layout_child_get_position (self, child));
- break;
-
- default:
- GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec);
- }
-}
-
-static void
-ide_layout_set_child_property (GtkContainer *container,
- GtkWidget *child,
- guint prop_id,
- const GValue *value,
- GParamSpec *pspec)
-{
- IdeLayout *self = (IdeLayout *)container;
-
- switch (prop_id)
- {
- case CHILD_PROP_REVEAL:
- ide_layout_child_set_reveal (self, child, g_value_get_boolean (value));
- break;
-
- case CHILD_PROP_POSITION:
- ide_layout_child_set_position (self, child, g_value_get_uint (value));
- break;
-
- default:
- GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, prop_id, pspec);
- }
-}
-
-static void
-ide_layout_get_preferred_width (GtkWidget *widget,
- gint *min_width,
- gint *nat_width)
-{
- IdeLayout *self = (IdeLayout *)widget;
- IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
- gint i;
-
- g_assert (IDE_IS_LAYOUT (self));
-
- for (i = 0; i < G_N_ELEMENTS (priv->children); i++)
- {
- IdeLayoutChild *child = &priv->children [i];
-
- if (gtk_widget_get_visible (child->widget))
- gtk_widget_get_preferred_width (child->widget, &child->min_width, &child->nat_width);
- }
-
- *min_width = priv->children [GTK_POS_LEFT].min_width
- + priv->children [GTK_POS_RIGHT].min_width
- + MAX (priv->children [GTK_POS_TOP].min_width,
- priv->children [GTK_POS_BOTTOM].min_width);
- *nat_width = priv->children [GTK_POS_LEFT].nat_width
- + priv->children [GTK_POS_RIGHT].nat_width
- + MAX (priv->children [GTK_POS_TOP].nat_width,
- priv->children [GTK_POS_BOTTOM].nat_width);
-}
-
-static void
-ide_layout_get_preferred_height (GtkWidget *widget,
- gint *min_height,
- gint *nat_height)
-{
- IdeLayout *self = (IdeLayout *)widget;
- IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
- gint i;
-
- g_assert (IDE_IS_LAYOUT (self));
-
- for (i = 0; i < G_N_ELEMENTS (priv->children); i++)
- {
- IdeLayoutChild *child = &priv->children [i];
-
- if (gtk_widget_get_visible (child->widget))
- gtk_widget_get_preferred_height (child->widget, &child->min_height, &child->nat_height);
- }
-
- *min_height = MAX (MAX (priv->children [GTK_POS_LEFT].min_height,
- priv->children [GTK_POS_RIGHT].min_height),
- (priv->children [GTK_POS_BOTTOM].position +
- priv->children [GTK_POS_TOP].min_height));
-
- *nat_height = MAX (MAX (priv->children [GTK_POS_LEFT].nat_height,
- priv->children [GTK_POS_RIGHT].nat_height),
- (priv->children [GTK_POS_BOTTOM].position +
- priv->children [GTK_POS_TOP].nat_height));
-}
-
-static GtkSizeRequestMode
-ide_layout_get_request_mode (GtkWidget *widget)
-{
- return GTK_SIZE_REQUEST_CONSTANT_SIZE;
-}
-
-static GtkAdjustment *
-ide_layout_create_adjustment (IdeLayout *self)
-{
- GtkAdjustment *adj;
-
- g_assert (IDE_IS_LAYOUT (self));
-
- adj = g_object_new (GTK_TYPE_ADJUSTMENT,
- "lower", 0.0,
- "upper", 1.0,
- "value", 0.0,
- NULL);
-
- g_signal_connect_object (adj,
- "value-changed",
- G_CALLBACK (gtk_widget_queue_allocate),
- self,
- G_CONNECT_SWAPPED);
-
- return adj;
-}
-
-static void
-ide_layout_drag_begin_cb (IdeLayout *self,
- gdouble x,
- gdouble y,
- GtkGesturePan *pan)
-{
- IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
- IdeLayoutChild *left;
- IdeLayoutChild *right;
- IdeLayoutChild *bottom;
- GdkEventSequence *sequence;
- const GdkEvent *event;
-
- g_assert (IDE_IS_LAYOUT (self));
- g_assert (GTK_IS_GESTURE_PAN (pan));
-
- left = &priv->children [GTK_POS_LEFT];
- right = &priv->children [GTK_POS_RIGHT];
- bottom = &priv->children [GTK_POS_BOTTOM];
-
- sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (pan));
- event = gtk_gesture_get_last_event (GTK_GESTURE (pan), sequence);
-
- if (event->any.window == left->handle)
- {
- gtk_gesture_pan_set_orientation (pan, GTK_ORIENTATION_HORIZONTAL);
- priv->drag_child = left;
- }
- else if (event->any.window == right->handle)
- {
- gtk_gesture_pan_set_orientation (pan, GTK_ORIENTATION_HORIZONTAL);
- priv->drag_child = right;
- }
- else if (event->any.window == bottom->handle)
- {
- gtk_gesture_pan_set_orientation (pan, GTK_ORIENTATION_VERTICAL);
- priv->drag_child = bottom;
- }
- else
- {
- gtk_gesture_set_state (GTK_GESTURE (pan), GTK_EVENT_SEQUENCE_DENIED);
- priv->drag_child = NULL;
- return;
- }
-
- priv->drag_position = MAX (priv->drag_child->position, MIN_POSITION);
- gtk_gesture_set_state (GTK_GESTURE (pan), GTK_EVENT_SEQUENCE_CLAIMED);
- gtk_container_child_notify (GTK_CONTAINER (self), priv->drag_child->widget, "position");
-}
-
-static void
-ide_layout_drag_end_cb (IdeLayout *self,
- gdouble x,
- gdouble y,
- GtkGesturePan *pan)
-{
- IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
- GdkEventSequence *sequence;
- GtkEventSequenceState state;
-
- g_assert (IDE_IS_LAYOUT (self));
- g_assert (GTK_IS_GESTURE_PAN (pan));
-
- if (priv->drag_child == NULL)
- return;
-
- sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (pan));
- state = gtk_gesture_get_sequence_state (GTK_GESTURE (pan), sequence);
- if (state == GTK_EVENT_SEQUENCE_DENIED)
- {
- priv->drag_child = NULL;
- return;
- }
-
- if (priv->drag_child->position < MIN_POSITION)
- {
- gtk_container_child_set (GTK_CONTAINER (self), priv->drag_child->widget,
- "reveal", FALSE,
- NULL);
- priv->drag_child->restore_position = priv->drag_position;
- }
-
- gtk_container_child_notify (GTK_CONTAINER (self), priv->drag_child->widget, "position");
-
- priv->drag_child = NULL;
- priv->drag_position = 0;
-}
-
-static void
-ide_layout_pan_cb (IdeLayout *self,
- GtkPanDirection direction,
- gdouble offset,
- GtkGesturePan *pan)
-{
- IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
- GtkAllocation alloc;
- gint target_position = 0;
- gint center_min_width;
- gint left_max;
- gint right_max;
- gint bottom_max;
-
- g_assert (IDE_IS_LAYOUT (self));
- g_assert (GTK_IS_GESTURE_PAN (pan));
- g_assert (priv->drag_child != NULL);
-
- /*
- * NOTE: This is trickier than it looks, so I choose to be
- * very verbose. Feel free to clean it up.
- */
-
- gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
-
- switch (direction)
- {
- case GTK_PAN_DIRECTION_LEFT:
- if (priv->drag_child->type == GTK_POS_LEFT)
- target_position = priv->drag_position - offset;
- else if (priv->drag_child->type == GTK_POS_RIGHT)
- target_position = priv->drag_position + offset;
- break;
-
- case GTK_PAN_DIRECTION_RIGHT:
- if (priv->drag_child->type == GTK_POS_LEFT)
- target_position = priv->drag_position + offset;
- else if (priv->drag_child->type == GTK_POS_RIGHT)
- target_position = priv->drag_position - offset;
- break;
-
- case GTK_PAN_DIRECTION_UP:
- if (priv->drag_child->type == GTK_POS_BOTTOM)
- target_position = priv->drag_position + offset;
- break;
-
- case GTK_PAN_DIRECTION_DOWN:
- if (priv->drag_child->type == GTK_POS_BOTTOM)
- target_position = priv->drag_position - offset;
- break;
-
- default:
- g_assert_not_reached ();
- }
-
- center_min_width = MAX (priv->children [GTK_POS_BOTTOM].min_width,
- priv->children [GTK_POS_TOP].min_width);
- left_max = alloc.width - priv->children [GTK_POS_RIGHT].alloc.width - center_min_width;
- right_max = alloc.width - priv->children [GTK_POS_LEFT].position - center_min_width;
- bottom_max = alloc.height - priv->children [GTK_POS_TOP].min_height;
-
- switch (priv->drag_child->type)
- {
- case GTK_POS_LEFT:
- target_position = MIN (left_max, target_position);
- break;
-
- case GTK_POS_RIGHT:
- target_position = MIN (right_max, target_position);
- break;
-
- case GTK_POS_BOTTOM:
- target_position = MIN (bottom_max, target_position);
- break;
-
- case GTK_POS_TOP:
- default:
- g_assert_not_reached ();
- }
-
- priv->drag_child->position = MAX (0, target_position);
-
- gtk_widget_queue_allocate (GTK_WIDGET (self));
-}
-
-static GtkGesture *
-ide_layout_create_pan_gesture (IdeLayout *self,
- GtkOrientation orientation)
-{
- GtkGesture *gesture;
-
- g_assert (IDE_IS_LAYOUT (self));
-
- gesture = gtk_gesture_pan_new (GTK_WIDGET (self), orientation);
- 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 (ide_layout_drag_begin_cb),
- self,
- G_CONNECT_SWAPPED);
- g_signal_connect_object (gesture,
- "drag-end",
- G_CALLBACK (ide_layout_drag_end_cb),
- self,
- G_CONNECT_SWAPPED);
- g_signal_connect_object (gesture,
- "pan",
- G_CALLBACK (ide_layout_pan_cb),
- self,
- G_CONNECT_SWAPPED);
-
- return gesture;
-}
-
-static void
-ide_layout_realize (GtkWidget *widget)
-{
- IdeLayout *self = (IdeLayout *)widget;
-
- g_assert (IDE_IS_LAYOUT (self));
-
- GTK_WIDGET_CLASS (ide_layout_parent_class)->realize (widget);
-
- ide_layout_create_handle_window (self, GTK_POS_LEFT);
- ide_layout_create_handle_window (self, GTK_POS_RIGHT);
- ide_layout_create_handle_window (self, GTK_POS_BOTTOM);
-}
-
-static void
-ide_layout_unrealize (GtkWidget *widget)
-{
- IdeLayout *self = (IdeLayout *)widget;
-
- g_assert (IDE_IS_LAYOUT (self));
-
- ide_layout_destroy_handle_window (self, GTK_POS_LEFT);
- ide_layout_destroy_handle_window (self, GTK_POS_RIGHT);
- ide_layout_destroy_handle_window (self, GTK_POS_BOTTOM);
-
- GTK_WIDGET_CLASS (ide_layout_parent_class)->unrealize (widget);
-}
-
-static void
-ide_layout_map (GtkWidget *widget)
-{
- IdeLayout *self = (IdeLayout *)widget;
- IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
- gint i;
-
- g_assert (IDE_IS_LAYOUT (self));
-
- GTK_WIDGET_CLASS (ide_layout_parent_class)->map (widget);
-
- for (i = 0; i < G_N_ELEMENTS (priv->children); i++)
- {
- IdeLayoutChild *child = &priv->children [i];
-
- if (child->handle != NULL)
- gdk_window_show (child->handle);
- }
-}
-
-static void
-ide_layout_unmap (GtkWidget *widget)
-{
- IdeLayout *self = (IdeLayout *)widget;
- IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
- int i;
-
- g_assert (IDE_IS_LAYOUT (self));
-
- for (i = 0; i < G_N_ELEMENTS (priv->children); i++)
- {
- IdeLayoutChild *child = &priv->children [i];
-
- if (child->handle != NULL)
- gdk_window_hide (child->handle);
- }
-
- GTK_WIDGET_CLASS (ide_layout_parent_class)->unmap (widget);
-}
-
-static GObject *
-ide_layout_get_internal_child (GtkBuildable *buildable,
- GtkBuilder *builder,
- const gchar *childname)
-{
- IdeLayout *self = (IdeLayout *)buildable;
-
- g_assert (IDE_IS_LAYOUT (self));
+static GParamSpec *properties [LAST_PROP];
- /*
- * Override default get_internal_child to handle RTL vs LTR.
- */
- if (ide_str_equal0 (childname, "left_pane"))
- return G_OBJECT (ide_layout_get_left_pane (self));
- else if (ide_str_equal0 (childname, "right_pane"))
- return G_OBJECT (ide_layout_get_right_pane (self));
-
- return ide_layout_parent_buildable_iface->get_internal_child (buildable, builder, childname);
-}
-
-static void
-ide_layout_grab_focus (GtkWidget *widget)
+static GtkWidget *
+ide_layout_create_edge (PnlDockBin *dock)
{
- IdeLayout *self = (IdeLayout *)widget;
- IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
+ g_assert (IDE_IS_LAYOUT (dock));
- g_assert (IDE_IS_LAYOUT (self));
-
- gtk_widget_grab_focus (priv->children [GTK_POS_TOP].widget);
-}
-
-static void
-ide_layout_active_view_weak_cb (IdeLayout *self,
- GtkWidget *where_view_was)
-{
- IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-
- g_assert (IDE_IS_LAYOUT (self));
-
- if (where_view_was == priv->active_view)
- {
- priv->active_view = NULL;
- g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ACTIVE_VIEW]);
- }
-}
-
-static void
-ide_layout_set_active_view (IdeLayout *self,
- GtkWidget *active_view)
-{
- IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-
- g_assert (IDE_IS_LAYOUT (self));
- g_assert (!active_view || GTK_IS_WIDGET (active_view));
-
- if (active_view != priv->active_view)
- {
- if (priv->active_view != NULL)
- {
- g_object_weak_unref (G_OBJECT (priv->active_view),
- (GWeakNotify)ide_layout_active_view_weak_cb,
- self);
- priv->active_view = NULL;
- }
-
- if (active_view != NULL)
- {
- priv->active_view = active_view;
- g_object_weak_ref (G_OBJECT (priv->active_view),
- (GWeakNotify)ide_layout_active_view_weak_cb,
- self);
- }
-
- g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ACTIVE_VIEW]);
- }
-}
-
-static void
-ide_layout_toplevel_set_focus (IdeLayout *self,
- GtkWidget *widget,
- GtkWidget *toplevel)
-{
- g_assert (IDE_IS_LAYOUT (self));
-
- if (widget != NULL && !IDE_IS_LAYOUT_VIEW (widget))
- widget = gtk_widget_get_ancestor (widget, IDE_TYPE_LAYOUT_VIEW);
-
- if (widget != NULL)
- ide_layout_set_active_view (self, widget);
-}
-
-static void
-ide_layout_hierarchy_changed (GtkWidget *widget,
- GtkWidget *old_toplevel)
-{
- IdeLayout *self = (IdeLayout *)widget;
- IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
- GtkWidget *toplevel;
-
- g_assert (IDE_IS_LAYOUT (self));
-
- if ((old_toplevel != NULL) && (priv->focus_handler != 0))
- {
- g_signal_handler_disconnect (old_toplevel, priv->focus_handler);
- priv->focus_handler = 0;
- ide_clear_weak_pointer (&priv->active_view);
- }
-
- toplevel = gtk_widget_get_toplevel (widget);
-
- if (GTK_IS_WINDOW (toplevel))
- priv->focus_handler =
- g_signal_connect_swapped (toplevel,
- "set-focus",
- G_CALLBACK (ide_layout_toplevel_set_focus),
- self);
-}
-
-static void
-ide_layout_finalize (GObject *object)
-{
- IdeLayout *self = (IdeLayout *)object;
- IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
- gsize i;
-
- for (i = 0; i < G_N_ELEMENTS (priv->children); i++)
- {
- IdeLayoutChild *child = &priv->children [i];
-
- ide_clear_weak_pointer (&child->animation);
- g_clear_object (&child->adjustment);
- }
-
- if (priv->active_view)
- {
- g_object_weak_unref (G_OBJECT (priv->active_view),
- (GWeakNotify)ide_layout_active_view_weak_cb,
- self);
- priv->active_view = NULL;
- }
-
- g_clear_object (&priv->pan_gesture);
- g_clear_object (&priv->actions);
-
- G_OBJECT_CLASS (ide_layout_parent_class)->finalize (object);
+ return g_object_new (IDE_TYPE_LAYOUT_PANE,
+ "visible", TRUE,
+ "reveal-child", FALSE,
+ NULL);
}
static void
@@ -1146,92 +59,24 @@ ide_layout_get_property (GObject *object,
g_value_set_object (value, ide_layout_get_active_view (self));
break;
- case PROP_LEFT_PANE:
- g_value_set_object (value, ide_layout_get_left_pane (self));
- break;
-
- case PROP_RIGHT_PANE:
- g_value_set_object (value, ide_layout_get_right_pane (self));
- break;
-
- case PROP_BOTTOM_PANE:
- g_value_set_object (value, ide_layout_get_bottom_pane (self));
- break;
-
- case PROP_CONTENT_PANE:
- g_value_set_object (value, ide_layout_get_content_pane (self));
- break;
-
default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
}
}
static void
-buildable_init_iface (GtkBuildableIface *iface)
-{
- ide_layout_parent_buildable_iface = g_type_interface_peek_parent (iface);
-
- iface->get_internal_child = ide_layout_get_internal_child;
-}
-
-static void
ide_layout_class_init (IdeLayoutClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
- GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
- GtkOverlayClass *overlay_class = GTK_OVERLAY_CLASS (klass);
+ PnlDockBinClass *dock_bin_class = PNL_DOCK_BIN_CLASS (klass);
- object_class->finalize = ide_layout_finalize;
object_class->get_property = ide_layout_get_property;
- widget_class->get_preferred_height = ide_layout_get_preferred_height;
- widget_class->get_preferred_width = ide_layout_get_preferred_width;
- widget_class->get_request_mode = ide_layout_get_request_mode;
- widget_class->hierarchy_changed = ide_layout_hierarchy_changed;
- widget_class->map = ide_layout_map;
- widget_class->unmap = ide_layout_unmap;
- widget_class->realize = ide_layout_realize;
- widget_class->unrealize = ide_layout_unrealize;
- widget_class->size_allocate = ide_layout_size_allocate;
- widget_class->grab_focus = ide_layout_grab_focus;
+ dock_bin_class->create_edge = ide_layout_create_edge;
gtk_widget_class_set_css_name (widget_class, "layout");
- container_class->get_child_property = ide_layout_get_child_property;
- container_class->set_child_property = ide_layout_set_child_property;
-
- overlay_class->get_child_position = ide_layout_get_child_position;
-
- properties [PROP_LEFT_PANE] =
- g_param_spec_object ("left-pane",
- "Left Pane",
- "The left workspace pane.",
- GTK_TYPE_WIDGET,
- (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
-
- properties [PROP_RIGHT_PANE] =
- g_param_spec_object ("right-pane",
- "Right Pane",
- "The right workspace pane.",
- GTK_TYPE_WIDGET,
- (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
-
- properties [PROP_BOTTOM_PANE] =
- g_param_spec_object ("bottom-pane",
- "Bottom Pane",
- "The bottom workspace pane.",
- GTK_TYPE_WIDGET,
- (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
-
- properties [PROP_CONTENT_PANE] =
- g_param_spec_object ("content-pane",
- "Content Pane",
- "The content workspace pane.",
- GTK_TYPE_WIDGET,
- (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
-
properties [PROP_ACTIVE_VIEW] =
g_param_spec_object ("active-view",
"Active View",
@@ -1240,205 +85,11 @@ ide_layout_class_init (IdeLayoutClass *klass)
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, LAST_PROP, properties);
-
- child_properties [CHILD_PROP_POSITION] =
- g_param_spec_uint ("position",
- "Position",
- "The position of the pane relative to its edge.",
- 0, G_MAXUINT,
- 0,
- (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
- gtk_container_class_install_child_property (container_class, CHILD_PROP_POSITION,
- child_properties [CHILD_PROP_POSITION]);
-
- child_properties [CHILD_PROP_REVEAL] =
- g_param_spec_boolean ("reveal",
- "Reveal",
- "If the pane should be revealed.",
- TRUE,
- (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
- gtk_container_class_install_child_property (container_class, CHILD_PROP_REVEAL,
- child_properties [CHILD_PROP_REVEAL]);
-
- gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/builder/ui/ide-layout.ui");
-
- gtk_widget_class_bind_template_child_full (widget_class, "bottom_pane", TRUE,
- G_PRIVATE_OFFSET (IdeLayout, children[GTK_POS_BOTTOM].widget));
- gtk_widget_class_bind_template_child_full (widget_class, "content_pane", TRUE,
- G_PRIVATE_OFFSET (IdeLayout, children[GTK_POS_TOP].widget));
- gtk_widget_class_bind_template_child_full (widget_class, "left_pane", TRUE,
- G_PRIVATE_OFFSET (IdeLayout, children[GTK_POS_LEFT].widget));
- gtk_widget_class_bind_template_child_full (widget_class, "right_pane", TRUE,
- G_PRIVATE_OFFSET (IdeLayout, children[GTK_POS_RIGHT].widget));
-}
-
-static void
-ide_layout_activate_left (GSimpleAction *action,
- GVariant *param,
- gpointer user_data)
-{
- IdeLayout *self = user_data;
- GtkWidget *child;
- gboolean reveal;
-
- g_assert (G_IS_SIMPLE_ACTION (action));
- g_assert (IDE_IS_LAYOUT (self));
-
- child = ide_layout_get_left_pane (self);
- reveal = g_variant_get_boolean (param);
- gtk_container_child_set (GTK_CONTAINER (self), child, "reveal", reveal, NULL);
}
static void
-ide_layout_activate_right (GSimpleAction *action,
- GVariant *param,
- gpointer user_data)
-{
- IdeLayout *self = user_data;
- GtkWidget *child;
- gboolean reveal;
-
- g_assert (G_IS_SIMPLE_ACTION (action));
- g_assert (IDE_IS_LAYOUT (self));
-
- child = ide_layout_get_right_pane (self);
- reveal = g_variant_get_boolean (param);
- gtk_container_child_set (GTK_CONTAINER (self), child, "reveal", reveal, NULL);
-}
-
-static void
-ide_layout_activate_bottom (GSimpleAction *action,
- GVariant *param,
- gpointer user_data)
-{
- IdeLayout *self = user_data;
- GtkWidget *child;
- gboolean reveal;
-
- g_assert (G_IS_SIMPLE_ACTION (action));
- g_assert (IDE_IS_LAYOUT (self));
-
- child = ide_layout_get_bottom_pane (self);
- reveal = g_variant_get_boolean (param);
- gtk_container_child_set (GTK_CONTAINER (self), child, "reveal", reveal, NULL);
-}
-
-static const GActionEntry action_entries[] = {
- { "left", NULL, NULL, "true", ide_layout_activate_left },
- { "right", NULL, NULL, "true", ide_layout_activate_right },
- { "bottom", NULL, NULL, "true", ide_layout_activate_bottom },
-};
-
-static void
ide_layout_init (IdeLayout *self)
{
- IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-
- priv->children [GTK_POS_LEFT].type = GTK_POS_LEFT;
- priv->children [GTK_POS_LEFT].reveal = TRUE;
- priv->children [GTK_POS_LEFT].position = 250;
- priv->children [GTK_POS_LEFT].adjustment = ide_layout_create_adjustment (self);
- priv->children [GTK_POS_LEFT].cursor_type = GDK_SB_H_DOUBLE_ARROW;
-
- priv->children [GTK_POS_RIGHT].type = GTK_POS_RIGHT;
- priv->children [GTK_POS_RIGHT].reveal = TRUE;
- priv->children [GTK_POS_RIGHT].position = 250;
- priv->children [GTK_POS_RIGHT].adjustment = ide_layout_create_adjustment (self);
- priv->children [GTK_POS_RIGHT].cursor_type = GDK_SB_H_DOUBLE_ARROW;
-
- priv->children [GTK_POS_BOTTOM].type = GTK_POS_BOTTOM;
- priv->children [GTK_POS_BOTTOM].reveal = TRUE;
- priv->children [GTK_POS_BOTTOM].position = 150;
- priv->children [GTK_POS_BOTTOM].adjustment = ide_layout_create_adjustment (self);
- priv->children [GTK_POS_BOTTOM].cursor_type = GDK_SB_V_DOUBLE_ARROW;
-
- priv->children [GTK_POS_TOP].type = GTK_POS_TOP;
- priv->children [GTK_POS_TOP].reveal = TRUE;
- priv->children [GTK_POS_TOP].adjustment = ide_layout_create_adjustment (self);
-
- priv->pan_gesture = ide_layout_create_pan_gesture (self, GTK_ORIENTATION_HORIZONTAL);
-
- gtk_widget_init_template (GTK_WIDGET (self));
-
- priv->actions = G_ACTION_MAP (g_simple_action_group_new ());
- g_action_map_add_action_entries (G_ACTION_MAP (priv->actions),
- action_entries,
- G_N_ELEMENTS (action_entries),
- self);
- gtk_widget_insert_action_group (GTK_WIDGET (self), "panels",
- G_ACTION_GROUP (priv->actions));
-}
-
-GtkWidget *
-ide_layout_new (void)
-{
- return g_object_new (IDE_TYPE_LAYOUT, NULL);
-}
-
-/**
- * ide_layout_get_left_pane:
- *
- * Returns: (transfer none): A #GtkWidget.
- */
-GtkWidget *
-ide_layout_get_left_pane (IdeLayout *self)
-{
- IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-
- g_return_val_if_fail (IDE_IS_LAYOUT (self), NULL);
-
- if (gtk_widget_get_state_flags (GTK_WIDGET (self)) & GTK_STATE_FLAG_DIR_RTL)
- return priv->children [GTK_POS_RIGHT].widget;
- else
- return priv->children [GTK_POS_LEFT].widget;
-}
-
-/**
- * ide_layout_get_right_pane:
- *
- * Returns: (transfer none): A #GtkWidget.
- */
-GtkWidget *
-ide_layout_get_right_pane (IdeLayout *self)
-{
- IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-
- g_return_val_if_fail (IDE_IS_LAYOUT (self), NULL);
-
- if (gtk_widget_get_state_flags (GTK_WIDGET (self)) & GTK_STATE_FLAG_DIR_RTL)
- return priv->children [GTK_POS_LEFT].widget;
- else
- return priv->children [GTK_POS_RIGHT].widget;
-}
-
-/**
- * ide_layout_get_bottom_pane:
- *
- * Returns: (transfer none): A #GtkWidget.
- */
-GtkWidget *
-ide_layout_get_bottom_pane (IdeLayout *self)
-{
- IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-
- g_return_val_if_fail (IDE_IS_LAYOUT (self), NULL);
-
- return priv->children [GTK_POS_BOTTOM].widget;
-}
-
-/**
- * ide_layout_get_content_pane:
- *
- * Returns: (transfer none): A #GtkWidget.
- */
-GtkWidget *
-ide_layout_get_content_pane (IdeLayout *self)
-{
- IdeLayoutPrivate *priv = ide_layout_get_instance_private (self);
-
- g_return_val_if_fail (IDE_IS_LAYOUT (self), NULL);
-
- return priv->children [GTK_POS_TOP].widget;
}
/**
diff --git a/libide/ide-layout.h b/libide/ide-layout.h
index 98698d6..409427c 100644
--- a/libide/ide-layout.h
+++ b/libide/ide-layout.h
@@ -19,27 +19,20 @@
#ifndef IDE_LAYOUT_H
#define IDE_LAYOUT_H
-#include <gtk/gtk.h>
-
-#include "ide-layout-pane.h"
+#include <pnl.h>
G_BEGIN_DECLS
#define IDE_TYPE_LAYOUT (ide_layout_get_type())
-G_DECLARE_DERIVABLE_TYPE (IdeLayout, ide_layout, IDE, LAYOUT, GtkOverlay)
+G_DECLARE_DERIVABLE_TYPE (IdeLayout, ide_layout, IDE, LAYOUT, PnlDockBin)
struct _IdeLayoutClass
{
- GtkOverlayClass parent_instance;
+ PnlDockBinClass parent_class;
};
-GtkWidget *ide_layout_new (void);
-GtkWidget *ide_layout_get_left_pane (IdeLayout *self);
-GtkWidget *ide_layout_get_right_pane (IdeLayout *self);
-GtkWidget *ide_layout_get_bottom_pane (IdeLayout *self);
-GtkWidget *ide_layout_get_content_pane (IdeLayout *self);
-GtkWidget *ide_layout_get_active_view (IdeLayout *self);
+GtkWidget *ide_layout_get_active_view (IdeLayout *self);
G_END_DECLS
diff --git a/libide/ide-workbench.c b/libide/ide-workbench.c
index 9459273..3d986be 100644
--- a/libide/ide-workbench.c
+++ b/libide/ide-workbench.c
@@ -757,11 +757,7 @@ ide_workbench_show_parents (GtkWidget *widget)
parent = gtk_widget_get_parent (widget);
if (IDE_IS_LAYOUT_PANE (widget))
- {
- gtk_container_child_set (GTK_CONTAINER (parent), widget,
- "reveal", TRUE,
- NULL);
- }
+ pnl_dock_revealer_set_reveal_child (PNL_DOCK_REVEALER (widget), TRUE);
if (IDE_IS_PERSPECTIVE (widget))
ide_workbench_set_visible_perspective (ide_widget_get_workbench (widget),
diff --git a/libide/resources/libide.gresource.xml b/libide/resources/libide.gresource.xml
index be892d8..658dcb7 100644
--- a/libide/resources/libide.gresource.xml
+++ b/libide/resources/libide.gresource.xml
@@ -57,7 +57,6 @@
<file compressed="true"
alias="ide-genesis-perspective.ui">../../data/ui/ide-genesis-perspective.ui</file>
<file compressed="true"
alias="ide-greeter-perspective.ui">../../data/ui/ide-greeter-perspective.ui</file>
<file compressed="true"
alias="ide-greeter-project-row.ui">../../data/ui/ide-greeter-project-row.ui</file>
- <file compressed="true" alias="ide-layout.ui">../../data/ui/ide-layout.ui</file>
<file compressed="true" alias="ide-layout-tab.ui">../../data/ui/ide-layout-tab.ui</file>
<file compressed="true" alias="ide-layout-tab-bar.ui">../../data/ui/ide-layout-tab-bar.ui</file>
<file compressed="true" alias="ide-layout-pane.ui">../../data/ui/ide-layout-pane.ui</file>
diff --git a/plugins/build-tools/gbp-build-workbench-addin.c b/plugins/build-tools/gbp-build-workbench-addin.c
index f074b71..50571ea 100644
--- a/plugins/build-tools/gbp-build-workbench-addin.c
+++ b/plugins/build-tools/gbp-build-workbench-addin.c
@@ -262,14 +262,14 @@ gbp_build_workbench_addin_load (IdeWorkbenchAddin *addin,
configuration = ide_configuration_manager_get_current (configuration_manager);
editor = ide_workbench_get_perspective_by_name (workbench, "editor");
- pane = ide_layout_get_right_pane (IDE_LAYOUT (editor));
+ pane = pnl_dock_bin_get_right_edge (PNL_DOCK_BIN (editor));
self->panel = g_object_new (GBP_TYPE_BUILD_PANEL,
"configuration-manager", configuration_manager,
"visible", TRUE,
NULL);
ide_layout_pane_add_page (IDE_LAYOUT_PANE (pane), GTK_WIDGET (self->panel), _("Build"), NULL);
- pane = ide_layout_get_bottom_pane (IDE_LAYOUT (editor));
+ pane = pnl_dock_bin_get_bottom_edge (PNL_DOCK_BIN (editor));
self->build_log_panel = g_object_new (GBP_TYPE_BUILD_LOG_PANEL, NULL);
ide_layout_pane_add_page (IDE_LAYOUT_PANE (pane), GTK_WIDGET (self->build_log_panel),
_("Build Output"), NULL);
@@ -307,7 +307,7 @@ gbp_build_workbench_addin_unload (IdeWorkbenchAddin *addin,
gtk_widget_insert_action_group (GTK_WIDGET (workbench), "build-tools", NULL);
editor = ide_workbench_get_perspective_by_name (workbench, "editor");
- pane = ide_layout_get_right_pane (IDE_LAYOUT (editor));
+ pane = pnl_dock_bin_get_right_edge (PNL_DOCK_BIN (editor));
ide_layout_pane_remove_page (IDE_LAYOUT_PANE (pane), GTK_WIDGET (self->panel));
}
diff --git a/plugins/devhelp/gbp-devhelp-editor-view-addin.c b/plugins/devhelp/gbp-devhelp-editor-view-addin.c
index 73fb07b..e67047e 100644
--- a/plugins/devhelp/gbp-devhelp-editor-view-addin.c
+++ b/plugins/devhelp/gbp-devhelp-editor-view-addin.c
@@ -45,7 +45,7 @@ request_documentation_cb (GbpDevhelpEditorViewAddin *self,
if (layout == NULL)
return;
- pane = ide_layout_get_right_pane (IDE_LAYOUT (layout));
+ pane = pnl_dock_bin_get_right_edge (PNL_DOCK_BIN (layout));
panel = ide_widget_find_child_typed (pane, GBP_TYPE_DEVHELP_PANEL);
gbp_devhelp_panel_focus_search (GBP_DEVHELP_PANEL (panel), word);
}
diff --git a/plugins/devhelp/gbp-devhelp-search-provider.c b/plugins/devhelp/gbp-devhelp-search-provider.c
index cc8dbf5..e1e18b5 100644
--- a/plugins/devhelp/gbp-devhelp-search-provider.c
+++ b/plugins/devhelp/gbp-devhelp-search-provider.c
@@ -174,7 +174,7 @@ gbp_devhelp_search_provider_activate (IdeSearchProvider *provider,
editor = ide_workbench_get_perspective_by_name (IDE_WORKBENCH (toplevel), "editor");
g_assert (editor != NULL);
- pane = ide_layout_get_right_pane (IDE_LAYOUT (editor));
+ pane = pnl_dock_bin_get_right_edge (PNL_DOCK_BIN (editor));
g_assert (pane != NULL);
panel = ide_widget_find_child_typed (pane, GBP_TYPE_DEVHELP_PANEL);
diff --git a/plugins/devhelp/gbp-devhelp-workbench-addin.c b/plugins/devhelp/gbp-devhelp-workbench-addin.c
index 2844720..dfb9f32 100644
--- a/plugins/devhelp/gbp-devhelp-workbench-addin.c
+++ b/plugins/devhelp/gbp-devhelp-workbench-addin.c
@@ -79,7 +79,7 @@ gbp_devhelp_workbench_addin_load (IdeWorkbenchAddin *addin,
perspective = ide_workbench_get_perspective_by_name (workbench, "editor");
g_assert (IDE_IS_LAYOUT (perspective));
- pane = ide_layout_get_right_pane (IDE_LAYOUT (perspective));
+ pane = pnl_dock_bin_get_right_edge (PNL_DOCK_BIN (perspective));
g_assert (IDE_IS_LAYOUT_PANE (pane));
self->panel = g_object_new (GBP_TYPE_DEVHELP_PANEL,
@@ -114,7 +114,7 @@ gbp_devhelp_workbench_addin_unload (IdeWorkbenchAddin *addin,
perspective = ide_workbench_get_perspective_by_name (workbench, "editor");
g_assert (IDE_IS_LAYOUT (perspective));
- pane = ide_layout_get_right_pane (IDE_LAYOUT (perspective));
+ pane = pnl_dock_bin_get_right_edge (PNL_DOCK_BIN (perspective));
g_assert (IDE_IS_LAYOUT_PANE (pane));
ide_layout_pane_remove_page (IDE_LAYOUT_PANE (pane), GTK_WIDGET (self->panel));
diff --git a/plugins/project-tree/gb-project-tree-addin.c b/plugins/project-tree/gb-project-tree-addin.c
index 359a6dd..d6e008f 100644
--- a/plugins/project-tree/gb-project-tree-addin.c
+++ b/plugins/project-tree/gb-project-tree-addin.c
@@ -59,7 +59,7 @@ gb_project_tree_addin_grid_empty (GbProjectTreeAddin *self,
layout = gtk_widget_get_ancestor (GTK_WIDGET (grid), IDE_TYPE_LAYOUT);
g_assert (layout != NULL);
- pane = ide_layout_get_left_pane (IDE_LAYOUT (layout));
+ pane = pnl_dock_bin_get_left_edge (PNL_DOCK_BIN (layout));
g_assert (pane != NULL);
gtk_container_child_set (GTK_CONTAINER (layout), GTK_WIDGET (pane),
@@ -84,10 +84,10 @@ gb_project_tree_addin_load (IdeWorkbenchAddin *addin,
editor = ide_workbench_get_perspective_by_name (workbench, "editor");
g_assert (editor != NULL);
- pane = ide_layout_get_left_pane (IDE_LAYOUT (editor));
+ pane = pnl_dock_bin_get_left_edge (PNL_DOCK_BIN (editor));
g_assert (pane != NULL);
- content = ide_layout_get_content_pane (IDE_LAYOUT (editor));
+ content = pnl_dock_bin_get_center_widget (PNL_DOCK_BIN (editor));
g_assert (content != NULL);
grid = ide_widget_find_child_typed (content, IDE_TYPE_LAYOUT_GRID);
@@ -130,7 +130,7 @@ gb_project_tree_addin_unload (IdeWorkbenchAddin *addin,
editor = ide_workbench_get_perspective_by_name (workbench, "editor");
g_assert (editor != NULL);
- pane = ide_layout_get_left_pane (IDE_LAYOUT (editor));
+ pane = pnl_dock_bin_get_left_edge (PNL_DOCK_BIN (editor));
g_assert (pane != NULL);
ide_layout_pane_remove_page (IDE_LAYOUT_PANE (pane),
diff --git a/plugins/symbol-tree/symbol-tree.c b/plugins/symbol-tree/symbol-tree.c
index 10c3ef2..b21626d 100644
--- a/plugins/symbol-tree/symbol-tree.c
+++ b/plugins/symbol-tree/symbol-tree.c
@@ -74,7 +74,7 @@ symbol_tree_load (IdeWorkbenchAddin *addin,
self,
G_CONNECT_SWAPPED);
- right_pane = ide_layout_get_right_pane (IDE_LAYOUT (perspective));
+ right_pane = pnl_dock_bin_get_right_edge (PNL_DOCK_BIN (perspective));
g_assert (right_pane != NULL);
self->panel = g_object_new (SYMBOL_TYPE_TREE_PANEL, NULL);
@@ -106,7 +106,7 @@ symbol_tree_unload (IdeWorkbenchAddin *addin,
perspective = ide_workbench_get_perspective_by_name (workbench, "editor");
g_assert (IDE_IS_LAYOUT (perspective));
- pane = ide_layout_get_right_pane (IDE_LAYOUT (perspective));
+ pane = pnl_dock_bin_get_right_edge (PNL_DOCK_BIN (perspective));
g_assert (IDE_IS_LAYOUT_PANE (pane));
ide_layout_pane_remove_page (IDE_LAYOUT_PANE (pane), GTK_WIDGET (self->panel));
diff --git a/plugins/sysmon/gb-sysmon-addin.c b/plugins/sysmon/gb-sysmon-addin.c
index 5a93899..f18ae40 100644
--- a/plugins/sysmon/gb-sysmon-addin.c
+++ b/plugins/sysmon/gb-sysmon-addin.c
@@ -53,7 +53,7 @@ gb_sysmon_addin_load (IdeWorkbenchAddin *addin,
g_assert (editor != NULL);
g_assert (IDE_IS_LAYOUT (editor));
- pane = ide_layout_get_bottom_pane (IDE_LAYOUT (editor));
+ pane = pnl_dock_bin_get_bottom_edge (PNL_DOCK_BIN (editor));
panel = g_object_new (GB_TYPE_SYSMON_PANEL,
"visible", TRUE,
NULL);
diff --git a/plugins/terminal/gb-terminal-workbench-addin.c b/plugins/terminal/gb-terminal-workbench-addin.c
index 2845e18..2d3fec9 100644
--- a/plugins/terminal/gb-terminal-workbench-addin.c
+++ b/plugins/terminal/gb-terminal-workbench-addin.c
@@ -93,7 +93,7 @@ gb_terminal_workbench_addin_load (IdeWorkbenchAddin *addin,
perspective = ide_workbench_get_perspective_by_name (workbench, "editor");
g_assert (IDE_IS_LAYOUT (perspective));
- bottom_pane = ide_layout_get_bottom_pane (IDE_LAYOUT (perspective));
+ bottom_pane = pnl_dock_bin_get_bottom_edge (PNL_DOCK_BIN (perspective));
ide_layout_pane_add_page (IDE_LAYOUT_PANE (bottom_pane),
GTK_WIDGET (self->panel_terminal),
_("Terminal"),
diff --git a/plugins/todo/todo_plugin/__init__.py b/plugins/todo/todo_plugin/__init__.py
index b95a9dd..61cdb18 100644
--- a/plugins/todo/todo_plugin/__init__.py
+++ b/plugins/todo/todo_plugin/__init__.py
@@ -56,7 +56,7 @@ class TodoWorkbenchAddin(GObject.Object, Ide.WorkbenchAddin):
# Create our panel to display results
self.panel = TodoPanel(workdir, visible=True)
editor = workbench.get_perspective_by_name('editor')
- pane = editor.get_bottom_pane()
+ pane = editor.get_bottom_edge()
pane.add_page(self.panel, _("Todo"), None)
# Mine the directory in a background thread
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]