summaryrefslogtreecommitdiffstats
path: root/subprojects/libhandy/src/hdy-header-group.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--subprojects/libhandy/src/hdy-header-group.c1115
1 files changed, 1115 insertions, 0 deletions
diff --git a/subprojects/libhandy/src/hdy-header-group.c b/subprojects/libhandy/src/hdy-header-group.c
new file mode 100644
index 0000000..e8287fa
--- /dev/null
+++ b/subprojects/libhandy/src/hdy-header-group.c
@@ -0,0 +1,1115 @@
+/*
+ * Copyright (C) 2018 Purism SPC
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+
+#include "hdy-header-group.h"
+
+/**
+ * SECTION:hdy-header-group
+ * @short_description: An object handling composite title bars.
+ * @Title: HdyHeaderGroup
+ * @See_also: #GtkHeaderBar, #HdyHeaderBar, #HdyLeaflet
+ *
+ * The #HdyHeaderGroup object handles the header bars of a composite title bar.
+ * It splits the window decoration across the header bars, giving the left side
+ * of the decorations to the leftmost header bar, and the right side of the
+ * decorations to the rightmost header bar.
+ * See hdy_header_bar_set_decoration_layout().
+ *
+ * The #HdyHeaderGroup:decorate-all property can be used in conjunction with
+ * #HdyLeaflet:folded when the title bar is split across the pages of a
+ * #HdyLeaflet to automatically display the decorations on all the pages when
+ * the leaflet is folded.
+ *
+ * You can nest header groups, which is convenient when you nest leaflets too:
+ * |[
+ * <object class="HdyHeaderGroup" id="inner_header_group">
+ * <property name="decorate-all" bind-source="inner_leaflet" bind-property="folded" bind-flags="sync-create"/>
+ * <headerbars>
+ * <headerbar name="inner_header_bar_1"/>
+ * <headerbar name="inner_header_bar_2"/>
+ * </headerbars>
+ * </object>
+ * <object class="HdyHeaderGroup" id="outer_header_group">
+ * <property name="decorate-all" bind-source="outer_leaflet" bind-property="folded" bind-flags="sync-create"/>
+ * <headerbars>
+ * <headerbar name="inner_header_group"/>
+ * <headerbar name="outer_header_bar"/>
+ * </headerbars>
+ * </object>
+ * ]|
+ *
+ * Since: 0.0.4
+ */
+
+/**
+ * HdyHeaderGroupChildType:
+ * @HDY_HEADER_GROUP_CHILD_TYPE_HEADER_BAR: The child is a #HdyHeaderBar
+ * @HDY_HEADER_GROUP_CHILD_TYPE_GTK_HEADER_BAR: The child is a #GtkHeaderBar
+ * @HDY_HEADER_GROUP_CHILD_TYPE_HEADER_GROUP: The child is a #HdyHeaderGroup
+ *
+ * This enumeration value describes the child types handled by #HdyHeaderGroup.
+ *
+ * New values may be added to this enumeration over time.
+ *
+ * Since: 1.0
+ */
+
+struct _HdyHeaderGroupChild
+{
+ GObject parent_instance;
+
+ HdyHeaderGroupChildType type;
+ GObject *object;
+};
+
+enum {
+ SIGNAL_UPDATE_DECORATION_LAYOUTS,
+ SIGNAL_LAST_SIGNAL,
+};
+
+static guint signals[SIGNAL_LAST_SIGNAL];
+
+G_DEFINE_TYPE (HdyHeaderGroupChild, hdy_header_group_child, G_TYPE_OBJECT)
+
+struct _HdyHeaderGroup
+{
+ GObject parent_instance;
+
+ GSList *children;
+ gboolean decorate_all;
+ gchar *layout;
+};
+
+static void hdy_header_group_buildable_init (GtkBuildableIface *iface);
+static gboolean hdy_header_group_buildable_custom_tag_start (GtkBuildable *buildable,
+ GtkBuilder *builder,
+ GObject *child,
+ const gchar *tagname,
+ GMarkupParser *parser,
+ gpointer *data);
+static void hdy_header_group_buildable_custom_finished (GtkBuildable *buildable,
+ GtkBuilder *builder,
+ GObject *child,
+ const gchar *tagname,
+ gpointer user_data);
+
+G_DEFINE_TYPE_WITH_CODE (HdyHeaderGroup, hdy_header_group, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
+ hdy_header_group_buildable_init))
+
+enum {
+ PROP_0,
+ PROP_DECORATE_ALL,
+ N_PROPS
+};
+
+static GParamSpec *props [N_PROPS];
+
+static void update_decoration_layouts (HdyHeaderGroup *self);
+
+static void
+object_destroyed_cb (HdyHeaderGroupChild *self,
+ GObject *object)
+{
+ g_assert (HDY_IS_HEADER_GROUP_CHILD (self));
+
+ self->object = NULL;
+
+ g_object_unref (self);
+}
+
+static void
+forward_update_decoration_layouts (HdyHeaderGroupChild *self)
+{
+ HdyHeaderGroup *header_group;
+
+ g_assert (HDY_IS_HEADER_GROUP_CHILD (self));
+
+ header_group = HDY_HEADER_GROUP (g_object_get_data (G_OBJECT (self), "header-group"));
+
+ g_assert (HDY_IS_HEADER_GROUP (header_group));
+
+ g_signal_emit (header_group, signals[SIGNAL_UPDATE_DECORATION_LAYOUTS], 0);
+
+ update_decoration_layouts (header_group);
+}
+
+static void
+hdy_header_group_child_dispose (GObject *object)
+{
+ HdyHeaderGroupChild *self = (HdyHeaderGroupChild *)object;
+
+ if (self->object) {
+
+ switch (self->type) {
+ case HDY_HEADER_GROUP_CHILD_TYPE_HEADER_BAR:
+ case HDY_HEADER_GROUP_CHILD_TYPE_GTK_HEADER_BAR:
+ g_signal_handlers_disconnect_by_func (self->object, G_CALLBACK (object_destroyed_cb), self);
+ g_signal_handlers_disconnect_by_func (self->object, G_CALLBACK (forward_update_decoration_layouts), self);
+ break;
+ case HDY_HEADER_GROUP_CHILD_TYPE_HEADER_GROUP:
+ g_object_weak_unref (self->object, (GWeakNotify) object_destroyed_cb, self);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ self->object = NULL;
+ }
+
+ G_OBJECT_CLASS (hdy_header_group_child_parent_class)->dispose (object);
+}
+
+static HdyHeaderGroupChild *
+hdy_header_group_child_new_for_header_bar (HdyHeaderBar *header_bar)
+{
+ HdyHeaderGroupChild *self;
+ gpointer header_group;
+
+ g_return_val_if_fail (HDY_IS_HEADER_BAR (header_bar), NULL);
+
+ header_group = g_object_get_data (G_OBJECT (header_bar), "header-group");
+
+ g_return_val_if_fail (header_group == NULL, NULL);
+
+ self = g_object_new (HDY_TYPE_HEADER_GROUP_CHILD, NULL);
+ self->type = HDY_HEADER_GROUP_CHILD_TYPE_HEADER_BAR;
+ self->object = G_OBJECT (header_bar);
+
+ g_signal_connect_swapped (header_bar, "destroy", G_CALLBACK (object_destroyed_cb), self);
+
+ g_signal_connect_swapped (header_bar, "map", G_CALLBACK (forward_update_decoration_layouts), self);
+ g_signal_connect_swapped (header_bar, "unmap", G_CALLBACK (forward_update_decoration_layouts), self);
+
+ return self;
+}
+
+static HdyHeaderGroupChild *
+hdy_header_group_child_new_for_gtk_header_bar (GtkHeaderBar *header_bar)
+{
+ HdyHeaderGroupChild *self;
+ gpointer header_group;
+
+ g_return_val_if_fail (GTK_IS_HEADER_BAR (header_bar), NULL);
+
+ header_group = g_object_get_data (G_OBJECT (header_bar), "header-group");
+
+ g_return_val_if_fail (header_group == NULL, NULL);
+
+ self = g_object_new (HDY_TYPE_HEADER_GROUP_CHILD, NULL);
+ self->type = HDY_HEADER_GROUP_CHILD_TYPE_GTK_HEADER_BAR;
+ self->object = G_OBJECT (header_bar);
+
+ g_signal_connect_swapped (header_bar, "destroy", G_CALLBACK (object_destroyed_cb), self);
+
+ g_signal_connect_swapped (header_bar, "map", G_CALLBACK (forward_update_decoration_layouts), self);
+ g_signal_connect_swapped (header_bar, "unmap", G_CALLBACK (forward_update_decoration_layouts), self);
+
+ return self;
+}
+
+static HdyHeaderGroupChild *
+hdy_header_group_child_new_for_header_group (HdyHeaderGroup *header_group)
+{
+ HdyHeaderGroupChild *self;
+ gpointer parent_header_group;
+
+ g_return_val_if_fail (HDY_IS_HEADER_GROUP (header_group), NULL);
+
+ parent_header_group = g_object_get_data (G_OBJECT (header_group), "header-group");
+
+ g_return_val_if_fail (parent_header_group == NULL, NULL);
+
+ self = g_object_new (HDY_TYPE_HEADER_GROUP_CHILD, NULL);
+ self->type = HDY_HEADER_GROUP_CHILD_TYPE_HEADER_GROUP;
+ self->object = G_OBJECT (header_group);
+
+ g_object_weak_unref (G_OBJECT (header_group), (GWeakNotify) object_destroyed_cb, self);
+
+ g_signal_connect_swapped (header_group, "update-decoration-layouts", G_CALLBACK (forward_update_decoration_layouts), self);
+
+ return self;
+}
+
+static void
+hdy_header_group_child_class_init (HdyHeaderGroupChildClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = hdy_header_group_child_dispose;
+}
+
+static void
+hdy_header_group_child_init (HdyHeaderGroupChild *self)
+{
+}
+
+static void
+hdy_header_group_child_set_decoration_layout (HdyHeaderGroupChild *self,
+ const gchar *layout)
+{
+ g_assert (HDY_IS_HEADER_GROUP_CHILD (self));
+
+ switch (self->type) {
+ case HDY_HEADER_GROUP_CHILD_TYPE_HEADER_BAR:
+ hdy_header_bar_set_decoration_layout (HDY_HEADER_BAR (self->object), layout);
+ break;
+ case HDY_HEADER_GROUP_CHILD_TYPE_GTK_HEADER_BAR:
+ gtk_header_bar_set_decoration_layout (GTK_HEADER_BAR (self->object), layout);
+ break;
+ case HDY_HEADER_GROUP_CHILD_TYPE_HEADER_GROUP:
+ {
+ HdyHeaderGroup *group = HDY_HEADER_GROUP (self->object);
+
+ g_free (group->layout);
+ group->layout = g_strdup (layout);
+
+ update_decoration_layouts (group);
+ }
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static gboolean
+hdy_header_group_child_get_mapped (HdyHeaderGroupChild *self)
+{
+ g_assert (HDY_IS_HEADER_GROUP_CHILD (self));
+
+ switch (self->type) {
+ case HDY_HEADER_GROUP_CHILD_TYPE_HEADER_BAR:
+ case HDY_HEADER_GROUP_CHILD_TYPE_GTK_HEADER_BAR:
+ return gtk_widget_get_mapped (GTK_WIDGET (self->object));
+ case HDY_HEADER_GROUP_CHILD_TYPE_HEADER_GROUP:
+ for (GSList *children = HDY_HEADER_GROUP (self->object)->children;
+ children != NULL;
+ children = children->next)
+ if (hdy_header_group_child_get_mapped (HDY_HEADER_GROUP_CHILD (children->data)))
+ return TRUE;
+
+ return FALSE;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static HdyHeaderGroupChild *
+get_child_for_object (HdyHeaderGroup *self,
+ gpointer object)
+{
+ GSList *children;
+
+ for (children = self->children; children != NULL; children = children->next) {
+ HdyHeaderGroupChild *child = HDY_HEADER_GROUP_CHILD (children->data);
+
+ g_assert (child);
+
+ if (child->object == object)
+ return child;
+ }
+
+ return NULL;
+}
+
+static void
+update_decoration_layouts (HdyHeaderGroup *self)
+{
+ GSList *children;
+ GtkSettings *settings;
+ HdyHeaderGroupChild *start_child = NULL, *end_child = NULL;
+ g_autofree gchar *layout = NULL;
+ g_autofree gchar *start_layout = NULL;
+ g_autofree gchar *end_layout = NULL;
+ g_auto(GStrv) ends = NULL;
+
+ g_return_if_fail (HDY_IS_HEADER_GROUP (self));
+
+ children = self->children;
+
+ if (children == NULL)
+ return;
+
+ settings = gtk_settings_get_default ();
+ if (self->layout)
+ layout = g_strdup (self->layout);
+ else
+ g_object_get (G_OBJECT (settings), "gtk-decoration-layout", &layout, NULL);
+ if (layout == NULL)
+ layout = g_strdup (":");
+
+ if (self->decorate_all) {
+ for (; children != NULL; children = children->next)
+ hdy_header_group_child_set_decoration_layout (HDY_HEADER_GROUP_CHILD (children->data), layout);
+
+ return;
+ }
+
+ for (; children != NULL; children = children->next) {
+ HdyHeaderGroupChild *child = HDY_HEADER_GROUP_CHILD (children->data);
+
+ hdy_header_group_child_set_decoration_layout (child, ":");
+
+ if (!hdy_header_group_child_get_mapped (child))
+ continue;
+
+ /* The headerbars are in reverse order in the list. */
+ start_child = child;
+ if (end_child == NULL)
+ end_child = child;
+ }
+
+ if (start_child == NULL || end_child == NULL)
+ return;
+
+ if (start_child == end_child) {
+ hdy_header_group_child_set_decoration_layout (start_child, layout);
+
+ return;
+ }
+
+ ends = g_strsplit (layout, ":", 2);
+ if (g_strv_length (ends) >= 2) {
+ start_layout = g_strdup_printf ("%s:", ends[0]);
+ end_layout = g_strdup_printf (":%s", ends[1]);
+ } else {
+ start_layout = g_strdup (":");
+ end_layout = g_strdup (":");
+ }
+ hdy_header_group_child_set_decoration_layout (start_child, start_layout);
+ hdy_header_group_child_set_decoration_layout (end_child, end_layout);
+}
+
+static void
+child_destroyed_cb (HdyHeaderGroup *self,
+ HdyHeaderGroupChild *child)
+{
+ g_assert (HDY_IS_HEADER_GROUP (self));
+ g_assert (HDY_IS_HEADER_GROUP_CHILD (child));
+ g_assert (g_slist_find (self->children, child) != NULL);
+
+ self->children = g_slist_remove (self->children, child);
+
+ g_object_unref (self);
+}
+
+HdyHeaderGroup *
+hdy_header_group_new (void)
+{
+ return g_object_new (HDY_TYPE_HEADER_GROUP, NULL);
+}
+
+static void
+hdy_header_group_add_child (HdyHeaderGroup *self,
+ HdyHeaderGroupChild *child)
+{
+ g_assert (HDY_IS_HEADER_GROUP (self));
+ g_assert (HDY_IS_HEADER_GROUP_CHILD (child));
+ g_assert (g_slist_find (self->children, child) == NULL);
+
+ self->children = g_slist_prepend (self->children, child);
+ g_object_weak_ref (G_OBJECT (child), (GWeakNotify) child_destroyed_cb, self);
+ g_object_ref (self);
+
+ update_decoration_layouts (self);
+
+ g_object_set_data (G_OBJECT (child), "header-group", self);
+}
+
+/**
+ * hdy_header_group_add_header_bar:
+ * @self: a #HdyHeaderGroup
+ * @header_bar: the #HdyHeaderBar to add
+ *
+ * Adds @header_bar to @self.
+ * When the widget is destroyed or no longer referenced elsewhere, it will
+ * be removed from the header group.
+ *
+ * Since: 1.0
+ */
+void
+hdy_header_group_add_header_bar (HdyHeaderGroup *self,
+ HdyHeaderBar *header_bar)
+{
+ HdyHeaderGroupChild *child;
+
+ g_return_if_fail (HDY_IS_HEADER_GROUP (self));
+ g_return_if_fail (HDY_IS_HEADER_BAR (header_bar));
+ g_return_if_fail (get_child_for_object (self, header_bar) == NULL);
+
+ child = hdy_header_group_child_new_for_header_bar (header_bar);
+ hdy_header_group_add_child (self, child);
+}
+
+/**
+ * hdy_header_group_add_gtk_header_bar:
+ * @self: a #HdyHeaderGroup
+ * @header_bar: the #GtkHeaderBar to add
+ *
+ * Adds @header_bar to @self.
+ * When the widget is destroyed or no longer referenced elsewhere, it will
+ * be removed from the header group.
+ *
+ * Since: 1.0
+ */
+void
+hdy_header_group_add_gtk_header_bar (HdyHeaderGroup *self,
+ GtkHeaderBar *header_bar)
+{
+ HdyHeaderGroupChild *child;
+
+ g_return_if_fail (HDY_IS_HEADER_GROUP (self));
+ g_return_if_fail (GTK_IS_HEADER_BAR (header_bar));
+ g_return_if_fail (get_child_for_object (self, header_bar) == NULL);
+
+ child = hdy_header_group_child_new_for_gtk_header_bar (header_bar);
+ hdy_header_group_add_child (self, child);
+}
+
+/**
+ * hdy_header_group_add_header_group:
+ * @self: a #HdyHeaderGroup
+ * @header_group: the #HdyHeaderGroup to add
+ *
+ * Adds @header_group to @self.
+ * When the nested group is no longer referenced elsewhere, it will be removed
+ * from the header group.
+ *
+ * Since: 1.0
+ */
+void
+hdy_header_group_add_header_group (HdyHeaderGroup *self,
+ HdyHeaderGroup *header_group)
+{
+ HdyHeaderGroupChild *child;
+
+ g_return_if_fail (HDY_IS_HEADER_GROUP (self));
+ g_return_if_fail (HDY_IS_HEADER_GROUP (header_group));
+ g_return_if_fail (get_child_for_object (self, header_group) == NULL);
+
+ child = hdy_header_group_child_new_for_header_group (header_group);
+ hdy_header_group_add_child (self, child);
+}
+
+typedef struct {
+ gchar *name;
+ gint line;
+ gint col;
+} ItemData;
+
+static void
+item_data_free (gpointer data)
+{
+ ItemData *item_data = data;
+
+ g_free (item_data->name);
+ g_free (item_data);
+}
+
+typedef struct {
+ GObject *object;
+ GtkBuilder *builder;
+ GSList *items;
+} GSListSubParserData;
+
+static void
+hdy_header_group_dispose (GObject *object)
+{
+ HdyHeaderGroup *self = (HdyHeaderGroup *)object;
+
+ g_slist_free_full (self->children, (GDestroyNotify) g_object_unref);
+ self->children = NULL;
+
+ G_OBJECT_CLASS (hdy_header_group_parent_class)->dispose (object);
+}
+
+static void
+hdy_header_group_finalize (GObject *object)
+{
+ HdyHeaderGroup *self = (HdyHeaderGroup *) object;
+
+ g_free (self->layout);
+
+ G_OBJECT_CLASS (hdy_header_group_parent_class)->finalize (object);
+}
+
+static void
+hdy_header_group_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ HdyHeaderGroup *self = HDY_HEADER_GROUP (object);
+
+ switch (prop_id) {
+ case PROP_DECORATE_ALL:
+ g_value_set_boolean (value, hdy_header_group_get_decorate_all (self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+hdy_header_group_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ HdyHeaderGroup *self = HDY_HEADER_GROUP (object);
+
+ switch (prop_id) {
+ case PROP_DECORATE_ALL:
+ hdy_header_group_set_decorate_all (self, g_value_get_boolean (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+/*< private >
+ * @builder: a #GtkBuilder
+ * @context: the #GMarkupParseContext
+ * @parent_name: the name of the expected parent element
+ * @error: return location for an error
+ *
+ * Checks that the parent element of the currently handled
+ * start tag is @parent_name and set @error if it isn't.
+ *
+ * This is intended to be called in start_element vfuncs to
+ * ensure that element nesting is as intended.
+ *
+ * Returns: %TRUE if @parent_name is the parent element
+ */
+/* This has been copied and modified from gtkbuilder.c. */
+static gboolean
+_gtk_builder_check_parent (GtkBuilder *builder,
+ GMarkupParseContext *context,
+ const gchar *parent_name,
+ GError **error)
+{
+ const GSList *stack;
+ gint line, col;
+ const gchar *parent;
+ const gchar *element;
+
+ stack = g_markup_parse_context_get_element_stack (context);
+
+ element = (const gchar *)stack->data;
+ parent = stack->next ? (const gchar *)stack->next->data : "";
+
+ if (g_str_equal (parent_name, parent) ||
+ (g_str_equal (parent_name, "object") && g_str_equal (parent, "template")))
+ return TRUE;
+
+ g_markup_parse_context_get_position (context, &line, &col);
+ g_set_error (error,
+ GTK_BUILDER_ERROR,
+ GTK_BUILDER_ERROR_INVALID_TAG,
+ ".:%d:%d Can't use <%s> here",
+ line, col, element);
+
+ return FALSE;
+}
+
+/*< private >
+ * _gtk_builder_prefix_error:
+ * @builder: a #GtkBuilder
+ * @context: the #GMarkupParseContext
+ * @error: an error
+ *
+ * Calls g_prefix_error() to prepend a filename:line:column marker
+ * to the given error. The filename is taken from @builder, and
+ * the line and column are obtained by calling
+ * g_markup_parse_context_get_position().
+ *
+ * This is intended to be called on errors returned by
+ * g_markup_collect_attributes() in a start_element vfunc.
+ */
+/* This has been copied and modified from gtkbuilder.c. */
+static void
+_gtk_builder_prefix_error (GtkBuilder *builder,
+ GMarkupParseContext *context,
+ GError **error)
+{
+ gint line, col;
+
+ g_markup_parse_context_get_position (context, &line, &col);
+ g_prefix_error (error, ".:%d:%d ", line, col);
+}
+
+/*< private >
+ * _gtk_builder_error_unhandled_tag:
+ * @builder: a #GtkBuilder
+ * @context: the #GMarkupParseContext
+ * @object: name of the object that is being handled
+ * @element_name: name of the element whose start tag is being handled
+ * @error: return location for the error
+ *
+ * Sets @error to a suitable error indicating that an @element_name
+ * tag is not expected in the custom markup for @object.
+ *
+ * This is intended to be called in a start_element vfunc.
+ */
+/* This has been copied and modified from gtkbuilder.c. */
+static void
+_gtk_builder_error_unhandled_tag (GtkBuilder *builder,
+ GMarkupParseContext *context,
+ const gchar *object,
+ const gchar *element_name,
+ GError **error)
+{
+ gint line, col;
+
+ g_markup_parse_context_get_position (context, &line, &col);
+ g_set_error (error,
+ GTK_BUILDER_ERROR,
+ GTK_BUILDER_ERROR_UNHANDLED_TAG,
+ ".:%d:%d Unsupported tag for %s: <%s>",
+ line, col,
+ object, element_name);
+}
+
+/* This has been copied and modified from gtksizegroup.c. */
+static void
+header_group_start_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **names,
+ const gchar **values,
+ gpointer user_data,
+ GError **error)
+{
+ GSListSubParserData *data = (GSListSubParserData*)user_data;
+
+ if (strcmp (element_name, "headerbar") == 0)
+ {
+ const gchar *name;
+ ItemData *item_data;
+
+ if (!_gtk_builder_check_parent (data->builder, context, "headerbars", error))
+ return;
+
+ if (!g_markup_collect_attributes (element_name, names, values, error,
+ G_MARKUP_COLLECT_STRING, "name", &name,
+ G_MARKUP_COLLECT_INVALID))
+ {
+ _gtk_builder_prefix_error (data->builder, context, error);
+ return;
+ }
+
+ item_data = g_new (ItemData, 1);
+ item_data->name = g_strdup (name);
+ g_markup_parse_context_get_position (context, &item_data->line, &item_data->col);
+ data->items = g_slist_prepend (data->items, item_data);
+ }
+ else if (strcmp (element_name, "headerbars") == 0)
+ {
+ if (!_gtk_builder_check_parent (data->builder, context, "object", error))
+ return;
+
+ if (!g_markup_collect_attributes (element_name, names, values, error,
+ G_MARKUP_COLLECT_INVALID, NULL, NULL,
+ G_MARKUP_COLLECT_INVALID))
+ _gtk_builder_prefix_error (data->builder, context, error);
+ }
+ else
+ {
+ _gtk_builder_error_unhandled_tag (data->builder, context,
+ "HdyHeaderGroup", element_name,
+ error);
+ }
+}
+
+
+/* This has been copied and modified from gtksizegroup.c. */
+static const GMarkupParser header_group_parser =
+ {
+ header_group_start_element
+ };
+
+/* This has been copied and modified from gtksizegroup.c. */
+static gboolean
+hdy_header_group_buildable_custom_tag_start (GtkBuildable *buildable,
+ GtkBuilder *builder,
+ GObject *child,
+ const gchar *tagname,
+ GMarkupParser *parser,
+ gpointer *parser_data)
+{
+ GSListSubParserData *data;
+
+ if (child)
+ return FALSE;
+
+ if (strcmp (tagname, "headerbars") == 0)
+ {
+ data = g_slice_new0 (GSListSubParserData);
+ data->items = NULL;
+ data->object = G_OBJECT (buildable);
+ data->builder = builder;
+
+ *parser = header_group_parser;
+ *parser_data = data;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* This has been copied and modified from gtksizegroup.c. */
+static void
+hdy_header_group_buildable_custom_finished (GtkBuildable *buildable,
+ GtkBuilder *builder,
+ GObject *child,
+ const gchar *tagname,
+ gpointer user_data)
+{
+ GSList *l;
+ GSListSubParserData *data;
+
+ if (strcmp (tagname, "headerbars") != 0)
+ return;
+
+ data = (GSListSubParserData*)user_data;
+ data->items = g_slist_reverse (data->items);
+
+ for (l = data->items; l; l = l->next) {
+ ItemData *item_data = l->data;
+ GObject *object = gtk_builder_get_object (builder, item_data->name);
+
+ if (!object)
+ continue;
+
+ if (GTK_IS_HEADER_BAR (object))
+ hdy_header_group_add_gtk_header_bar (HDY_HEADER_GROUP (data->object),
+ GTK_HEADER_BAR (object));
+ else if (HDY_IS_HEADER_BAR (object))
+ hdy_header_group_add_header_bar (HDY_HEADER_GROUP (data->object),
+ HDY_HEADER_BAR (object));
+ else if (HDY_IS_HEADER_GROUP (object))
+ hdy_header_group_add_header_group (HDY_HEADER_GROUP (data->object),
+ HDY_HEADER_GROUP (object));
+ }
+
+ g_slist_free_full (data->items, item_data_free);
+ g_slice_free (GSListSubParserData, data);
+}
+
+static void
+hdy_header_group_class_init (HdyHeaderGroupClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = hdy_header_group_dispose;
+ object_class->finalize = hdy_header_group_finalize;
+ object_class->get_property = hdy_header_group_get_property;
+ object_class->set_property = hdy_header_group_set_property;
+
+ /**
+ * HdyHeaderGroup:decorate-all:
+ *
+ * Whether the elements of the group should all receive the full decoration.
+ * This is useful in conjunction with #HdyLeaflet:folded when the leaflet
+ * contains the header bars of the group, as you want them all to display the
+ * complete decoration when the leaflet is folded.
+ *
+ * Since: 1.0
+ */
+ props[PROP_DECORATE_ALL] =
+ g_param_spec_boolean ("decorate-all",
+ _("Decorate all"),
+ _("Whether the elements of the group should all receive the full decoration"),
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, N_PROPS, props);
+
+ /**
+ * HdyHeaderGroup::update-decoration-layouts:
+ * @self: The #HdyHeaderGroup instance
+ *
+ * This signal is emitted before updating the decoration layouts.
+ *
+ * Since: 1.0
+ */
+ signals[SIGNAL_UPDATE_DECORATION_LAYOUTS] =
+ g_signal_new ("update-decoration-layouts",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 0);
+}
+
+static void
+hdy_header_group_init (HdyHeaderGroup *self)
+{
+ GtkSettings *settings = gtk_settings_get_default ();
+
+ g_signal_connect_swapped (settings, "notify::gtk-decoration-layout", G_CALLBACK (update_decoration_layouts), self);
+}
+
+static void
+hdy_header_group_buildable_init (GtkBuildableIface *iface)
+{
+ iface->custom_tag_start = hdy_header_group_buildable_custom_tag_start;
+ iface->custom_finished = hdy_header_group_buildable_custom_finished;
+}
+
+/**
+ * hdy_header_group_child_get_header_bar:
+ * @self: a #HdyHeaderGroupChild
+ *
+ * Gets the child #HdyHeaderBar.
+ * Use hdy_header_group_child_get_child_type() to check the child type.
+ *
+ * Returns: (transfer none): the child #HdyHeaderBar, or %NULL in case of error.
+ *
+ * Since: 1.0
+ */
+HdyHeaderBar *
+hdy_header_group_child_get_header_bar (HdyHeaderGroupChild *self)
+{
+ g_return_val_if_fail (HDY_IS_HEADER_GROUP_CHILD (self), NULL);
+ g_return_val_if_fail (self->type == HDY_HEADER_GROUP_CHILD_TYPE_HEADER_BAR, NULL);
+
+ return HDY_HEADER_BAR (self->object);
+}
+
+/**
+ * hdy_header_group_child_get_gtk_header_bar:
+ * @self: a #HdyHeaderGroupChild
+ *
+ * Gets the child #GtkHeaderBar.
+ * Use hdy_header_group_child_get_child_type() to check the child type.
+ *
+ * Returns: (transfer none): the child #GtkHeaderBar, or %NULL in case of error.
+ *
+ * Since: 1.0
+ */
+GtkHeaderBar *
+hdy_header_group_child_get_gtk_header_bar (HdyHeaderGroupChild *self)
+{
+ g_return_val_if_fail (HDY_IS_HEADER_GROUP_CHILD (self), NULL);
+ g_return_val_if_fail (self->type == HDY_HEADER_GROUP_CHILD_TYPE_GTK_HEADER_BAR, NULL);
+
+ return GTK_HEADER_BAR (self->object);
+}
+
+/**
+ * hdy_header_group_child_get_header_group:
+ * @self: a #HdyHeaderGroupChild
+ *
+ * Gets the child #HdyHeaderGroup.
+ * Use hdy_header_group_child_get_child_type() to check the child type.
+ *
+ * Returns: (transfer none): the child #HdyHeaderGroup, or %NULL in case of error.
+ *
+ * Since: 1.0
+ */
+HdyHeaderGroup *
+hdy_header_group_child_get_header_group (HdyHeaderGroupChild *self)
+{
+ g_return_val_if_fail (HDY_IS_HEADER_GROUP_CHILD (self), NULL);
+ g_return_val_if_fail (self->type == HDY_HEADER_GROUP_CHILD_TYPE_HEADER_GROUP, NULL);
+
+ return HDY_HEADER_GROUP (self->object);
+}
+
+/**
+ * hdy_header_group_child_get_child_type:
+ * @self: a #HdyHeaderGroupChild
+ *
+ * Gets the child type.
+ *
+ * Returns: the child type.
+ *
+ * Since: 1.0
+ */
+HdyHeaderGroupChildType
+hdy_header_group_child_get_child_type (HdyHeaderGroupChild *self)
+{
+ g_return_val_if_fail (HDY_IS_HEADER_GROUP_CHILD (self), HDY_HEADER_GROUP_CHILD_TYPE_HEADER_BAR);
+
+ return self->type;
+}
+
+/**
+ * hdy_header_group_get_children:
+ * @self: a #HdyHeaderGroup
+ *
+ * Returns the list of children associated with @self.
+ *
+ * Returns: (element-type HdyHeaderGroupChild) (transfer none): the #GSList of
+ * children. The list is owned by libhandy and should not be modified.
+ *
+ * Since: 1.0
+ */
+GSList *
+hdy_header_group_get_children (HdyHeaderGroup *self)
+{
+ g_return_val_if_fail (HDY_IS_HEADER_GROUP (self), NULL);
+
+ return self->children;
+}
+
+static void
+remove_child (HdyHeaderGroup *self,
+ HdyHeaderGroupChild *child)
+{
+ self->children = g_slist_remove (self->children, child);
+
+ g_object_weak_unref (G_OBJECT (child), (GWeakNotify) child_destroyed_cb, self);
+
+ g_object_unref (self);
+ g_object_unref (child);
+}
+
+/**
+ * hdy_header_group_remove_header_bar:
+ * @self: a #HdyHeaderGroup
+ * @header_bar: the #HdyHeaderBar to remove
+ *
+ * Removes @header_bar from @self.
+ *
+ * Since: 1.0
+ */
+void
+hdy_header_group_remove_header_bar (HdyHeaderGroup *self,
+ HdyHeaderBar *header_bar)
+{
+ HdyHeaderGroupChild *child;
+
+ g_return_if_fail (HDY_IS_HEADER_GROUP (self));
+ g_return_if_fail (HDY_IS_HEADER_BAR (header_bar));
+
+ child = get_child_for_object (self, header_bar);
+
+ g_return_if_fail (child != NULL);
+
+ remove_child (self, child);
+}
+
+/**
+ * hdy_header_group_remove_gtk_header_bar:
+ * @self: a #HdyHeaderGroup
+ * @header_bar: the #GtkHeaderBar to remove
+ *
+ * Removes @header_bar from @self.
+ *
+ * Since: 1.0
+ */
+void
+hdy_header_group_remove_gtk_header_bar (HdyHeaderGroup *self,
+ GtkHeaderBar *header_bar)
+{
+ HdyHeaderGroupChild *child;
+
+ g_return_if_fail (HDY_IS_HEADER_GROUP (self));
+ g_return_if_fail (GTK_IS_HEADER_BAR (header_bar));
+
+ child = get_child_for_object (self, header_bar);
+
+ g_return_if_fail (child != NULL);
+
+ remove_child (self, child);
+}
+
+/**
+ * hdy_header_group_remove_header_group:
+ * @self: a #HdyHeaderGroup
+ * @header_group: the #HdyHeaderGroup to remove
+ *
+ * Removes a nested #HdyHeaderGroup from a #HdyHeaderGroup
+ *
+ * Since: 1.0
+ */
+void
+hdy_header_group_remove_header_group (HdyHeaderGroup *self,
+ HdyHeaderGroup *header_group)
+{
+ HdyHeaderGroupChild *child;
+
+ g_return_if_fail (HDY_IS_HEADER_GROUP (self));
+ g_return_if_fail (HDY_IS_HEADER_GROUP (header_group));
+
+ child = get_child_for_object (self, header_group);
+
+ g_return_if_fail (child != NULL);
+
+ remove_child (self, child);
+}
+
+/**
+ * hdy_header_group_remove_child:
+ * @self: a #HdyHeaderGroup
+ * @child: the #HdyHeaderGroupChild to remove
+ *
+ * Removes @child from @self.
+ *
+ * Since: 1.0
+ */
+void
+hdy_header_group_remove_child (HdyHeaderGroup *self,
+ HdyHeaderGroupChild *child)
+{
+ g_return_if_fail (HDY_IS_HEADER_GROUP (self));
+ g_return_if_fail (HDY_IS_HEADER_GROUP_CHILD (child));
+ g_return_if_fail (g_slist_find (self->children, child) != NULL);
+
+ remove_child (self, child);
+}
+
+/**
+ * hdy_header_group_set_decorate_all:
+ * @self: a #HdyHeaderGroup
+ * @decorate_all: whether the elements of the group should all receive the full decoration
+ *
+ * Sets whether the elements of the group should all receive the full decoration.
+ *
+ * Since: 1.0
+ */
+void
+hdy_header_group_set_decorate_all (HdyHeaderGroup *self,
+ gboolean decorate_all)
+{
+ g_return_if_fail (HDY_IS_HEADER_GROUP (self));
+
+ decorate_all = !!decorate_all;
+
+ if (self->decorate_all == decorate_all)
+ return;
+
+ self->decorate_all = decorate_all;
+
+ update_decoration_layouts (self);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DECORATE_ALL]);
+}
+
+/**
+ * hdy_header_group_get_decorate_all:
+ * @self: a #HdyHeaderGroup
+ *
+ * Gets whether the elements of the group should all receive the full decoration.
+ *
+ * Returns: %TRUE if the elements of the group should all receive the full
+ * decoration, %FALSE otherwise.
+ *
+ * Since: 1.0
+ */
+gboolean
+hdy_header_group_get_decorate_all (HdyHeaderGroup *self)
+{
+ g_return_val_if_fail (HDY_IS_HEADER_GROUP (self), FALSE);
+
+ return self->decorate_all;
+}