summaryrefslogtreecommitdiffstats
path: root/subprojects/libhandy/glade
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 14:36:24 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 14:36:24 +0000
commit9b6d8e63db85c30007b463e91f91a791969fa83f (patch)
tree0899af51d73c1bf986f73ae39a03c4436083018a /subprojects/libhandy/glade
parentInitial commit. (diff)
downloadgnome-control-center-9b6d8e63db85c30007b463e91f91a791969fa83f.tar.xz
gnome-control-center-9b6d8e63db85c30007b463e91f91a791969fa83f.zip
Adding upstream version 1:3.38.4.upstream/1%3.38.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'subprojects/libhandy/glade')
-rw-r--r--subprojects/libhandy/glade/glade-catalog.dtd196
-rw-r--r--subprojects/libhandy/glade/glade-hdy-carousel.c460
-rw-r--r--subprojects/libhandy/glade/glade-hdy-carousel.h56
-rw-r--r--subprojects/libhandy/glade/glade-hdy-expander-row.c105
-rw-r--r--subprojects/libhandy/glade/glade-hdy-expander-row.h39
-rw-r--r--subprojects/libhandy/glade/glade-hdy-header-bar.c547
-rw-r--r--subprojects/libhandy/glade/glade-hdy-header-bar.h59
-rw-r--r--subprojects/libhandy/glade/glade-hdy-header-group.c162
-rw-r--r--subprojects/libhandy/glade/glade-hdy-header-group.h24
-rw-r--r--subprojects/libhandy/glade/glade-hdy-leaflet.c533
-rw-r--r--subprojects/libhandy/glade/glade-hdy-leaflet.h56
-rw-r--r--subprojects/libhandy/glade/glade-hdy-preferences-page.c218
-rw-r--r--subprojects/libhandy/glade/glade-hdy-preferences-page.h50
-rw-r--r--subprojects/libhandy/glade/glade-hdy-preferences-window.c286
-rw-r--r--subprojects/libhandy/glade/glade-hdy-preferences-window.h50
-rw-r--r--subprojects/libhandy/glade/glade-hdy-search-bar.c107
-rw-r--r--subprojects/libhandy/glade/glade-hdy-search-bar.h34
-rw-r--r--subprojects/libhandy/glade/glade-hdy-swipe-group.c161
-rw-r--r--subprojects/libhandy/glade/glade-hdy-swipe-group.h28
-rw-r--r--subprojects/libhandy/glade/glade-hdy-utils.c121
-rw-r--r--subprojects/libhandy/glade/glade-hdy-utils.h36
-rw-r--r--subprojects/libhandy/glade/glade-hdy-window.c108
-rw-r--r--subprojects/libhandy/glade/glade-hdy-window.h34
-rw-r--r--subprojects/libhandy/glade/libhandy.xml420
-rw-r--r--subprojects/libhandy/glade/meson.build59
-rw-r--r--subprojects/libhandy/glade/rename-id.patch28
-rw-r--r--subprojects/libhandy/glade/sm.puri.Handy.Glade.json83
27 files changed, 4060 insertions, 0 deletions
diff --git a/subprojects/libhandy/glade/glade-catalog.dtd b/subprojects/libhandy/glade/glade-catalog.dtd
new file mode 100644
index 0000000..c0d073e
--- /dev/null
+++ b/subprojects/libhandy/glade/glade-catalog.dtd
@@ -0,0 +1,196 @@
+<!ELEMENT glade-catalog (init-function?,
+ glade-widget-classes?,
+ glade-widget-group*)>
+
+<!ATTLIST glade-catalog name CDATA #REQUIRED
+ version CDATA #IMPLIED
+ targetable CDATA #IMPLIED
+ library CDATA #IMPLIED
+ depends CDATA #IMPLIED
+ domain CDATA #IMPLIED
+ book CDATA #IMPLIED
+ icon-prefix CDATA #IMPLIED
+ requires CDATA #IMPLIED>
+
+<!ELEMENT glade-widget-classes (glade-widget-class+)>
+
+<!ELEMENT glade-widget-class (post-create-function?,
+ add-child-verify-function?,
+ add-child-function?,
+ remove-child-function?,
+ replace-child-function?,
+ get-children-function?,
+ get-internal-child-function?,
+ child-property-applies-function?,
+ child-action-activate-function?,
+ read-widget-function?,
+ write-widget-function?,
+ get-property-function?,
+ set-property-function?,
+ child-set-property-function?,
+ child-get-property-function?,
+ action-activate-function?,
+ verify-function?,
+ special-child-type?,
+ packing-properties?,
+ packing-actions?,
+ properties?,
+ children?,
+ packing-defaults?,
+ actions?)>
+
+<!ATTLIST glade-widget-class toplevel CDATA #IMPLIED
+ since CDATA #IMPLIED
+ deprecated CDATA #IMPLIED
+ use-placeholders CDATA #IMPLIED
+ default-width CDATA #IMPLIED
+ default-height CDATA #IMPLIED
+ name CDATA #REQUIRED
+ generic-name CDATA #IMPLIED
+ icon-name CDATA #IMPLIED
+ title CDATA #REQUIRED
+ parent CDATA #IMPLIED
+ get-type-function CDATA #IMPLIED
+ adaptor CDATA #IMPLIED>
+
+<!ELEMENT properties (property+)>
+
+<!ELEMENT property (spec?,
+ type?,
+ parameter-spec?,
+ tooltip?,
+ parameters?,
+ set-function?,
+ get-function?,
+ verify-function?,
+ displayable-values?)>
+
+<!ATTLIST property id CDATA #REQUIRED
+ since CDATA #IMPLIED
+ deprecated CDATA #IMPLIED
+ create-type CDATA #IMPLIED
+ name CDATA #IMPLIED
+ tooltip CDATA #IMPLIED
+ themed-icon CDATA #IMPLIED
+ stock CDATA #IMPLIED
+ stock-icon CDATA #IMPLIED
+ weight CDATA #IMPLIED
+ transfer-on-paste CDATA #IMPLIED
+ save-always CDATA #IMPLIED
+ parentless-widget CDATA #IMPLIED
+ atk-property CDATA #IMPLIED
+ default CDATA #IMPLIED
+ query CDATA #IMPLIED
+ save CDATA #IMPLIED
+ common CDATA #IMPLIED
+ disabled CDATA #IMPLIED
+ visible CDATA #IMPLIED
+ custom-layout CDATA #IMPLIED
+ multiline CDATA #IMPLIED
+ optional CDATA #IMPLIED
+ optional-default CDATA #IMPLIED
+ ignore CDATA #IMPLIED
+ needs-sync CDATA #IMPLIED
+ construct-only CDATA #IMPLIED
+ translatable CDATA #IMPLIED>
+
+<!ELEMENT parameter-spec (type?,
+ value-type?,
+ min?)>
+<!ELEMENT value-type (#PCDATA)>
+<!ELEMENT min (#PCDATA)>
+<!ELEMENT set-function (#PCDATA)>
+<!ELEMENT get-function (#PCDATA)>
+<!ELEMENT spec (#PCDATA)>
+<!ELEMENT tooltip (#PCDATA)>
+<!ELEMENT verify-function (#PCDATA)>
+
+<!ELEMENT displayable-values (value+)>
+
+<!ELEMENT value EMPTY>
+
+<!ATTLIST value id CDATA #REQUIRED
+ name CDATA #REQUIRED>
+
+<!ELEMENT parameters (parameter+)>
+
+<!ELEMENT parameter EMPTY>
+
+<!ATTLIST parameter key CDATA #REQUIRED
+ value CDATA #REQUIRED>
+
+<!ELEMENT children (child+)>
+
+<!ELEMENT child (type,
+ add-child-function?,
+ remove-child-function?,
+ get-children-function?,
+ get-all-children-function?,
+ set-property-function?,
+ get-property-function?,
+ replace-child-function?,
+ fill-empty-function?,
+ properties?)>
+
+<!ELEMENT type (#PCDATA)>
+<!ELEMENT add-child-verify-function (#PCDATA)>
+<!ELEMENT add-child-function (#PCDATA)>
+<!ELEMENT remove-child-function (#PCDATA)>
+<!ELEMENT get-children-function (#PCDATA)>
+<!ELEMENT get-all-children-function (#PCDATA)>
+<!ELEMENT set-prop-function (#PCDATA)>
+<!ELEMENT get-prop-function (#PCDATA)>
+<!ELEMENT fill-empty-function (#PCDATA)>
+<!ELEMENT replace-child-function (#PCDATA)>
+<!ELEMENT child-set-property-function (#PCDATA)>
+<!ELEMENT child-get-property-function (#PCDATA)>
+<!ELEMENT action-activate-function (#PCDATA)>
+
+<!ELEMENT post-create-function (#PCDATA)>
+<!ELEMENT get-internal-child-function (#PCDATA)>
+<!ELEMENT child-property-applies-function (#PCDATA)>
+
+<!ELEMENT child-action-activate-function (#PCDATA)>
+
+<!ELEMENT read-widget-function (#PCDATA)>
+<!ELEMENT write-widget-function (#PCDATA)>
+<!ELEMENT get-property-function (#PCDATA)>
+<!ELEMENT set-property-function (#PCDATA)>
+
+<!ELEMENT glade-widget-group (default-palette-state?,
+ glade-widget-class-ref+)>
+
+<!ATTLIST glade-widget-group name CDATA #REQUIRED
+ title CDATA #REQUIRED>
+
+<!ELEMENT default-palette-state EMPTY>
+<!ATTLIST default-palette-state expanded CDATA #IMPLIED>
+
+<!ELEMENT glade-widget-class-ref EMPTY>
+<!ATTLIST glade-widget-class-ref name CDATA #REQUIRED>
+
+<!ELEMENT packing-defaults (parent-class+)>
+
+<!ELEMENT parent-class (child-property+)>
+<!ATTLIST parent-class name CDATA #REQUIRED>
+
+<!ELEMENT child-property EMPTY>
+<!ATTLIST child-property id CDATA #REQUIRED
+ default CDATA #REQUIRED>
+
+<!ELEMENT special-child-type (#PCDATA)>
+
+<!ELEMENT packing-properties (property+)>
+
+<!ELEMENT packing-actions (action+)>
+
+<!ELEMENT actions (action+)>
+
+<!ELEMENT action EMPTY>
+
+<!ATTLIST action id CDATA #REQUIRED
+ name CDATA #REQUIRED
+ stock CDATA #IMPLIED
+ important CDATA #IMPLIED>
+
+<!ELEMENT init-function (#PCDATA)>
diff --git a/subprojects/libhandy/glade/glade-hdy-carousel.c b/subprojects/libhandy/glade/glade-hdy-carousel.c
new file mode 100644
index 0000000..c06a4e4
--- /dev/null
+++ b/subprojects/libhandy/glade/glade-hdy-carousel.c
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) 2019 Alexander Mikhaylenko <exalm7659@gmail.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Based on
+ * glade-gtk-stack.c - GladeWidgetAdaptor for GtkStack
+ * Copyright (C) 2014 Red Hat, Inc.
+ */
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#include "glade-hdy-carousel.h"
+
+#include <gladeui/glade.h>
+#include "glade-hdy-utils.h"
+
+#include <math.h>
+
+static gint
+hdy_carousel_get_page (HdyCarousel *carousel)
+{
+ return round (hdy_carousel_get_position (carousel));
+}
+
+static gboolean
+hdy_carousel_is_transient (HdyCarousel *carousel)
+{
+ return fmod (hdy_carousel_get_position (carousel), 1.0) > 0.00001;
+}
+
+static gint
+get_n_pages_excluding_placeholders (GtkContainer *container)
+{
+ GList *children, *l;
+ gint n_pages;
+
+ children = gtk_container_get_children (container);
+
+ n_pages = 0;
+ for (l = children; l; l = l->next)
+ if (!GLADE_IS_PLACEHOLDER (l->data))
+ n_pages++;
+
+ g_list_free (children);
+
+
+ return n_pages;
+}
+
+static void
+selection_changed_cb (GladeProject *project,
+ GladeWidget *gwidget)
+{
+ GList *list;
+ GtkWidget *page, *sel_widget;
+ GtkContainer *container;
+ GList *children, *l;
+ gint index;
+
+ list = glade_project_selection_get (project);
+ if (!list || g_list_length (list) != 1)
+ return;
+
+ sel_widget = list->data;
+
+ container = GTK_CONTAINER (glade_widget_get_object (gwidget));
+
+ if (!GTK_IS_WIDGET (sel_widget) ||
+ !gtk_widget_is_ancestor (sel_widget, GTK_WIDGET (container)))
+ return;
+
+ children = gtk_container_get_children (container);
+ for (l = children; l; l = l->next) {
+ page = l->data;
+ if (sel_widget == page || gtk_widget_is_ancestor (sel_widget, page)) {
+ hdy_carousel_scroll_to (HDY_CAROUSEL (container), page);
+ index = glade_hdy_get_child_index (container, page);
+ glade_widget_property_set (gwidget, "page", index);
+ break;
+ }
+ }
+ g_list_free (children);
+}
+
+static void
+project_changed_cb (GladeWidget *gwidget,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ GladeProject *project, *old_project;
+
+ project = glade_widget_get_project (gwidget);
+ old_project = g_object_get_data (G_OBJECT (gwidget), "carousel-project-ptr");
+
+ if (old_project)
+ g_signal_handlers_disconnect_by_func (G_OBJECT (old_project),
+ G_CALLBACK (selection_changed_cb),
+ gwidget);
+
+ if (project)
+ g_signal_connect (G_OBJECT (project), "selection-changed",
+ G_CALLBACK (selection_changed_cb), gwidget);
+
+ g_object_set_data (G_OBJECT (gwidget), "carousel-project-ptr", project);
+}
+
+static void
+position_changed_cb (HdyCarousel *carousel,
+ GParamSpec *pspec,
+ GladeWidget *gwidget)
+{
+ gint old_page, new_page;
+
+ glade_widget_property_get (gwidget, "page", &old_page);
+ new_page = hdy_carousel_get_page (carousel);
+
+ if (old_page == new_page || hdy_carousel_is_transient (carousel))
+ return;
+
+ glade_widget_property_set (gwidget, "page", new_page);
+}
+
+void
+glade_hdy_carousel_post_create (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GladeCreateReason reason)
+{
+ GladeWidget *gwidget = glade_widget_get_from_gobject (container);
+
+ if (reason == GLADE_CREATE_USER)
+ gtk_container_add (GTK_CONTAINER (container), glade_placeholder_new ());
+
+ g_signal_connect (G_OBJECT (gwidget), "notify::project",
+ G_CALLBACK (project_changed_cb), NULL);
+
+ project_changed_cb (gwidget, NULL, NULL);
+
+ g_signal_connect (G_OBJECT (container), "notify::position",
+ G_CALLBACK (position_changed_cb), gwidget);
+}
+
+void
+glade_hdy_carousel_child_action_activate (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *object,
+ const gchar *action_path)
+{
+ if (!strcmp (action_path, "insert_page_after") ||
+ !strcmp (action_path, "insert_page_before")) {
+ GladeWidget *parent;
+ GladeProperty *property;
+ GtkWidget *placeholder;
+ gint pages, index;
+
+ parent = glade_widget_get_from_gobject (container);
+ glade_widget_property_get (parent, "pages", &pages);
+
+ glade_command_push_group (_("Insert placeholder to %s"),
+ glade_widget_get_name (parent));
+
+ index = glade_hdy_get_child_index (GTK_CONTAINER (container), GTK_WIDGET (object));
+ if (!strcmp (action_path, "insert_page_after"))
+ index++;
+
+ placeholder = glade_placeholder_new ();
+
+ hdy_carousel_insert (HDY_CAROUSEL (container), placeholder, index);
+ hdy_carousel_scroll_to (HDY_CAROUSEL (container), placeholder);
+
+ glade_hdy_sync_child_positions (GTK_CONTAINER (container));
+
+ property = glade_widget_get_property (parent, "pages");
+ glade_command_set_property (property, pages + 1);
+
+ property = glade_widget_get_property (parent, "page");
+ glade_command_set_property (property, index);
+
+ glade_command_pop_group ();
+ } else if (strcmp (action_path, "remove_page") == 0) {
+ GladeWidget *parent;
+ GladeProperty *property;
+ gint pages, position;
+
+ parent = glade_widget_get_from_gobject (container);
+ glade_widget_property_get (parent, "pages", &pages);
+
+ glade_command_push_group (_("Remove placeholder from %s"),
+ glade_widget_get_name (parent));
+
+ g_assert (GLADE_IS_PLACEHOLDER (object));
+ gtk_container_remove (GTK_CONTAINER (container), GTK_WIDGET (object));
+
+ glade_hdy_sync_child_positions (GTK_CONTAINER (container));
+
+ property = glade_widget_get_property (parent, "pages");
+ glade_command_set_property (property, pages - 1);
+
+ glade_widget_property_get (parent, "page", &position);
+ property = glade_widget_get_property (parent, "page");
+ glade_command_set_property (property, position);
+
+ glade_command_pop_group ();
+ } else
+ GWA_GET_CLASS (GTK_TYPE_CONTAINER)->child_action_activate (adaptor,
+ container,
+ object,
+ action_path);
+}
+
+static void
+set_n_pages (GObject *container,
+ const GValue *value)
+{
+ GladeWidget *gbox;
+ GtkWidget *child;
+ gint old_size, new_size, i, page;
+
+ new_size = g_value_get_int (value);
+ old_size = hdy_carousel_get_n_pages (HDY_CAROUSEL (container));
+
+ if (old_size == new_size)
+ return;
+
+ for (i = old_size; i < new_size; i++)
+ gtk_container_add (GTK_CONTAINER (container), glade_placeholder_new ());
+
+ for (i = old_size; i > 0; i--) {
+ if (old_size <= new_size)
+ break;
+ child = glade_hdy_get_nth_child (GTK_CONTAINER (container), i - 1);
+ if (GLADE_IS_PLACEHOLDER (child)) {
+ gtk_container_remove (GTK_CONTAINER (container), child);
+ old_size--;
+ }
+ }
+
+ gbox = glade_widget_get_from_gobject (container);
+ glade_widget_property_get (gbox, "page", &page);
+ glade_widget_property_set (gbox, "page", page);
+}
+
+static void
+set_page (GObject *object,
+ const GValue *value)
+{
+ gint new_page;
+ GtkWidget *child;
+
+ new_page = g_value_get_int (value);
+ child = glade_hdy_get_nth_child (GTK_CONTAINER (object), new_page);
+
+ if (child)
+ hdy_carousel_scroll_to (HDY_CAROUSEL (object), child);
+}
+
+void
+glade_hdy_carousel_set_property (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *id,
+ const GValue *value)
+{
+ if (!strcmp (id, "pages"))
+ set_n_pages (object, value);
+ else if (!strcmp (id, "page"))
+ set_page (object, value);
+ else {
+ GWA_GET_CLASS (GTK_TYPE_CONTAINER)->set_property (adaptor, object, id, value);
+ }
+}
+
+void
+glade_hdy_carousel_get_property (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *id,
+ GValue *value)
+{
+ if (!strcmp (id, "pages")) {
+ g_value_reset (value);
+ g_value_set_int (value, hdy_carousel_get_n_pages (HDY_CAROUSEL (object)));
+ } else if (!strcmp (id, "page")) {
+ g_value_reset (value);
+ g_value_set_int (value, hdy_carousel_get_page (HDY_CAROUSEL (object)));
+ } else {
+ GWA_GET_CLASS (GTK_TYPE_CONTAINER)->get_property (adaptor, object, id, value);
+ }
+}
+
+static gboolean
+glade_hdy_carousel_verify_n_pages (GObject *object,
+ const GValue *value)
+{
+ gint new_size, old_size;
+
+ new_size = g_value_get_int (value);
+ old_size = get_n_pages_excluding_placeholders (GTK_CONTAINER (object));
+
+ return old_size <= new_size;
+}
+
+static gboolean
+glade_hdy_carousel_verify_page (GObject *object,
+ const GValue *value)
+{
+ gint page, pages;
+
+ page = g_value_get_int (value);
+ pages = hdy_carousel_get_n_pages (HDY_CAROUSEL (object));
+
+ return 0 <= page && page < pages;
+}
+
+gboolean
+glade_hdy_carousel_verify_property (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *id,
+ const GValue *value)
+{
+ if (!strcmp (id, "pages"))
+ return glade_hdy_carousel_verify_n_pages (object, value);
+ else if (!strcmp (id, "page"))
+ return glade_hdy_carousel_verify_page (object, value);
+ else if (GWA_GET_CLASS (GTK_TYPE_CONTAINER)->verify_property)
+ return GWA_GET_CLASS (GTK_TYPE_CONTAINER)->verify_property (adaptor, object,
+ id, value);
+
+ return TRUE;
+}
+
+void
+glade_hdy_carousel_add_child (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child)
+{
+ GladeWidget *gbox, *gchild;
+ gint pages, page;
+
+ if (!glade_widget_superuser () && !GLADE_IS_PLACEHOLDER (child)) {
+ GList *l, *children;
+
+ children = gtk_container_get_children (GTK_CONTAINER (container));
+
+ for (l = g_list_last (children); l; l = l->prev) {
+ GtkWidget *widget = l->data;
+
+ if (GLADE_IS_PLACEHOLDER (widget)) {
+ gtk_container_remove (GTK_CONTAINER (container), widget);
+ break;
+ }
+ }
+
+ g_list_free (children);
+ }
+
+ gtk_container_add (GTK_CONTAINER (container), GTK_WIDGET (child));
+
+ gchild = glade_widget_get_from_gobject (child);
+ if (gchild)
+ glade_widget_set_pack_action_visible (gchild, "remove_page", FALSE);
+
+ glade_hdy_sync_child_positions (GTK_CONTAINER (container));
+
+ gbox = glade_widget_get_from_gobject (container);
+ glade_widget_property_get (gbox, "pages", &pages);
+ glade_widget_property_set (gbox, "pages", pages);
+ glade_widget_property_get (gbox, "page", &page);
+ glade_widget_property_set (gbox, "page", page);
+}
+
+void
+glade_hdy_carousel_remove_child (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child)
+{
+ GladeWidget *gbox;
+ gint pages, page;
+
+ gtk_container_remove (GTK_CONTAINER (container), GTK_WIDGET (child));
+
+ glade_hdy_sync_child_positions (GTK_CONTAINER (container));
+
+ gbox = glade_widget_get_from_gobject (container);
+ glade_widget_property_get (gbox, "pages", &pages);
+ glade_widget_property_set (gbox, "pages", pages);
+ glade_widget_property_get (gbox, "page", &page);
+ glade_widget_property_set (gbox, "page", page);
+}
+
+void
+glade_hdy_carousel_replace_child (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *current,
+ GObject *new_widget)
+{
+ GladeWidget *gbox, *gchild;
+ gint pages, page, index;
+
+ index = glade_hdy_get_child_index (GTK_CONTAINER (container), GTK_WIDGET (current));
+ gtk_container_remove (GTK_CONTAINER (container), GTK_WIDGET (current));
+ hdy_carousel_insert (HDY_CAROUSEL (container), GTK_WIDGET (new_widget),
+ index);
+ hdy_carousel_scroll_to_full (HDY_CAROUSEL (container),
+ GTK_WIDGET (new_widget), 0);
+
+ glade_hdy_sync_child_positions (GTK_CONTAINER (container));
+
+ gchild = glade_widget_get_from_gobject (new_widget);
+ if (gchild)
+ glade_widget_set_pack_action_visible (gchild, "remove_page", FALSE);
+
+ /* NOTE: make sure to sync this at the end because new_widget could be
+ * a placeholder and syncing these properties could destroy it.
+ */
+ gbox = glade_widget_get_from_gobject (container);
+ glade_widget_property_get (gbox, "pages", &pages);
+ glade_widget_property_set (gbox, "pages", pages);
+ glade_widget_property_get (gbox, "page", &page);
+ glade_widget_property_set (gbox, "page", page);
+}
+
+void
+glade_hdy_carousel_get_child_property (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child,
+ const gchar *property_name,
+ GValue *value)
+{
+ if (strcmp (property_name, "position") == 0)
+ g_value_set_int (value, glade_hdy_get_child_index (GTK_CONTAINER (container),
+ GTK_WIDGET (child)));
+ else
+ GWA_GET_CLASS (GTK_TYPE_CONTAINER)->child_get_property (adaptor,
+ container,
+ child,
+ property_name,
+ value);
+}
+
+void
+glade_hdy_carousel_set_child_property (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child,
+ const gchar *property_name,
+ GValue *value)
+{
+ if (strcmp (property_name, "position") == 0) {
+ glade_hdy_reorder_child (GTK_CONTAINER (container),
+ GTK_WIDGET (child),
+ g_value_get_int (value));
+
+ glade_hdy_sync_child_positions (GTK_CONTAINER (container));
+ } else {
+ GWA_GET_CLASS (GTK_TYPE_CONTAINER)->child_set_property (adaptor,
+ container,
+ child,
+ property_name,
+ value);
+ }
+}
diff --git a/subprojects/libhandy/glade/glade-hdy-carousel.h b/subprojects/libhandy/glade/glade-hdy-carousel.h
new file mode 100644
index 0000000..211c0a6
--- /dev/null
+++ b/subprojects/libhandy/glade/glade-hdy-carousel.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2019 Alexander Mikhaylenko <exalm7659@gmail.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#pragma once
+
+#include <gladeui/glade.h>
+
+#include <handy.h>
+
+
+void glade_hdy_carousel_post_create (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GladeCreateReason reason);
+
+void glade_hdy_carousel_child_action_activate (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *object,
+ const gchar *action_path);
+
+void glade_hdy_carousel_set_property (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *id,
+ const GValue *value);
+void glade_hdy_carousel_get_property (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *id,
+ GValue *value);
+gboolean glade_hdy_carousel_verify_property (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *id,
+ const GValue *value);
+
+void glade_hdy_carousel_add_child (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child);
+void glade_hdy_carousel_remove_child (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child);
+void glade_hdy_carousel_replace_child (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *current,
+ GObject *new_widget);
+
+void glade_hdy_carousel_get_child_property (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child,
+ const gchar *property_name,
+ GValue *value);
+void glade_hdy_carousel_set_child_property (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child,
+ const gchar *property_name,
+ GValue *value);
diff --git a/subprojects/libhandy/glade/glade-hdy-expander-row.c b/subprojects/libhandy/glade/glade-hdy-expander-row.c
new file mode 100644
index 0000000..d1a2ccc
--- /dev/null
+++ b/subprojects/libhandy/glade/glade-hdy-expander-row.c
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2020 Alexander Mikhaylenko <alexm@gnome.org>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Based on
+ * glade-gtk-list-box.c - GladeWidgetAdaptor for GtkListBox
+ * Copyright (C) 2013 Kalev Lember
+ */
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#include "glade-hdy-expander-row.h"
+
+#include <gladeui/glade.h>
+#include "glade-hdy-utils.h"
+
+void
+glade_hdy_expander_row_post_create (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GladeCreateReason reason)
+{
+ g_object_set (container, "expanded", TRUE, NULL);
+}
+
+void
+glade_hdy_expander_row_get_child_property (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child,
+ const gchar *property_name,
+ GValue *value)
+{
+ if (strcmp (property_name, "position") == 0)
+ g_value_set_int (value, glade_hdy_get_child_index (GTK_CONTAINER (container),
+ GTK_WIDGET (child)));
+ else
+ GWA_GET_CLASS (GTK_TYPE_CONTAINER)->child_get_property (adaptor,
+ container,
+ child,
+ property_name,
+ value);
+}
+
+void
+glade_hdy_expander_row_set_child_property (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child,
+ const gchar *property_name,
+ GValue *value)
+{
+ if (strcmp (property_name, "position") == 0)
+ glade_hdy_reorder_child (GTK_CONTAINER (container),
+ GTK_WIDGET (child),
+ g_value_get_int (value));
+ else
+ GWA_GET_CLASS (GTK_TYPE_CONTAINER)->child_set_property (adaptor,
+ container,
+ child,
+ property_name,
+ value);
+}
+
+void
+glade_hdy_expander_row_add_child (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ GObject *child)
+{
+ gtk_container_add (GTK_CONTAINER (object), GTK_WIDGET (child));
+
+ glade_hdy_sync_child_positions (GTK_CONTAINER (object));
+}
+
+void
+glade_hdy_expander_row_remove_child (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ GObject *child)
+{
+ gtk_container_remove (GTK_CONTAINER (object), GTK_WIDGET (child));
+
+ glade_hdy_sync_child_positions (GTK_CONTAINER (object));
+}
+
+gboolean
+glade_hdy_expander_row_add_verify (GladeWidgetAdaptor *adaptor,
+ GtkWidget *object,
+ GtkWidget *child,
+ gboolean user_feedback)
+{
+ if (GTK_IS_LIST_BOX_ROW (child))
+ return TRUE;
+
+ if (user_feedback) {
+ GladeWidgetAdaptor *row_adaptor =
+ glade_widget_adaptor_get_by_type (GTK_TYPE_LIST_BOX_ROW);
+
+ glade_util_ui_message (glade_app_get_window (),
+ GLADE_UI_INFO, NULL,
+ ONLY_THIS_GOES_IN_THAT_MSG,
+ glade_widget_adaptor_get_title (row_adaptor),
+ glade_widget_adaptor_get_title (adaptor));
+ }
+
+ return FALSE;
+}
diff --git a/subprojects/libhandy/glade/glade-hdy-expander-row.h b/subprojects/libhandy/glade/glade-hdy-expander-row.h
new file mode 100644
index 0000000..221f65e
--- /dev/null
+++ b/subprojects/libhandy/glade/glade-hdy-expander-row.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 Alexander Mikhaylenko <alexm@gnome.org>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#pragma once
+
+#include <gladeui/glade.h>
+
+#include <handy.h>
+
+void glade_hdy_expander_row_post_create (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GladeCreateReason reason);
+
+void glade_hdy_expander_row_get_child_property (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child,
+ const gchar *property_name,
+ GValue *value);
+void glade_hdy_expander_row_set_child_property (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child,
+ const gchar *property_name,
+ GValue *value);
+
+void glade_hdy_expander_row_add_child (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ GObject *child);
+
+void glade_hdy_expander_row_remove_child (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ GObject *child);
+
+gboolean glade_hdy_expander_row_add_verify (GladeWidgetAdaptor *adaptor,
+ GtkWidget *object,
+ GtkWidget *child,
+ gboolean user_feedback);
diff --git a/subprojects/libhandy/glade/glade-hdy-header-bar.c b/subprojects/libhandy/glade/glade-hdy-header-bar.c
new file mode 100644
index 0000000..12b7afe
--- /dev/null
+++ b/subprojects/libhandy/glade/glade-hdy-header-bar.c
@@ -0,0 +1,547 @@
+/*
+ * Copyright (C) 2020 Alexander Mikhaylenko <alexm@gnome.org>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Based on
+ * glade-gtk-header-bar.c - GladeWidgetAdaptor for GtkHeaderBar
+ */
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#include "glade-hdy-header-bar.h"
+
+#include <gladeui/glade.h>
+#include "glade-hdy-utils.h"
+
+#define TITLE_DISABLED_MESSAGE _("This property does not apply when a custom title is set")
+
+typedef struct {
+ GtkContainer *parent;
+ GtkWidget *custom_title;
+ gboolean include_placeholders;
+ gint count;
+} ChildrenData;
+
+static void
+count_children (GtkWidget *widget, gpointer data)
+{
+ ChildrenData *cdata = data;
+
+ if (widget == cdata->custom_title)
+ return;
+
+ if ((GLADE_IS_PLACEHOLDER (widget) && cdata->include_placeholders) ||
+ glade_widget_get_from_gobject (widget) != NULL)
+ cdata->count++;
+}
+
+static gboolean
+verify_size (GObject *object,
+ const GValue *value)
+{
+ gint new_size;
+ ChildrenData data;
+
+ new_size = g_value_get_int (value);
+
+ data.parent = GTK_CONTAINER (object);
+ data.custom_title = hdy_header_bar_get_custom_title (HDY_HEADER_BAR (object));
+ data.include_placeholders = FALSE;
+ data.count = 0;
+
+ gtk_container_foreach (data.parent, count_children, &data);
+
+ return data.count <= new_size;
+}
+
+static gint
+get_n_children (GObject *object)
+{
+ ChildrenData data;
+
+ data.parent = GTK_CONTAINER (object);
+ data.custom_title = hdy_header_bar_get_custom_title (HDY_HEADER_BAR (object));
+ data.include_placeholders = TRUE;
+ data.count = 0;
+
+ gtk_container_foreach (data.parent, count_children, &data);
+
+ return data.count;
+}
+
+static void
+parse_finished_cb (GladeProject *project,
+ GObject *object)
+{
+ GladeWidget *gbox;
+
+ gbox = glade_widget_get_from_gobject (object);
+ glade_widget_property_set (gbox, "size", get_n_children (object));
+ glade_widget_property_set (gbox, "use-custom-title", hdy_header_bar_get_custom_title (HDY_HEADER_BAR (object)) != NULL);
+}
+
+void
+glade_hdy_header_bar_post_create (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GladeCreateReason reason)
+{
+ GladeWidget *parent = glade_widget_get_from_gobject (container);
+ GladeProject *project = glade_widget_get_project (parent);
+
+ if (reason == GLADE_CREATE_LOAD) {
+ g_signal_connect (project, "parse-finished",
+ G_CALLBACK (parse_finished_cb),
+ container);
+
+ return;
+ }
+
+ if (reason == GLADE_CREATE_USER)
+ hdy_header_bar_pack_start (HDY_HEADER_BAR (container),
+ glade_placeholder_new ());
+}
+
+void
+glade_hdy_header_bar_action_activate (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *action_path)
+{
+ if (!strcmp (action_path, "add_slot")) {
+ GladeWidget *parent;
+ GladeProperty *property;
+ gint size;
+
+ parent = glade_widget_get_from_gobject (object);
+
+ glade_command_push_group (_("Insert placeholder to %s"),
+ glade_widget_get_name (parent));
+
+ property = glade_widget_get_property (parent, "size");
+ glade_property_get (property, &size);
+ glade_command_set_property (property, size + 1);
+
+ glade_command_pop_group ();
+ } else {
+ GWA_GET_CLASS (GTK_TYPE_CONTAINER)->action_activate (adaptor,
+ object,
+ action_path);
+ }
+}
+
+void
+glade_hdy_header_bar_child_action_activate (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *object,
+ const gchar *action_path)
+{
+ if (strcmp (action_path, "remove_slot") == 0) {
+ GladeWidget *parent;
+ GladeProperty *property;
+
+ parent = glade_widget_get_from_gobject (container);
+ glade_command_push_group (_("Remove placeholder from %s"),
+ glade_widget_get_name (parent));
+
+ if (g_object_get_data (object, "special-child-type")) {
+ property = glade_widget_get_property (parent, "use-custom-title");
+ glade_command_set_property (property, FALSE);
+ } else {
+ gint size;
+
+ gtk_container_remove (GTK_CONTAINER (container), GTK_WIDGET (object));
+
+ property = glade_widget_get_property (parent, "size");
+ glade_property_get (property, &size);
+ glade_command_set_property (property, size - 1);
+ }
+
+ glade_command_pop_group ();
+ } else {
+ GWA_GET_CLASS (GTK_TYPE_CONTAINER)->child_action_activate (adaptor,
+ container,
+ object,
+ action_path);
+ }
+}
+
+void
+glade_hdy_header_bar_get_property (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *id,
+ GValue *value)
+{
+ if (!strcmp (id, "use-custom-title")) {
+ g_value_reset (value);
+ g_value_set_boolean (value, hdy_header_bar_get_custom_title (HDY_HEADER_BAR (object)) != NULL);
+ } else if (!strcmp (id, "size")) {
+ g_value_reset (value);
+ g_value_set_int (value, get_n_children (object));
+ } else {
+ GWA_GET_CLASS (GTK_TYPE_CONTAINER)->get_property (adaptor, object, id, value);
+ }
+}
+
+static void
+set_size (GObject *object,
+ const GValue *value)
+{
+ GList *l, *next;
+ g_autoptr (GList) children = NULL;
+ GtkWidget *child;
+ guint new_size, old_size, i;
+
+ if (glade_util_object_is_loading (object))
+ return;
+
+ children = gtk_container_get_children (GTK_CONTAINER (object));
+ l = children;
+
+ while (l) {
+ next = l->next;
+ if (l->data == hdy_header_bar_get_custom_title (HDY_HEADER_BAR (object)) ||
+ (!glade_widget_get_from_gobject (l->data) && !GLADE_IS_PLACEHOLDER (l->data)))
+ children = g_list_delete_link (children, l);
+ l = next;
+ }
+
+ old_size = g_list_length (children);
+ new_size = g_value_get_int (value);
+
+ if (old_size == new_size)
+ return;
+
+ for (i = old_size; i < new_size; i++) {
+ GtkWidget *placeholder = glade_placeholder_new ();
+ hdy_header_bar_pack_start (HDY_HEADER_BAR (object), placeholder);
+ }
+
+ for (l = g_list_last (children); l && old_size > new_size; l = l->prev) {
+ child = l->data;
+ if (glade_widget_get_from_gobject (child) || !GLADE_IS_PLACEHOLDER (child))
+ continue;
+
+ gtk_container_remove (GTK_CONTAINER (object), child);
+ old_size--;
+ }
+}
+
+static void
+set_use_custom_title (GObject *object,
+ gboolean use_custom_title)
+{
+ GladeWidget *gwidget = glade_widget_get_from_gobject (object);
+ GtkWidget *child;
+
+ if (use_custom_title) {
+ child = hdy_header_bar_get_custom_title (HDY_HEADER_BAR (object));
+ if (!child) {
+ child = glade_placeholder_new ();
+ g_object_set_data (G_OBJECT (child), "special-child-type", "title");
+ }
+ } else {
+ child = NULL;
+ }
+
+ hdy_header_bar_set_custom_title (HDY_HEADER_BAR (object), child);
+
+ if (GLADE_IS_PLACEHOLDER (child)) {
+ GList *list, *l;
+
+ list = glade_placeholder_packing_actions (GLADE_PLACEHOLDER (child));
+ for (l = list; l; l = l->next) {
+ GladeWidgetAction *gwa = l->data;
+ if (!strcmp (glade_widget_action_get_def (gwa)->id, "remove_slot"))
+ glade_widget_action_set_visible (gwa, FALSE);
+ }
+ }
+
+ if (use_custom_title) {
+ glade_widget_property_set_sensitive (gwidget, "title", FALSE, TITLE_DISABLED_MESSAGE);
+ glade_widget_property_set_sensitive (gwidget, "subtitle", FALSE, TITLE_DISABLED_MESSAGE);
+ glade_widget_property_set_sensitive (gwidget, "has-subtitle", FALSE, TITLE_DISABLED_MESSAGE);
+ } else {
+ glade_widget_property_set_sensitive (gwidget, "title", TRUE, NULL);
+ glade_widget_property_set_sensitive (gwidget, "subtitle", TRUE, NULL);
+ glade_widget_property_set_sensitive (gwidget, "has-subtitle", TRUE, NULL);
+ }
+}
+
+void
+glade_hdy_header_bar_set_property (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *id,
+ const GValue *value)
+{
+ if (!strcmp (id, "use-custom-title")) {
+ set_use_custom_title (object, g_value_get_boolean (value));
+ } else if (!strcmp (id, "show-close-button")) {
+ GladeWidget *gwidget = glade_widget_get_from_gobject (object);
+
+ /* We don't set the property to 'ignore' so that we catch this in the adaptor,
+ * but we also do not apply the property to the runtime object here, thus
+ * avoiding showing the close button which would in turn close glade itself
+ * when clicked.
+ */
+ glade_widget_property_set_sensitive (gwidget, "decoration-layout",
+ g_value_get_boolean (value),
+ _("The decoration layout does not apply to header bars "
+ "which do no show window controls"));
+ } else if (!strcmp (id, "size")) {
+ set_size (object, value);
+ } else {
+ GWA_GET_CLASS (GTK_TYPE_CONTAINER)->set_property (adaptor, object, id, value);
+ }
+}
+
+void
+glade_hdy_header_bar_add_child (GladeWidgetAdaptor *adaptor,
+ GObject *parent,
+ GObject *child)
+{
+ GladeWidget *gbox, *gchild;
+ gint size;
+ gchar *special_child_type;
+
+ gchild = glade_widget_get_from_gobject (child);
+ if (gchild)
+ glade_widget_set_pack_action_visible (gchild, "remove_slot", FALSE);
+
+ special_child_type = g_object_get_data (child, "special-child-type");
+
+ if (special_child_type && !strcmp (special_child_type, "title")) {
+ hdy_header_bar_set_custom_title (HDY_HEADER_BAR (parent), GTK_WIDGET (child));
+
+ return;
+ }
+
+ GWA_GET_CLASS (GTK_TYPE_CONTAINER)->add (adaptor, parent, child);
+
+ gbox = glade_widget_get_from_gobject (parent);
+ if (!glade_widget_superuser ()) {
+ glade_widget_property_get (gbox, "size", &size);
+ glade_widget_property_set (gbox, "size", size);
+ }
+}
+
+void
+glade_hdy_header_bar_remove_child (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ GObject *child)
+{
+ GladeWidget *gbox;
+ gint size;
+ gchar *special_child_type;
+
+ special_child_type = g_object_get_data (child, "special-child-type");
+
+ if (special_child_type && !strcmp (special_child_type, "title")) {
+ GtkWidget *replacement = glade_placeholder_new ();
+
+ g_object_set_data (G_OBJECT (replacement), "special-child-type", "title");
+ hdy_header_bar_set_custom_title (HDY_HEADER_BAR (object), replacement);
+
+ return;
+ }
+
+ gtk_container_remove (GTK_CONTAINER (object), GTK_WIDGET (child));
+
+ /* Synchronize number of placeholders, this should trigger the set_property method with the
+ * correct value (not the arbitrary number of children currently in the headerbar)
+ */
+ gbox = glade_widget_get_from_gobject (object);
+ if (!glade_widget_superuser ()) {
+ glade_widget_property_get (gbox, "size", &size);
+ glade_widget_property_set (gbox, "size", size);
+ }
+}
+
+void
+glade_hdy_header_bar_replace_child (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *current,
+ GObject *new_widget)
+{
+ GladeWidget *gbox;
+ gchar *special_child_type;
+ gint size;
+
+ special_child_type =
+ g_object_get_data (G_OBJECT (current), "special-child-type");
+
+ if (special_child_type && !strcmp (special_child_type, "title")) {
+ g_object_set_data (G_OBJECT (new_widget), "special-child-type", "title");
+ hdy_header_bar_set_custom_title (HDY_HEADER_BAR (container),
+ GTK_WIDGET (new_widget));
+
+ return;
+ }
+
+ g_object_set_data (G_OBJECT (new_widget), "special-child-type", NULL);
+
+ GWA_GET_CLASS (GTK_TYPE_CONTAINER)->replace_child (adaptor,
+ container,
+ current,
+ new_widget);
+
+ gbox = glade_widget_get_from_gobject (container);
+ if (!glade_widget_superuser ()) {
+ glade_widget_property_get (gbox, "size", &size);
+ glade_widget_property_set (gbox, "size", size);
+ }
+}
+
+gboolean
+glade_hdy_header_bar_verify_property (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *id,
+ const GValue *value)
+{
+ if (!strcmp (id, "size"))
+ return verify_size (object, value);
+ else if (GWA_GET_CLASS (GTK_TYPE_CONTAINER)->verify_property)
+ return GWA_GET_CLASS (GTK_TYPE_CONTAINER)->verify_property (adaptor,
+ object,
+ id,
+ value);
+
+ return TRUE;
+}
+
+static gint
+sort_children (GtkWidget *widget_a, GtkWidget *widget_b, GtkWidget *bar)
+{
+ GladeWidget *gwidget_a, *gwidget_b;
+ gint position_a, position_b;
+ GtkWidget *title;
+
+ /* title goes first */
+ title = hdy_header_bar_get_custom_title (HDY_HEADER_BAR (bar));
+ if (title == widget_a)
+ return -1;
+ if (title == widget_b)
+ return 1;
+
+ if ((gwidget_a = glade_widget_get_from_gobject (widget_a)) &&
+ (gwidget_b = glade_widget_get_from_gobject (widget_b))) {
+ glade_widget_pack_property_get (gwidget_a, "position", &position_a);
+ glade_widget_pack_property_get (gwidget_b, "position", &position_b);
+
+ /* If position is the same, try to give an stable order */
+ if (position_a == position_b)
+ return g_strcmp0 (glade_widget_get_name (gwidget_a),
+ glade_widget_get_name (gwidget_b));
+ } else {
+ gtk_container_child_get (GTK_CONTAINER (bar), widget_a,
+ "position", &position_a, NULL);
+ gtk_container_child_get (GTK_CONTAINER (bar), widget_b,
+ "position", &position_b, NULL);
+ }
+
+ return position_a - position_b;
+}
+
+GList *
+glade_hdy_header_bar_get_children (GladeWidgetAdaptor *adaptor,
+ GObject *container)
+{
+ GList *children = GWA_GET_CLASS (GTK_TYPE_CONTAINER)->get_children (adaptor, container);
+
+ return g_list_sort_with_data (children, (GCompareDataFunc) sort_children, container);
+}
+
+
+void
+glade_hdy_header_bar_child_set_property (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child,
+ const gchar *property_name,
+ const GValue *value)
+{
+ GladeWidget *gbox, *gchild, *gchild_iter;
+ GList *children, *list;
+ gboolean is_position;
+ gint old_position, iter_position, new_position;
+ static gboolean recursion = FALSE;
+
+ g_return_if_fail (HDY_IS_HEADER_BAR (container));
+ g_return_if_fail (GTK_IS_WIDGET (child));
+ g_return_if_fail (property_name != NULL || value != NULL);
+
+ gbox = glade_widget_get_from_gobject (container);
+ gchild = glade_widget_get_from_gobject (child);
+
+ g_return_if_fail (GLADE_IS_WIDGET (gbox));
+
+ /* Get old position */
+ if ((is_position = (strcmp (property_name, "position") == 0)) != FALSE) {
+ gtk_container_child_get (GTK_CONTAINER (container),
+ GTK_WIDGET (child),
+ "position", &old_position,
+ NULL);
+
+
+ /* Get the real value */
+ new_position = g_value_get_int (value);
+ }
+
+ if (is_position && recursion == FALSE) {
+ children = glade_widget_get_children (gbox);
+
+ for (list = children; list; list = list->next) {
+ gchild_iter = glade_widget_get_from_gobject (list->data);
+
+ if (gchild_iter == gchild) {
+ gtk_container_child_set (GTK_CONTAINER (container),
+ GTK_WIDGET (child),
+ "position", new_position,
+ NULL);
+
+ continue;
+ }
+
+ /* Get the old value from glade */
+ glade_widget_pack_property_get (gchild_iter, "position", &iter_position);
+
+ /* Search for the child at the old position and update it */
+ if (iter_position == new_position &&
+ glade_property_superuser () == FALSE) {
+ /* Update glade with the real value */
+ recursion = TRUE;
+ glade_widget_pack_property_set (gchild_iter, "position", old_position);
+ recursion = FALSE;
+
+ continue;
+ } else {
+ gtk_container_child_set (GTK_CONTAINER (container),
+ GTK_WIDGET (list->data),
+ "position", iter_position,
+ NULL);
+ }
+ }
+
+ for (list = children; list; list = list->next) {
+ gchild_iter = glade_widget_get_from_gobject (list->data);
+
+ /* Refresh values yet again */
+ glade_widget_pack_property_get (gchild_iter, "position", &iter_position);
+
+ gtk_container_child_set (GTK_CONTAINER (container),
+ GTK_WIDGET (list->data),
+ "position", iter_position,
+ NULL);
+ }
+
+ if (children)
+ g_list_free (children);
+ }
+
+ /* Chain Up */
+ if (!is_position)
+ GWA_GET_CLASS (GTK_TYPE_CONTAINER)->child_set_property (adaptor,
+ container,
+ child,
+ property_name,
+ value);
+}
diff --git a/subprojects/libhandy/glade/glade-hdy-header-bar.h b/subprojects/libhandy/glade/glade-hdy-header-bar.h
new file mode 100644
index 0000000..8f6c84a
--- /dev/null
+++ b/subprojects/libhandy/glade/glade-hdy-header-bar.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2020 Alexander Mikhaylenko <alexm@gnome.org>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#pragma once
+
+#include <gladeui/glade.h>
+
+#include <handy.h>
+
+
+void glade_hdy_header_bar_post_create (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GladeCreateReason reason);
+
+void glade_hdy_header_bar_action_activate (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *action_path);
+
+void glade_hdy_header_bar_child_action_activate (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *object,
+ const gchar *action_path);
+
+void glade_hdy_header_bar_get_property (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *id,
+ GValue *value);
+void glade_hdy_header_bar_set_property (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *id,
+ const GValue *value);
+
+void glade_hdy_header_bar_add_child (GladeWidgetAdaptor *adaptor,
+ GObject *parent,
+ GObject *child);
+void glade_hdy_header_bar_remove_child (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ GObject *child);
+void glade_hdy_header_bar_replace_child (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *current,
+ GObject *new_widget);
+
+gboolean glade_hdy_header_bar_verify_property (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *id,
+ const GValue *value);
+
+GList *glade_hdy_header_bar_get_children (GladeWidgetAdaptor *adaptor,
+ GObject *container);
+
+void glade_hdy_header_bar_child_set_property (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child,
+ const gchar *property_name,
+ const GValue *value);
diff --git a/subprojects/libhandy/glade/glade-hdy-header-group.c b/subprojects/libhandy/glade/glade-hdy-header-group.c
new file mode 100644
index 0000000..3ef633e
--- /dev/null
+++ b/subprojects/libhandy/glade/glade-hdy-header-group.c
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2018 Purism SPC
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Based on
+ * glade-gtk-size-group.c - GladeWidgetAdaptor for GtkSizeGroup
+ * Copyright (C) 2013 Tristan Van Berkom
+ */
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#include "glade-hdy-header-group.h"
+
+#include <gladeui/glade.h>
+#include "glade-hdy-utils.h"
+
+#define GLADE_TAG_HEADERGROUP_WIDGETS "headerbars"
+#define GLADE_TAG_HEADERGROUP_WIDGET "headerbar"
+
+
+static void
+glade_hdy_header_group_read_widgets (GladeWidget *widget, GladeXmlNode *node)
+{
+ GladeXmlNode *widgets_node;
+ GladeProperty *property;
+ gchar *string = NULL;
+
+ if ((widgets_node =
+ glade_xml_search_child (node, GLADE_TAG_HEADERGROUP_WIDGETS)) != NULL) {
+ GladeXmlNode *n;
+
+ for (n = glade_xml_node_get_children (widgets_node);
+ n; n = glade_xml_node_next (n)) {
+ gchar *widget_name, *tmp;
+
+ if (!glade_xml_node_verify (n, GLADE_TAG_HEADERGROUP_WIDGET))
+ continue;
+
+ widget_name = glade_xml_get_property_string_required
+ (n, GLADE_TAG_NAME, NULL);
+
+ if (string == NULL) {
+ string = widget_name;
+ } else if (widget_name != NULL) {
+ tmp =
+ g_strdup_printf ("%s%s%s", string, GLADE_PROPERTY_DEF_OBJECT_DELIMITER,
+ widget_name);
+ string = (g_free (string), tmp);
+ g_free (widget_name);
+ }
+ }
+ }
+
+ if (string) {
+ property = glade_widget_get_property (widget, "headerbars");
+ g_assert (property);
+
+ /* we must synchronize this directly after loading this project
+ * (i.e. lookup the actual objects after they've been parsed and
+ * are present).
+ */
+ g_object_set_data_full (G_OBJECT (property),
+ "glade-loaded-object", string, g_free);
+ }
+}
+
+void
+glade_hdy_header_group_read_widget (GladeWidgetAdaptor *adaptor,
+ GladeWidget *widget,
+ GladeXmlNode *node)
+{
+ if (!(glade_xml_node_verify_silent (node, GLADE_XML_TAG_WIDGET) ||
+ glade_xml_node_verify_silent (node, GLADE_XML_TAG_TEMPLATE)))
+ return;
+
+ /* First chain up and read in all the normal properties.. */
+ GWA_GET_CLASS (G_TYPE_OBJECT)->read_widget (adaptor, widget, node);
+
+ glade_hdy_header_group_read_widgets (widget, node);
+}
+
+
+static void
+glade_hdy_header_group_write_widgets (GladeWidget *widget,
+ GladeXmlContext *context,
+ GladeXmlNode *node)
+{
+ GladeXmlNode *widgets_node, *widget_node;
+ GList *widgets = NULL, *list;
+ GladeWidget *awidget;
+
+ widgets_node = glade_xml_node_new (context, GLADE_TAG_HEADERGROUP_WIDGETS);
+
+ if (glade_widget_property_get (widget, "headerbars", &widgets)) {
+ for (list = widgets; list; list = list->next) {
+ awidget = glade_widget_get_from_gobject (list->data);
+ widget_node =
+ glade_xml_node_new (context, GLADE_TAG_HEADERGROUP_WIDGET);
+ glade_xml_node_append_child (widgets_node, widget_node);
+ glade_xml_node_set_property_string (widget_node, GLADE_TAG_NAME,
+ glade_widget_get_name (awidget));
+ }
+ }
+
+ if (!glade_xml_node_get_children (widgets_node))
+ glade_xml_node_delete (widgets_node);
+ else
+ glade_xml_node_append_child (node, widgets_node);
+}
+
+
+void
+glade_hdy_header_group_write_widget (GladeWidgetAdaptor *adaptor,
+ GladeWidget *widget,
+ GladeXmlContext *context,
+ GladeXmlNode *node)
+{
+ if (!(glade_xml_node_verify_silent (node, GLADE_XML_TAG_WIDGET) ||
+ glade_xml_node_verify_silent (node, GLADE_XML_TAG_TEMPLATE)))
+ return;
+
+ /* First chain up and read in all the normal properties.. */
+ GWA_GET_CLASS (G_TYPE_OBJECT)->write_widget (adaptor, widget, context, node);
+
+ glade_hdy_header_group_write_widgets (widget, context, node);
+}
+
+
+void
+glade_hdy_header_group_set_property (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *property_name,
+ const GValue *value)
+{
+ if (!strcmp (property_name, "headerbars")) {
+ GSList *sg_widgets, *slist;
+ GList *widgets, *list;
+
+ /* remove old widgets */
+ if ((sg_widgets =
+ hdy_header_group_get_children (HDY_HEADER_GROUP (object))) != NULL) {
+ /* copy since we are modifying an internal list */
+ sg_widgets = g_slist_copy (sg_widgets);
+ for (slist = sg_widgets; slist; slist = slist->next)
+ hdy_header_group_remove_child (HDY_HEADER_GROUP (object),
+ HDY_HEADER_GROUP_CHILD (slist->data));
+ g_slist_free (sg_widgets);
+ }
+
+ /* add new widgets */
+ if ((widgets = g_value_get_boxed (value)) != NULL) {
+ for (list = widgets; list; list = list->next)
+ hdy_header_group_add_header_bar (HDY_HEADER_GROUP (object),
+ HDY_HEADER_BAR (list->data));
+ }
+ } else {
+ GWA_GET_CLASS (G_TYPE_OBJECT)->set_property (adaptor, object,
+ property_name, value);
+ }
+}
diff --git a/subprojects/libhandy/glade/glade-hdy-header-group.h b/subprojects/libhandy/glade/glade-hdy-header-group.h
new file mode 100644
index 0000000..5333c0f
--- /dev/null
+++ b/subprojects/libhandy/glade/glade-hdy-header-group.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2018 Purism SPC
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#pragma once
+
+#include <gladeui/glade.h>
+
+#include <handy.h>
+
+
+void glade_hdy_header_group_set_property (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *property_name,
+ const GValue *value);
+void glade_hdy_header_group_write_widget (GladeWidgetAdaptor *adaptor,
+ GladeWidget *widget,
+ GladeXmlContext *context,
+ GladeXmlNode *node);
+void glade_hdy_header_group_read_widget (GladeWidgetAdaptor *adaptor,
+ GladeWidget *widget,
+ GladeXmlNode *node);
diff --git a/subprojects/libhandy/glade/glade-hdy-leaflet.c b/subprojects/libhandy/glade/glade-hdy-leaflet.c
new file mode 100644
index 0000000..4032f9f
--- /dev/null
+++ b/subprojects/libhandy/glade/glade-hdy-leaflet.c
@@ -0,0 +1,533 @@
+/*
+ * Copyright (C) 2020 Alexander Mikhaylenko <alexm@gnome.org>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Based on
+ * glade-gtk-stack.c - GladeWidgetAdaptor for GtkStack
+ * Copyright (C) 2014 Red Hat, Inc.
+ */
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#include "glade-hdy-leaflet.h"
+
+#include <gladeui/glade.h>
+#include "glade-hdy-utils.h"
+
+#define PAGE_DISABLED_MESSAGE _("This property only applies when the leaflet is folded")
+
+static void
+selection_changed_cb (GladeProject *project,
+ GladeWidget *gwidget)
+{
+ GList *list;
+ GtkWidget *page, *sel_widget;
+ GtkContainer *container = GTK_CONTAINER (glade_widget_get_object (gwidget));
+ gint index;
+
+ if ((list = glade_project_selection_get (project)) != NULL &&
+ g_list_length (list) == 1) {
+ sel_widget = list->data;
+
+ if (GTK_IS_WIDGET (sel_widget) &&
+ gtk_widget_is_ancestor (sel_widget, GTK_WIDGET (container))) {
+ g_autoptr (GList) children = gtk_container_get_children (container);
+ GList *l;
+
+ index = 0;
+ for (l = children; l; l = l->next) {
+ page = l->data;
+ if (sel_widget == page ||
+ gtk_widget_is_ancestor (sel_widget, page)) {
+ glade_widget_property_set (gwidget, "page", index);
+
+ break;
+ }
+
+ index++;
+ }
+ }
+ }
+}
+
+static void
+project_changed_cb (GladeWidget *gwidget,
+ GParamSpec *pspec,
+ gpointer userdata)
+{
+ GladeProject *project = glade_widget_get_project (gwidget);
+ GladeProject *old_project = g_object_get_data (G_OBJECT (gwidget),
+ "project-ptr");
+
+ if (old_project)
+ g_signal_handlers_disconnect_by_func (G_OBJECT (old_project),
+ G_CALLBACK (selection_changed_cb),
+ gwidget);
+
+ if (project)
+ g_signal_connect (G_OBJECT (project),
+ "selection-changed",
+ G_CALLBACK (selection_changed_cb),
+ gwidget);
+
+ g_object_set_data (G_OBJECT (gwidget), "project-ptr", project);
+}
+
+static void
+add_named (GtkContainer *container,
+ GtkWidget *child,
+ const gchar *name)
+{
+ gtk_container_add_with_properties (container,
+ child,
+ "name", name,
+ NULL);
+}
+
+static void
+folded_changed_cb (HdyLeaflet *leaflet,
+ GParamSpec *pspec,
+ gpointer userdata)
+{
+ GladeWidget *gwidget = glade_widget_get_from_gobject (leaflet);
+ gboolean folded = hdy_leaflet_get_folded (leaflet);
+
+ glade_widget_property_set_sensitive (gwidget,
+ "page",
+ folded,
+ folded ? NULL : PAGE_DISABLED_MESSAGE);
+}
+
+void
+glade_hdy_leaflet_post_create (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GladeCreateReason reason)
+{
+ GladeWidget *gwidget = glade_widget_get_from_gobject (container);
+
+ if (reason == GLADE_CREATE_USER)
+ add_named (GTK_CONTAINER (container),
+ glade_placeholder_new (),
+ "page0");
+
+ g_signal_connect (G_OBJECT (gwidget),
+ "notify::project",
+ G_CALLBACK (project_changed_cb),
+ NULL);
+
+ project_changed_cb (gwidget, NULL, NULL);
+
+ if (HDY_IS_LEAFLET (container)) {
+ g_signal_connect (container,
+ "notify::folded",
+ G_CALLBACK (folded_changed_cb),
+ NULL);
+
+ folded_changed_cb (HDY_LEAFLET (container), NULL, NULL);
+ }
+}
+
+static GtkWidget *
+get_child_by_name (GtkContainer *container,
+ const gchar *name)
+{
+ g_autoptr (GList) children = gtk_container_get_children (container);
+ GList *l;
+
+ for (l = children; l; l = l->next) {
+ const gchar *child_name;
+
+ gtk_container_child_get (container, l->data, "name", &child_name, NULL);
+
+ if (child_name && !strcmp (child_name, name))
+ return l->data;
+ }
+
+ return NULL;
+}
+
+static gchar *
+get_unused_name (GtkContainer *container)
+{
+ gint i = 0;
+
+ while (TRUE) {
+ g_autofree gchar *name = g_strdup_printf ("page%d", i);
+
+ if (get_child_by_name (container, name) == NULL)
+ return g_steal_pointer (&name);
+
+ i++;
+ }
+
+ return NULL;
+}
+
+void
+glade_hdy_leaflet_child_action_activate (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *object,
+ const gchar *action_path)
+{
+ if (!strcmp (action_path, "insert_page_after") ||
+ !strcmp (action_path, "insert_page_before")) {
+ GladeWidget *parent = glade_widget_get_from_gobject (container);
+ GladeProperty *property;
+ g_autofree gchar *name = NULL;
+ GtkWidget *new_widget;
+ gint pages, index;
+
+ glade_widget_property_get (parent, "pages", &pages);
+
+ glade_command_push_group (_("Insert placeholder to %s"),
+ glade_widget_get_name (parent));
+
+ index = glade_hdy_get_child_index (GTK_CONTAINER (container), GTK_WIDGET (object));
+
+ if (!strcmp (action_path, "insert_page_after"))
+ index++;
+
+ name = get_unused_name (GTK_CONTAINER (container));
+ new_widget = glade_placeholder_new ();
+ add_named (GTK_CONTAINER (container), new_widget, name);
+ glade_hdy_reorder_child (GTK_CONTAINER (container), new_widget, index);
+ g_object_set (container, "visible-child", new_widget, NULL);
+
+ glade_hdy_sync_child_positions (GTK_CONTAINER (container));
+
+ property = glade_widget_get_property (parent, "pages");
+ glade_command_set_property (property, pages + 1);
+
+ property = glade_widget_get_property (parent, "page");
+ glade_command_set_property (property, index);
+
+ glade_command_pop_group ();
+ } else if (strcmp (action_path, "remove_page") == 0) {
+ GladeWidget *parent = glade_widget_get_from_gobject (container);
+ GladeProperty *property;
+ gint pages, index;
+
+ glade_widget_property_get (parent, "pages", &pages);
+
+ glade_command_push_group (_("Remove placeholder from %s"),
+ glade_widget_get_name (parent));
+ g_assert (GLADE_IS_PLACEHOLDER (object));
+ gtk_container_remove (GTK_CONTAINER (container), GTK_WIDGET (object));
+
+ glade_hdy_sync_child_positions (GTK_CONTAINER (container));
+
+ property = glade_widget_get_property (parent, "pages");
+ glade_command_set_property (property, pages - 1);
+
+ glade_widget_property_get (parent, "page", &index);
+ property = glade_widget_get_property (parent, "page");
+ glade_command_set_property (property, index);
+
+ glade_command_pop_group ();
+ } else {
+ GWA_GET_CLASS (GTK_TYPE_CONTAINER)->child_action_activate (adaptor,
+ container,
+ object,
+ action_path);
+ }
+}
+
+typedef struct {
+ gint size;
+ gboolean include_placeholders;
+} ChildData;
+
+static void
+count_child (GtkWidget *child,
+ gpointer data)
+{
+ ChildData *cdata = data;
+
+ if (cdata->include_placeholders || !GLADE_IS_PLACEHOLDER (child))
+ cdata->size++;
+}
+
+static gint
+get_n_pages (GtkContainer *container,
+ gboolean include_placeholders)
+{
+ ChildData data;
+
+ data.size = 0;
+ data.include_placeholders = include_placeholders;
+ gtk_container_foreach (container, count_child, &data);
+
+ return data.size;
+}
+
+static void
+set_n_pages (GObject *object,
+ const GValue *value)
+{
+ GladeWidget *gbox;
+ GtkContainer *container = GTK_CONTAINER (object);
+ GtkWidget *child;
+ gint new_size = g_value_get_int (value);
+ gint old_size = get_n_pages (container, TRUE);
+ gint i, page;
+
+ if (old_size == new_size)
+ return;
+
+ for (i = old_size; i < new_size; i++) {
+ g_autofree gchar *name = get_unused_name (container);
+ child = glade_placeholder_new ();
+ add_named (container, child, name);
+ }
+
+ for (i = old_size; i > 0; i--) {
+ if (old_size <= new_size)
+ break;
+
+ child = glade_hdy_get_nth_child (container, i - 1);
+ if (GLADE_IS_PLACEHOLDER (child)) {
+ gtk_container_remove (container, child);
+ old_size--;
+ }
+ }
+
+ gbox = glade_widget_get_from_gobject (container);
+ glade_widget_property_get (gbox, "page", &page);
+ glade_widget_property_set (gbox, "page", page);
+}
+
+static void
+set_page (GObject *object,
+ const GValue *value)
+{
+ gint new_page = g_value_get_int (value);
+ GtkWidget *child = glade_hdy_get_nth_child (GTK_CONTAINER (object), new_page);
+
+ if (!child)
+ return;
+
+ g_object_set (object, "visible-child", child, NULL);
+}
+
+static gint
+get_page (GtkContainer *container)
+{
+ GtkWidget *child;
+
+ g_object_get (container, "visible-child", &child, NULL);
+
+ return glade_hdy_get_child_index (container, child);
+}
+
+void
+glade_hdy_leaflet_set_property (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *id,
+ const GValue *value)
+{
+ if (!strcmp (id, "pages"))
+ set_n_pages (object, value);
+ else if (!strcmp (id, "page"))
+ set_page (object, value);
+ else
+ GWA_GET_CLASS (GTK_TYPE_CONTAINER)->set_property (adaptor, object, id, value);
+}
+
+void
+glade_hdy_leaflet_get_property (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *id,
+ GValue *value)
+{
+ if (!strcmp (id, "pages")) {
+ g_value_reset (value);
+ g_value_set_int (value, get_n_pages (GTK_CONTAINER (object), TRUE));
+ } else if (!strcmp (id, "page")) {
+ g_value_reset (value);
+ g_value_set_int (value, get_page (GTK_CONTAINER (object)));
+ } else {
+ GWA_GET_CLASS (GTK_TYPE_CONTAINER)->get_property (adaptor, object, id, value);
+ }
+}
+
+static gboolean
+verify_n_pages (GObject *object,
+ const GValue *value)
+{
+ gint new_size = g_value_get_int (value);
+ gint old_size = get_n_pages (GTK_CONTAINER (object), FALSE);
+
+ return old_size <= new_size;
+}
+
+static gboolean
+verify_page (GObject *object,
+ const GValue *value)
+{
+ gint page = g_value_get_int (value);
+ gint pages = get_n_pages (GTK_CONTAINER (object), TRUE);
+
+ if (page < 0 && page >= pages)
+ return FALSE;
+
+ if (HDY_IS_LEAFLET (object)) {
+ GtkWidget *child = glade_hdy_get_nth_child (GTK_CONTAINER (object), page);
+ gboolean navigatable;
+
+ gtk_container_child_get (GTK_CONTAINER (object), child,
+ "navigatable", &navigatable,
+ NULL);
+
+ if (!navigatable)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean
+glade_hdy_leaflet_verify_property (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *id,
+ const GValue *value)
+{
+ if (!strcmp (id, "pages"))
+ return verify_n_pages (object, value);
+ else if (!strcmp (id, "page"))
+ return verify_page (object, value);
+ else if (GWA_GET_CLASS (GTK_TYPE_CONTAINER)->verify_property)
+ return GWA_GET_CLASS (GTK_TYPE_CONTAINER)->verify_property (adaptor, object, id, value);
+
+ return TRUE;
+}
+
+
+void
+glade_hdy_leaflet_get_child_property (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child,
+ const gchar *property_name,
+ GValue *value)
+{
+ if (strcmp (property_name, "position") == 0)
+ g_value_set_int (value, glade_hdy_get_child_index (GTK_CONTAINER (container),
+ GTK_WIDGET (child)));
+ else
+ GWA_GET_CLASS (GTK_TYPE_CONTAINER)->child_get_property (adaptor,
+ container,
+ child,
+ property_name,
+ value);
+}
+
+void
+glade_hdy_leaflet_set_child_property (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child,
+ const gchar *property_name,
+ GValue *value)
+{
+ if (strcmp (property_name, "position") == 0) {
+ glade_hdy_reorder_child (GTK_CONTAINER (container),
+ GTK_WIDGET (child),
+ g_value_get_int (value));
+
+ glade_hdy_sync_child_positions (GTK_CONTAINER (container));
+ } else {
+ GWA_GET_CLASS (GTK_TYPE_CONTAINER)->child_set_property (adaptor,
+ container,
+ child,
+ property_name,
+ value);
+ }
+}
+
+void
+glade_hdy_leaflet_add_child (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ GObject *child)
+{
+ GladeWidget *gbox, *gchild;
+ gint pages, page;
+
+ if (!glade_widget_superuser () && !GLADE_IS_PLACEHOLDER (child)) {
+ g_autoptr (GList) children = gtk_container_get_children (GTK_CONTAINER (object));
+ GList *l;
+
+ for (l = g_list_last (children); l; l = l->prev) {
+ GtkWidget *widget = l->data;
+ if (GLADE_IS_PLACEHOLDER (widget)) {
+ gtk_container_remove (GTK_CONTAINER (object), widget);
+
+ break;
+ }
+ }
+ }
+
+ gtk_container_add (GTK_CONTAINER (object), GTK_WIDGET (child));
+
+ glade_hdy_sync_child_positions (GTK_CONTAINER (object));
+
+ gchild = glade_widget_get_from_gobject (child);
+ if (gchild != NULL)
+ glade_widget_set_pack_action_visible (gchild, "remove_page", FALSE);
+
+ gbox = glade_widget_get_from_gobject (object);
+ glade_widget_property_get (gbox, "pages", &pages);
+ glade_widget_property_set (gbox, "pages", pages);
+ glade_widget_property_get (gbox, "page", &page);
+ glade_widget_property_set (gbox, "page", page);
+}
+
+void
+glade_hdy_leaflet_remove_child (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ GObject *child)
+{
+ GladeWidget *gbox;
+ gint pages, page;
+
+ gtk_container_remove (GTK_CONTAINER (object), GTK_WIDGET (child));
+
+ glade_hdy_sync_child_positions (GTK_CONTAINER (object));
+
+ gbox = glade_widget_get_from_gobject (object);
+ glade_widget_property_get (gbox, "pages", &pages);
+ glade_widget_property_set (gbox, "pages", pages);
+ glade_widget_property_get (gbox, "page", &page);
+ glade_widget_property_set (gbox, "page", page);
+}
+
+void
+glade_hdy_leaflet_replace_child (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *current,
+ GObject *new_widget)
+{
+ GladeWidget *gchild;
+ GladeWidget *gbox;
+ gint pages, page, index;
+
+ index = glade_hdy_get_child_index (GTK_CONTAINER (container), GTK_WIDGET (current));
+ gtk_container_remove (GTK_CONTAINER (container), GTK_WIDGET (current));
+ gtk_container_add (GTK_CONTAINER (container), GTK_WIDGET (new_widget));
+ glade_hdy_reorder_child (GTK_CONTAINER (container), GTK_WIDGET (new_widget), index);
+
+ glade_hdy_sync_child_positions (GTK_CONTAINER (container));
+
+ gbox = glade_widget_get_from_gobject (container);
+
+ gchild = glade_widget_get_from_gobject (new_widget);
+ if (gchild != NULL)
+ glade_widget_set_pack_action_visible (gchild, "remove_page", FALSE);
+
+ /* NOTE: make sure to sync this at the end because new_widget could be
+ * a placeholder and syncing these properties could destroy it.
+ */
+ glade_widget_property_get (gbox, "pages", &pages);
+ glade_widget_property_set (gbox, "pages", pages);
+ glade_widget_property_get (gbox, "page", &page);
+ glade_widget_property_set (gbox, "page", page);
+}
diff --git a/subprojects/libhandy/glade/glade-hdy-leaflet.h b/subprojects/libhandy/glade/glade-hdy-leaflet.h
new file mode 100644
index 0000000..9f274b0
--- /dev/null
+++ b/subprojects/libhandy/glade/glade-hdy-leaflet.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2020 Alexander Mikhaylenko <alexm@gnome.org>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#pragma once
+
+#include <gladeui/glade.h>
+
+#include <handy.h>
+
+
+void glade_hdy_leaflet_post_create (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GladeCreateReason reason);
+
+void glade_hdy_leaflet_child_action_activate (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *object,
+ const gchar *action_path);
+
+void glade_hdy_leaflet_set_property (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *id,
+ const GValue *value);
+void glade_hdy_leaflet_get_property (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *id,
+ GValue *value);
+gboolean glade_hdy_leaflet_verify_property (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *id,
+ const GValue *value);
+
+void glade_hdy_leaflet_get_child_property (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child,
+ const gchar *property_name,
+ GValue *value);
+void glade_hdy_leaflet_set_child_property (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child,
+ const gchar *property_name,
+ GValue *value);
+
+void glade_hdy_leaflet_add_child (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child);
+void glade_hdy_leaflet_remove_child (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child);
+void glade_hdy_leaflet_replace_child (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *current,
+ GObject *new_widget);
diff --git a/subprojects/libhandy/glade/glade-hdy-preferences-page.c b/subprojects/libhandy/glade/glade-hdy-preferences-page.c
new file mode 100644
index 0000000..f1a62c7
--- /dev/null
+++ b/subprojects/libhandy/glade/glade-hdy-preferences-page.c
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2020 Alexander Mikhaylenko <alexm@gnome.org>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Based on
+ * glade-gtk-stack.c - GladeWidgetAdaptor for GtkStack
+ * Copyright (C) 2014 Red Hat, Inc.
+ */
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#include "glade-hdy-preferences-page.h"
+
+#include <gladeui/glade.h>
+#include "glade-hdy-utils.h"
+
+static GtkWidget *
+get_child_by_title (GtkContainer *container,
+ const gchar *title)
+{
+ g_autoptr (GList) children = gtk_container_get_children (container);
+ GList *l;
+
+ for (l = children; l; l = l->next) {
+ const gchar *child_title;
+
+ g_assert (HDY_IS_PREFERENCES_GROUP (l->data));
+
+ child_title = hdy_preferences_group_get_title (HDY_PREFERENCES_GROUP (l->data));
+
+ if (child_title && !strcmp (child_title, title))
+ return l->data;
+ }
+
+ return NULL;
+}
+
+static gchar *
+get_unused_title (GtkContainer *container)
+{
+ gint i = 1;
+
+ while (TRUE) {
+ g_autofree gchar *title = g_strdup_printf ("Group %d", i);
+
+ if (get_child_by_title (container, title) == NULL)
+ return g_steal_pointer (&title);
+
+ i++;
+ }
+
+ return NULL;
+}
+
+static void
+add_group (GladeWidgetAdaptor *adaptor,
+ GObject *container)
+{
+ GladeWidget *gwidget = glade_widget_get_from_gobject (container);
+ GladeWidget *gpage;
+ GladeWidgetAdaptor *page_adaptor;
+ g_autofree gchar *title = get_unused_title (GTK_CONTAINER (container));
+
+ page_adaptor = glade_widget_adaptor_get_by_type (HDY_TYPE_PREFERENCES_GROUP);
+
+ gpage = glade_widget_adaptor_create_widget (page_adaptor, FALSE,
+ "parent", gwidget,
+ "project", glade_widget_get_project (gwidget),
+ NULL);
+
+ glade_widget_property_set (gpage, "title", title);
+
+ glade_widget_add_child (gwidget, gpage, FALSE);
+}
+
+void
+glade_hdy_preferences_page_post_create (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GladeCreateReason reason)
+{
+ if (reason == GLADE_CREATE_USER) {
+ add_group (adaptor, container);
+ add_group (adaptor, container);
+ add_group (adaptor, container);
+ }
+}
+
+gboolean
+glade_hdy_preferences_page_add_verify (GladeWidgetAdaptor *adaptor,
+ GtkWidget *object,
+ GtkWidget *child,
+ gboolean user_feedback)
+{
+ if (!HDY_IS_PREFERENCES_GROUP (child)) {
+ if (user_feedback) {
+ GladeWidgetAdaptor *page_adaptor =
+ glade_widget_adaptor_get_by_type (HDY_TYPE_PREFERENCES_GROUP);
+
+ glade_util_ui_message (glade_app_get_window (),
+ GLADE_UI_INFO, NULL,
+ ONLY_THIS_GOES_IN_THAT_MSG,
+ glade_widget_adaptor_get_title (page_adaptor),
+ glade_widget_adaptor_get_title (adaptor));
+ }
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void
+glade_hdy_preferences_page_add_child (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ GObject *child)
+{
+ gtk_container_add (GTK_CONTAINER (object), GTK_WIDGET (child));
+}
+
+void
+glade_hdy_preferences_page_remove_child (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ GObject *child)
+{
+ gtk_container_remove (GTK_CONTAINER (object), GTK_WIDGET (child));
+}
+
+void
+glade_hdy_preferences_page_replace_child (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ GObject *current,
+ GObject *new_widget)
+{
+ gint index = glade_hdy_get_child_index (GTK_CONTAINER (object), GTK_WIDGET (current));
+ gtk_container_remove (GTK_CONTAINER (object), GTK_WIDGET (current));
+ gtk_container_add (GTK_CONTAINER (object), GTK_WIDGET (new_widget));
+ glade_hdy_reorder_child (GTK_CONTAINER (object), GTK_WIDGET (new_widget), index);
+}
+
+GList *
+glade_hdy_preferences_page_get_children (GladeWidgetAdaptor *adaptor,
+ GObject *object)
+{
+ return gtk_container_get_children (GTK_CONTAINER (object));
+}
+
+void
+glade_hdy_preferences_page_action_activate (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *action_path)
+{
+ GladeWidget *parent = glade_widget_get_from_gobject (object);
+
+ if (!g_strcmp0 (action_path, "add_group")) {
+ g_autofree gchar *title = get_unused_title (GTK_CONTAINER (object));
+ GladeWidget *gchild;
+
+ glade_command_push_group (_("Add group to %s"),
+ glade_widget_get_name (parent));
+
+ gchild = glade_command_create (glade_widget_adaptor_get_by_type (HDY_TYPE_PREFERENCES_GROUP),
+ parent,
+ NULL,
+ glade_widget_get_project (parent));
+
+ glade_widget_property_set (gchild, "title", title);
+
+ glade_command_pop_group ();
+ } else {
+ GWA_GET_CLASS (GTK_TYPE_CONTAINER)->action_activate (adaptor,
+ object,
+ action_path);
+ }
+}
+
+void
+glade_hdy_preferences_page_child_set_property (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child,
+ const gchar *property_name,
+ const GValue *value)
+{
+ if (!g_strcmp0 (property_name, "position")) {
+ GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (child));
+
+ gtk_container_child_set_property (GTK_CONTAINER (parent),
+ GTK_WIDGET (child), property_name, value);
+ } else {
+ GWA_GET_CLASS (GTK_TYPE_CONTAINER)->child_set_property (adaptor,
+ container,
+ child,
+ property_name,
+ value);
+ }
+}
+
+void
+glade_hdy_preferences_page_child_get_property (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child,
+ const gchar *property_name,
+ GValue *value)
+{
+ if (!g_strcmp0 (property_name, "position")) {
+ GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (child));
+
+ gtk_container_child_get_property (GTK_CONTAINER (parent),
+ GTK_WIDGET (child), property_name, value);
+ } else {
+ GWA_GET_CLASS (GTK_TYPE_CONTAINER)->child_get_property (adaptor,
+ container,
+ child,
+ property_name,
+ value);
+ }
+}
diff --git a/subprojects/libhandy/glade/glade-hdy-preferences-page.h b/subprojects/libhandy/glade/glade-hdy-preferences-page.h
new file mode 100644
index 0000000..2fd3f88
--- /dev/null
+++ b/subprojects/libhandy/glade/glade-hdy-preferences-page.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2019 Alexander Mikhaylenko <exalm7659@gmail.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#pragma once
+
+#include <gladeui/glade.h>
+
+#include <handy.h>
+
+void glade_hdy_preferences_page_post_create (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GladeCreateReason reason);
+
+gboolean glade_hdy_preferences_page_add_verify (GladeWidgetAdaptor *adaptor,
+ GtkWidget *object,
+ GtkWidget *child,
+ gboolean user_feedback);
+
+void glade_hdy_preferences_page_add_child (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child);
+void glade_hdy_preferences_page_remove_child (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child);
+void glade_hdy_preferences_page_replace_child (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *current,
+ GObject *new_widget);
+
+GList *glade_hdy_preferences_page_get_children (GladeWidgetAdaptor *adaptor,
+ GObject *object);
+
+void glade_hdy_preferences_page_action_activate (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *action_path);
+
+void glade_hdy_preferences_page_child_set_property (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child,
+ const gchar *property_name,
+ const GValue *value);
+
+void glade_hdy_preferences_page_child_get_property (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child,
+ const gchar *property_name,
+ GValue *value);
diff --git a/subprojects/libhandy/glade/glade-hdy-preferences-window.c b/subprojects/libhandy/glade/glade-hdy-preferences-window.c
new file mode 100644
index 0000000..1add369
--- /dev/null
+++ b/subprojects/libhandy/glade/glade-hdy-preferences-window.c
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2020 Alexander Mikhaylenko <alexm@gnome.org>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Based on
+ * glade-gtk-stack.c - GladeWidgetAdaptor for GtkStack
+ * Copyright (C) 2014 Red Hat, Inc.
+ */
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#include "glade-hdy-preferences-window.h"
+
+#include <gladeui/glade.h>
+#include "glade-hdy-utils.h"
+
+static void
+selection_changed_cb (GladeProject *project,
+ GladeWidget *gwidget)
+{
+ GList *list;
+ GtkWidget *page, *sel_widget;
+ GtkContainer *container = GTK_CONTAINER (glade_widget_get_object (gwidget));
+ gint index;
+
+ if ((list = glade_project_selection_get (project)) != NULL &&
+ g_list_length (list) == 1) {
+ sel_widget = list->data;
+
+ if (GTK_IS_WIDGET (sel_widget) &&
+ gtk_widget_is_ancestor (sel_widget, GTK_WIDGET (container))) {
+ g_autoptr (GList) children = gtk_container_get_children (container);
+ GList *l;
+
+ index = 0;
+ for (l = children; l; l = l->next) {
+ page = l->data;
+ if (sel_widget == page ||
+ gtk_widget_is_ancestor (sel_widget, page)) {
+ GtkWidget *parent = gtk_widget_get_parent (page);
+
+ g_object_set (parent, "visible-child", page, NULL);
+
+ break;
+ }
+
+ index++;
+ }
+ }
+ }
+}
+
+static void
+project_changed_cb (GladeWidget *gwidget,
+ GParamSpec *pspec,
+ gpointer userdata)
+{
+ GladeProject *project = glade_widget_get_project (gwidget);
+ GladeProject *old_project = g_object_get_data (G_OBJECT (gwidget),
+ "project-ptr");
+
+ if (old_project)
+ g_signal_handlers_disconnect_by_func (G_OBJECT (old_project),
+ G_CALLBACK (selection_changed_cb),
+ gwidget);
+
+ if (project)
+ g_signal_connect (G_OBJECT (project),
+ "selection-changed",
+ G_CALLBACK (selection_changed_cb),
+ gwidget);
+
+ g_object_set_data (G_OBJECT (gwidget), "project-ptr", project);
+}
+
+static GtkWidget *
+get_child_by_title (GtkContainer *container,
+ const gchar *title)
+{
+ g_autoptr (GList) children = gtk_container_get_children (container);
+ GList *l;
+
+ for (l = children; l; l = l->next) {
+ const gchar *child_title;
+
+ g_assert (HDY_IS_PREFERENCES_PAGE (l->data));
+
+ child_title = hdy_preferences_page_get_title (HDY_PREFERENCES_PAGE (l->data));
+
+ if (child_title && !strcmp (child_title, title))
+ return l->data;
+ }
+
+ return NULL;
+}
+
+static gchar *
+get_unused_title (GtkContainer *container)
+{
+ gint i = 1;
+
+ while (TRUE) {
+ g_autofree gchar *title = g_strdup_printf ("Page %d", i);
+
+ if (get_child_by_title (container, title) == NULL)
+ return g_steal_pointer (&title);
+
+ i++;
+ }
+
+ return NULL;
+}
+
+static void
+add_page (GladeWidgetAdaptor *adaptor,
+ GObject *container)
+{
+ GladeWidget *gwidget = glade_widget_get_from_gobject (container);
+ GladeWidget *gpage;
+ GladeWidgetAdaptor *page_adaptor;
+ g_autofree gchar *title = get_unused_title (GTK_CONTAINER (container));
+
+ page_adaptor = glade_widget_adaptor_get_by_type (HDY_TYPE_PREFERENCES_PAGE);
+
+ gpage = glade_widget_adaptor_create_widget (page_adaptor, FALSE,
+ "parent", gwidget,
+ "project", glade_widget_get_project (gwidget),
+ NULL);
+
+ glade_widget_property_set (gpage, "title", title);
+
+ glade_widget_add_child (gwidget, gpage, FALSE);
+}
+
+void
+glade_hdy_preferences_window_post_create (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GladeCreateReason reason)
+{
+ GladeWidget *gwidget = glade_widget_get_from_gobject (container);
+
+ if (reason == GLADE_CREATE_USER) {
+ add_page (adaptor, container);
+ add_page (adaptor, container);
+ add_page (adaptor, container);
+ }
+
+ g_signal_connect (G_OBJECT (gwidget),
+ "notify::project",
+ G_CALLBACK (project_changed_cb),
+ NULL);
+
+ project_changed_cb (gwidget, NULL, NULL);
+}
+
+gboolean
+glade_hdy_preferences_window_add_verify (GladeWidgetAdaptor *adaptor,
+ GtkWidget *object,
+ GtkWidget *child,
+ gboolean user_feedback)
+{
+ if (!HDY_IS_PREFERENCES_PAGE (child)) {
+ if (user_feedback) {
+ GladeWidgetAdaptor *page_adaptor =
+ glade_widget_adaptor_get_by_type (HDY_TYPE_PREFERENCES_PAGE);
+
+ glade_util_ui_message (glade_app_get_window (),
+ GLADE_UI_INFO, NULL,
+ ONLY_THIS_GOES_IN_THAT_MSG,
+ glade_widget_adaptor_get_title (page_adaptor),
+ glade_widget_adaptor_get_title (adaptor));
+ }
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void
+glade_hdy_preferences_window_add_child (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ GObject *child)
+{
+ gtk_container_add (GTK_CONTAINER (object), GTK_WIDGET (child));
+}
+
+void
+glade_hdy_preferences_window_remove_child (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ GObject *child)
+{
+ gtk_container_remove (GTK_CONTAINER (object), GTK_WIDGET (child));
+}
+
+void
+glade_hdy_preferences_window_replace_child (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ GObject *current,
+ GObject *new_widget)
+{
+ gint index = glade_hdy_get_child_index (GTK_CONTAINER (object), GTK_WIDGET (current));
+ gtk_container_remove (GTK_CONTAINER (object), GTK_WIDGET (current));
+ gtk_container_add (GTK_CONTAINER (object), GTK_WIDGET (new_widget));
+ glade_hdy_reorder_child (GTK_CONTAINER (object), GTK_WIDGET (new_widget), index);
+}
+
+GList *
+glade_hdy_preferences_window_get_children (GladeWidgetAdaptor *adaptor,
+ GObject *object)
+{
+ return gtk_container_get_children (GTK_CONTAINER (object));
+}
+
+void
+glade_hdy_preferences_window_action_activate (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *action_path)
+{
+ GladeWidget *parent = glade_widget_get_from_gobject (object);
+
+ if (!g_strcmp0 (action_path, "add_page")) {
+ g_autofree gchar *title = get_unused_title (GTK_CONTAINER (object));
+ GladeWidget *gchild;
+
+ glade_command_push_group (_("Add page to %s"),
+ glade_widget_get_name (parent));
+
+ gchild = glade_command_create (glade_widget_adaptor_get_by_type (HDY_TYPE_PREFERENCES_PAGE),
+ parent,
+ NULL,
+ glade_widget_get_project (parent));
+
+ glade_widget_property_set (gchild, "title", title);
+
+ glade_command_pop_group ();
+ } else {
+ GWA_GET_CLASS (GTK_TYPE_CONTAINER)->action_activate (adaptor,
+ object,
+ action_path);
+ }
+}
+
+void
+glade_hdy_preferences_window_child_set_property (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child,
+ const gchar *property_name,
+ const GValue *value)
+{
+ if (!g_strcmp0 (property_name, "position")) {
+ GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (child));
+
+ gtk_container_child_set_property (GTK_CONTAINER (parent),
+ GTK_WIDGET (child), property_name, value);
+ } else {
+ GWA_GET_CLASS (GTK_TYPE_CONTAINER)->child_set_property (adaptor,
+ container,
+ child,
+ property_name,
+ value);
+ }
+}
+
+void
+glade_hdy_preferences_window_child_get_property (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child,
+ const gchar *property_name,
+ GValue *value)
+{
+ if (!g_strcmp0 (property_name, "position")) {
+ GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (child));
+
+ gtk_container_child_get_property (GTK_CONTAINER (parent),
+ GTK_WIDGET (child), property_name, value);
+ } else {
+ GWA_GET_CLASS (GTK_TYPE_CONTAINER)->child_get_property (adaptor,
+ container,
+ child,
+ property_name,
+ value);
+ }
+}
diff --git a/subprojects/libhandy/glade/glade-hdy-preferences-window.h b/subprojects/libhandy/glade/glade-hdy-preferences-window.h
new file mode 100644
index 0000000..e4f1f37
--- /dev/null
+++ b/subprojects/libhandy/glade/glade-hdy-preferences-window.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2019 Alexander Mikhaylenko <exalm7659@gmail.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#pragma once
+
+#include <gladeui/glade.h>
+
+#include <handy.h>
+
+void glade_hdy_preferences_window_post_create (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GladeCreateReason reason);
+
+gboolean glade_hdy_preferences_window_add_verify (GladeWidgetAdaptor *adaptor,
+ GtkWidget *object,
+ GtkWidget *child,
+ gboolean user_feedback);
+
+void glade_hdy_preferences_window_add_child (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child);
+void glade_hdy_preferences_window_remove_child (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child);
+void glade_hdy_preferences_window_replace_child (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *current,
+ GObject *new_widget);
+
+GList *glade_hdy_preferences_window_get_children (GladeWidgetAdaptor *adaptor,
+ GObject *object);
+
+void glade_hdy_preferences_window_action_activate (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *action_path);
+
+void glade_hdy_preferences_window_child_set_property (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child,
+ const gchar *property_name,
+ const GValue *value);
+
+void glade_hdy_preferences_window_child_get_property (GladeWidgetAdaptor *adaptor,
+ GObject *container,
+ GObject *child,
+ const gchar *property_name,
+ GValue *value);
diff --git a/subprojects/libhandy/glade/glade-hdy-search-bar.c b/subprojects/libhandy/glade/glade-hdy-search-bar.c
new file mode 100644
index 0000000..c13f1eb
--- /dev/null
+++ b/subprojects/libhandy/glade/glade-hdy-search-bar.c
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2020 Alexander Mikhaylenko <alexm@gnome.org>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Based on
+ * glade-gtk-searchbar.c - GladeWidgetAdaptor for GtkSearchBar
+ * Copyright (C) 2014 Red Hat, Inc.
+ */
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#include "glade-hdy-search-bar.h"
+
+#include <gladeui/glade.h>
+
+void
+glade_hdy_search_bar_post_create (GladeWidgetAdaptor *adaptor,
+ GObject *widget,
+ GladeCreateReason reason)
+{
+ if (reason == GLADE_CREATE_USER) {
+ GtkWidget *child = glade_placeholder_new ();
+ gtk_container_add (GTK_CONTAINER (widget), child);
+ g_object_set_data (G_OBJECT (widget), "child", child);
+ }
+
+ hdy_search_bar_set_search_mode (HDY_SEARCH_BAR (widget), TRUE);
+ hdy_search_bar_set_show_close_button (HDY_SEARCH_BAR (widget), FALSE);
+}
+
+void
+glade_hdy_search_bar_add_child (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ GObject *child)
+{
+ GObject *current = g_object_get_data (G_OBJECT (object), "child");
+
+ if (current)
+ gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (GTK_WIDGET (current))),
+ GTK_WIDGET (current));
+
+ gtk_container_add (GTK_CONTAINER (object), GTK_WIDGET (child));
+ g_object_set_data (G_OBJECT (object), "child", child);
+}
+
+void
+glade_hdy_search_bar_remove_child (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ GObject *child)
+{
+ GObject *current = g_object_get_data (G_OBJECT (object), "child");
+ GtkWidget *new_child;
+
+ if (current != child)
+ return;
+
+ gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (GTK_WIDGET (child))), GTK_WIDGET (child));
+ new_child = glade_placeholder_new ();
+ gtk_container_add (GTK_CONTAINER (object), new_child);
+ g_object_set_data (G_OBJECT (object), "child", new_child);
+}
+
+void
+glade_hdy_search_bar_replace_child (GladeWidgetAdaptor *adaptor,
+ GtkWidget *container,
+ GtkWidget *current,
+ GtkWidget *new_widget)
+{
+ if (current != (GtkWidget *) g_object_get_data (G_OBJECT (container), "child"))
+ return;
+
+ gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (GTK_WIDGET (current))),
+ GTK_WIDGET (current));
+ gtk_container_add (GTK_CONTAINER (container), new_widget);
+ g_object_set_data (G_OBJECT (container), "child", new_widget);
+}
+
+GList *
+glade_hdy_search_bar_get_children (GladeWidgetAdaptor *adaptor,
+ GObject *widget)
+{
+ GObject *current = g_object_get_data (G_OBJECT (widget), "child");
+
+ return g_list_append (NULL, current);
+}
+
+gboolean
+glade_hdy_search_bar_add_verify (GladeWidgetAdaptor *adaptor,
+ GtkWidget *container,
+ GtkWidget *child,
+ gboolean user_feedback)
+{
+ GObject *current = g_object_get_data (G_OBJECT (container), "child");
+
+ if (!GLADE_IS_PLACEHOLDER (current)) {
+ if (user_feedback)
+ glade_util_ui_message (glade_app_get_window (),
+ GLADE_UI_INFO, NULL,
+ _("Search bar is already full"));
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/subprojects/libhandy/glade/glade-hdy-search-bar.h b/subprojects/libhandy/glade/glade-hdy-search-bar.h
new file mode 100644
index 0000000..05306b8
--- /dev/null
+++ b/subprojects/libhandy/glade/glade-hdy-search-bar.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 Alexander Mikhaylenko <alexm@gnome.org>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#pragma once
+
+#include <gladeui/glade.h>
+
+#include <handy.h>
+
+void glade_hdy_search_bar_post_create (GladeWidgetAdaptor *adaptor,
+ GObject *widget,
+ GladeCreateReason reason);
+
+void glade_hdy_search_bar_add_child (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ GObject *child);
+void glade_hdy_search_bar_remove_child (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ GObject *child);
+void glade_hdy_search_bar_replace_child (GladeWidgetAdaptor *adaptor,
+ GtkWidget *container,
+ GtkWidget *current,
+ GtkWidget *new_widget);
+
+GList *glade_hdy_search_bar_get_children (GladeWidgetAdaptor *adaptor,
+ GObject *widget);
+
+gboolean glade_hdy_search_bar_add_verify (GladeWidgetAdaptor *adaptor,
+ GtkWidget *container,
+ GtkWidget *child,
+ gboolean user_feedback);
diff --git a/subprojects/libhandy/glade/glade-hdy-swipe-group.c b/subprojects/libhandy/glade/glade-hdy-swipe-group.c
new file mode 100644
index 0000000..059138d
--- /dev/null
+++ b/subprojects/libhandy/glade/glade-hdy-swipe-group.c
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2019 Alexander Mikhaylenko <exalm7659@gmail.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Based on
+ * hdy-header-group.c - GladeWidgetAdaptor for HdyHeaderGroup
+ * Copyright (C) 2018 Purism SPC
+ * Copyright (C) 2013 Tristan Van Berkom
+ */
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#include "glade-hdy-swipe-group.h"
+
+#include <gladeui/glade.h>
+#include "glade-hdy-utils.h"
+
+#define PROP_SWIPEABLES "swipeables"
+#define GLADE_TAG_SWIPEGROUP_SWIPEABLES "swipeables"
+#define GLADE_TAG_SWIPEGROUP_SWIPEABLE "swipeable"
+
+static void
+glade_hdy_swipe_group_read_widgets (GladeWidget *widget,
+ GladeXmlNode *node)
+{
+ GladeXmlNode *widgets_node;
+ GladeProperty *property;
+ gchar *string = NULL;
+
+ if ((widgets_node =
+ glade_xml_search_child (node, GLADE_TAG_SWIPEGROUP_SWIPEABLES)) != NULL) {
+ GladeXmlNode *n;
+
+ for (n = glade_xml_node_get_children (widgets_node);
+ n; n = glade_xml_node_next (n)) {
+ gchar *widget_name, *tmp;
+
+ if (!glade_xml_node_verify (n, GLADE_TAG_SWIPEGROUP_SWIPEABLE))
+ continue;
+
+ widget_name = glade_xml_get_property_string_required
+ (n, GLADE_TAG_NAME, NULL);
+
+ if (string == NULL) {
+ string = widget_name;
+ } else if (widget_name != NULL) {
+ tmp =
+ g_strdup_printf ("%s%s%s", string, GLADE_PROPERTY_DEF_OBJECT_DELIMITER,
+ widget_name);
+ string = (g_free (string), tmp);
+ g_free (widget_name);
+ }
+ }
+ }
+
+ if (string) {
+ property = glade_widget_get_property (widget, PROP_SWIPEABLES);
+ g_assert (property);
+
+ /* we must synchronize this directly after loading this project
+ * (i.e. lookup the actual objects after they've been parsed and
+ * are present).
+ */
+ g_object_set_data_full (G_OBJECT (property),
+ "glade-loaded-object", string, g_free);
+ }
+}
+
+void
+glade_hdy_swipe_group_read_widget (GladeWidgetAdaptor *adaptor,
+ GladeWidget *widget,
+ GladeXmlNode *node)
+{
+ if (!(glade_xml_node_verify_silent (node, GLADE_XML_TAG_WIDGET) ||
+ glade_xml_node_verify_silent (node, GLADE_XML_TAG_TEMPLATE)))
+ return;
+
+ /* First chain up and read in all the normal properties.. */
+ GWA_GET_CLASS (G_TYPE_OBJECT)->read_widget (adaptor, widget, node);
+
+ glade_hdy_swipe_group_read_widgets (widget, node);
+}
+
+static void
+glade_hdy_swipe_group_write_widgets (GladeWidget *widget,
+ GladeXmlContext *context,
+ GladeXmlNode *node)
+{
+ GladeXmlNode *widgets_node, *widget_node;
+ GList *widgets = NULL, *list;
+ GladeWidget *awidget;
+
+ widgets_node = glade_xml_node_new (context, GLADE_TAG_SWIPEGROUP_SWIPEABLES);
+
+ if (glade_widget_property_get (widget, PROP_SWIPEABLES, &widgets)) {
+ for (list = widgets; list; list = list->next) {
+ awidget = glade_widget_get_from_gobject (list->data);
+ widget_node =
+ glade_xml_node_new (context, GLADE_TAG_SWIPEGROUP_SWIPEABLE);
+ glade_xml_node_append_child (widgets_node, widget_node);
+ glade_xml_node_set_property_string (widget_node, GLADE_TAG_NAME,
+ glade_widget_get_name (awidget));
+ }
+ }
+
+ if (!glade_xml_node_get_children (widgets_node))
+ glade_xml_node_delete (widgets_node);
+ else
+ glade_xml_node_append_child (node, widgets_node);
+}
+
+void
+glade_hdy_swipe_group_write_widget (GladeWidgetAdaptor *adaptor,
+ GladeWidget *widget,
+ GladeXmlContext *context,
+ GladeXmlNode *node)
+{
+ if (!(glade_xml_node_verify_silent (node, GLADE_XML_TAG_WIDGET) ||
+ glade_xml_node_verify_silent (node, GLADE_XML_TAG_TEMPLATE)))
+ return;
+
+ /* First chain up and read in all the normal properties.. */
+ GWA_GET_CLASS (G_TYPE_OBJECT)->write_widget (adaptor, widget, context, node);
+
+ glade_hdy_swipe_group_write_widgets (widget, context, node);
+}
+
+void
+glade_hdy_swipe_group_set_property (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *property_name,
+ const GValue *value)
+{
+ if (!strcmp (property_name, PROP_SWIPEABLES)) {
+ GSList *sg_widgets, *slist;
+ GList *widgets, *list;
+
+ /* remove old widgets */
+ if ((sg_widgets =
+ hdy_swipe_group_get_swipeables (HDY_SWIPE_GROUP (object))) != NULL) {
+ /* copy since we are modifying an internal list */
+ sg_widgets = g_slist_copy (sg_widgets);
+ for (slist = sg_widgets; slist; slist = slist->next)
+ hdy_swipe_group_remove_swipeable (HDY_SWIPE_GROUP (object),
+ HDY_SWIPEABLE (slist->data));
+ g_slist_free (sg_widgets);
+ }
+
+ /* add new widgets */
+ if ((widgets = g_value_get_boxed (value)) != NULL) {
+ for (list = widgets; list; list = list->next)
+ hdy_swipe_group_add_swipeable (HDY_SWIPE_GROUP (object),
+ HDY_SWIPEABLE (list->data));
+ }
+ } else {
+ GWA_GET_CLASS (G_TYPE_OBJECT)->set_property (adaptor, object,
+ property_name, value);
+ }
+}
diff --git a/subprojects/libhandy/glade/glade-hdy-swipe-group.h b/subprojects/libhandy/glade/glade-hdy-swipe-group.h
new file mode 100644
index 0000000..5f593e5
--- /dev/null
+++ b/subprojects/libhandy/glade/glade-hdy-swipe-group.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2019 Alexander Mikhaylenko <exalm7659@gmail.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Based on
+ * hdy-header-group.h - GladeWidgetAdaptor for HdyHeaderGroup
+ * Copyright (C) 2018 Purism SPC
+ */
+
+#pragma once
+
+#include <gladeui/glade.h>
+
+#include <handy.h>
+
+
+void glade_hdy_swipe_group_set_property (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ const gchar *property_name,
+ const GValue *value);
+void glade_hdy_swipe_group_write_widget (GladeWidgetAdaptor *adaptor,
+ GladeWidget *widget,
+ GladeXmlContext *context,
+ GladeXmlNode *node);
+void glade_hdy_swipe_group_read_widget (GladeWidgetAdaptor *adaptor,
+ GladeWidget *widget,
+ GladeXmlNode *node);
diff --git a/subprojects/libhandy/glade/glade-hdy-utils.c b/subprojects/libhandy/glade/glade-hdy-utils.c
new file mode 100644
index 0000000..81b8ae5
--- /dev/null
+++ b/subprojects/libhandy/glade/glade-hdy-utils.c
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2020 Alexander Mikhaylenko <alexm@gnome.org>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#include "glade-hdy-utils.h"
+
+#include <gladeui/glade.h>
+
+void
+glade_hdy_init (const gchar *name)
+{
+ g_assert (strcmp (name, "libhandy") == 0);
+
+ gtk_init (NULL, NULL);
+ hdy_init ();
+}
+
+/* This function has been copied and modified from:
+ * glade-gtk-list-box.c - GladeWidgetAdaptor for GtkListBox widget
+ *
+ * Copyright (C) 2013 Kalev Lember
+ *
+ * Authors:
+ * Kalev Lember <kalevlember@gmail.com>
+ */
+void
+glade_hdy_sync_child_positions (GtkContainer *container)
+{
+ g_autoptr (GList) children = NULL;
+ GList *l;
+ gint position;
+ static gboolean recursion = FALSE;
+
+ /* Avoid feedback loop */
+ if (recursion)
+ return;
+
+ children = gtk_container_get_children (container);
+
+ position = 0;
+ for (l = children; l; l = l->next) {
+ gint old_position;
+
+ glade_widget_pack_property_get (glade_widget_get_from_gobject (l->data),
+ "position", &old_position);
+ if (position != old_position) {
+ /* Update glade with the new value */
+ recursion = TRUE;
+ glade_widget_pack_property_set (glade_widget_get_from_gobject (l->data),
+ "position", position);
+ recursion = FALSE;
+ }
+
+ position++;
+ }
+}
+
+gint
+glade_hdy_get_child_index (GtkContainer *container,
+ GtkWidget *child)
+{
+ g_autoptr (GList) children = gtk_container_get_children (container);
+
+ return g_list_index (children, child);
+}
+
+void
+glade_hdy_reorder_child (GtkContainer *container,
+ GtkWidget *child,
+ gint index)
+{
+ gint old_index = glade_hdy_get_child_index (container, child);
+ gint i = 0, n;
+ g_autoptr (GList) children = NULL;
+ g_autoptr (GList) removed_children = NULL;
+ GList *l;
+
+ if (old_index == index)
+ return;
+
+ gtk_container_remove (container, g_object_ref (child));
+
+ children = gtk_container_get_children (container);
+ n = g_list_length (children);
+
+ children = g_list_reverse (children);
+ l = children;
+
+ if (index > old_index)
+ n--;
+
+ for (i = n - 1; i >= index; i--) {
+ GtkWidget *last_child = l->data;
+
+ gtk_container_remove (container, g_object_ref (last_child));
+ l = l->next;
+
+ removed_children = g_list_prepend (removed_children, last_child);
+ }
+
+ removed_children = g_list_prepend (removed_children, child);
+
+ for (l = removed_children; l; l = l->next) {
+ gtk_container_add (container, l->data);
+ g_object_unref (l->data);
+ }
+}
+
+GtkWidget *
+glade_hdy_get_nth_child (GtkContainer *container,
+ gint n)
+{
+ g_autoptr (GList) children = gtk_container_get_children (container);
+
+ return g_list_nth_data (children, n);
+}
diff --git a/subprojects/libhandy/glade/glade-hdy-utils.h b/subprojects/libhandy/glade/glade-hdy-utils.h
new file mode 100644
index 0000000..11870d1
--- /dev/null
+++ b/subprojects/libhandy/glade/glade-hdy-utils.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 Alexander Mikhaylenko <alexm@gnome.org>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#pragma once
+
+#include <gladeui/glade.h>
+
+#include <handy.h>
+
+#define ONLY_THIS_GOES_IN_THAT_MSG _("Only objects of type %s can be added to objects of type %s.")
+
+/* Guess whether we are using a Glade version older than 3.36.
+ *
+ * If yes, redefine some symbols which got renamed.
+ */
+#ifndef GLADE_PROPERTY_DEF_OBJECT_DELIMITER
+#define GLADE_PROPERTY_DEF_OBJECT_DELIMITER GPC_OBJECT_DELIMITER
+#define glade_widget_action_get_def glade_widget_action_get_class
+#endif
+
+void glade_hdy_init (const gchar *name);
+
+void glade_hdy_sync_child_positions (GtkContainer *container);
+
+gint glade_hdy_get_child_index (GtkContainer *container,
+ GtkWidget *child);
+
+void glade_hdy_reorder_child (GtkContainer *container,
+ GtkWidget *child,
+ gint index);
+
+GtkWidget *glade_hdy_get_nth_child (GtkContainer *container,
+ gint n);
diff --git a/subprojects/libhandy/glade/glade-hdy-window.c b/subprojects/libhandy/glade/glade-hdy-window.c
new file mode 100644
index 0000000..7832f91
--- /dev/null
+++ b/subprojects/libhandy/glade/glade-hdy-window.c
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2020 Alexander Mikhaylenko <alexm@gnome.org>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Based on
+ * glade-gtk-searchbar.c - GladeWidgetAdaptor for GtkSearchBar
+ * Copyright (C) 2014 Red Hat, Inc.
+ */
+
+#include <config.h>
+#include <glib/gi18n-lib.h>
+
+#include "glade-hdy-window.h"
+
+#include <gladeui/glade.h>
+
+#define ALREADY_HAS_A_CHILD_MSG _("%s cannot have more than one child.")
+
+static GtkWidget *
+get_child (GtkContainer *window)
+{
+ g_autoptr (GList) children = gtk_container_get_children (window);
+
+ if (!children)
+ return NULL;
+
+ return children->data;
+}
+
+void
+glade_hdy_window_post_create (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ GladeCreateReason reason)
+{
+ if (reason != GLADE_CREATE_USER)
+ return;
+
+ gtk_container_add (GTK_CONTAINER (object), glade_placeholder_new ());
+}
+
+void
+glade_hdy_window_add_child (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ GObject *child)
+{
+ GtkWidget *window_child = get_child (GTK_CONTAINER (object));
+
+ if (window_child) {
+ if (GLADE_IS_PLACEHOLDER (window_child)) {
+ gtk_container_remove (GTK_CONTAINER (object), window_child);
+ } else {
+ g_critical ("Can't add more than one widget to a HdyWindow");
+
+ return;
+ }
+ }
+
+ gtk_container_add (GTK_CONTAINER (object), GTK_WIDGET (child));
+}
+
+void
+glade_hdy_window_remove_child (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ GObject *child)
+{
+ gtk_container_remove (GTK_CONTAINER (object), GTK_WIDGET (child));
+ gtk_container_add (GTK_CONTAINER (object), glade_placeholder_new ());
+}
+
+void
+glade_hdy_window_replace_child (GladeWidgetAdaptor *adaptor,
+ GtkWidget *object,
+ GtkWidget *current,
+ GtkWidget *new_widget)
+{
+ gtk_container_remove (GTK_CONTAINER (object), current);
+ gtk_container_add (GTK_CONTAINER (object), new_widget);
+}
+
+GList *
+glade_hdy_window_get_children (GladeWidgetAdaptor *adaptor,
+ GObject *object)
+{
+ return gtk_container_get_children (GTK_CONTAINER (object));
+}
+
+gboolean
+glade_hdy_window_add_verify (GladeWidgetAdaptor *adaptor,
+ GtkWidget *object,
+ GtkWidget *child,
+ gboolean user_feedback)
+{
+ GtkWidget *window_child = get_child (GTK_CONTAINER (object));
+
+ if (window_child && !GLADE_IS_PLACEHOLDER (window_child)) {
+ if (user_feedback)
+ glade_util_ui_message (glade_app_get_window (),
+ GLADE_UI_INFO, NULL,
+ ALREADY_HAS_A_CHILD_MSG,
+ glade_widget_adaptor_get_title (adaptor));
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
diff --git a/subprojects/libhandy/glade/glade-hdy-window.h b/subprojects/libhandy/glade/glade-hdy-window.h
new file mode 100644
index 0000000..75f15e4
--- /dev/null
+++ b/subprojects/libhandy/glade/glade-hdy-window.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 Alexander Mikhaylenko <alexm@gnome.org>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#pragma once
+
+#include <gladeui/glade.h>
+
+#include <handy.h>
+
+void glade_hdy_window_post_create (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ GladeCreateReason reason);
+
+void glade_hdy_window_add_child (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ GObject *child);
+void glade_hdy_window_remove_child (GladeWidgetAdaptor *adaptor,
+ GObject *object,
+ GObject *child);
+void glade_hdy_window_replace_child (GladeWidgetAdaptor *adaptor,
+ GtkWidget *object,
+ GtkWidget *current,
+ GtkWidget *new_widget);
+
+GList *glade_hdy_window_get_children (GladeWidgetAdaptor *adaptor,
+ GObject *object);
+
+gboolean glade_hdy_window_add_verify (GladeWidgetAdaptor *adaptor,
+ GtkWidget *object,
+ GtkWidget *child,
+ gboolean user_feedback);
diff --git a/subprojects/libhandy/glade/libhandy.xml b/subprojects/libhandy/glade/libhandy.xml
new file mode 100644
index 0000000..7f60118
--- /dev/null
+++ b/subprojects/libhandy/glade/libhandy.xml
@@ -0,0 +1,420 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<glade-catalog name="libhandy" library="glade-handy-1" depends="gtk+" book="libhandy">
+ <init-function>glade_hdy_init</init-function>
+ <glade-widget-classes>
+ <glade-widget-class name="HdyActionRow" generic-name="actionrow" title="Action Row" since="0.0.6">
+ <properties>
+ <property id="icon-name" themed-icon="True" />
+ <property id="title" translatable="True" />
+ <property id="subtitle" translatable="True" />
+ </properties>
+ </glade-widget-class>
+ <glade-widget-class name="HdyAvatar" generic-name="avatar" title="Avatar" since="1.0"/>
+ <glade-widget-class name="HdyApplicationWindow" generic-name="applicationwindow" title="Application Window" since="1.0" use-placeholders="False">
+ <post-create-function>glade_hdy_window_post_create</post-create-function>
+ <add-child-verify-function>glade_hdy_window_add_verify</add-child-verify-function>
+ <add-child-function>glade_hdy_window_add_child</add-child-function>
+ <remove-child-function>glade_hdy_window_remove_child</remove-child-function>
+ <replace-child-function>glade_hdy_window_replace_child</replace-child-function>
+ <get-children-function>glade_hdy_window_get_children</get-children-function>
+ <properties>
+ <property id="show-menubar" disabled="True" />
+ <property id="use-csd" disabled="True" />
+ </properties>
+ </glade-widget-class>
+ <glade-widget-class name="HdyCarousel" generic-name="carousel" title="Carousel" since="1.0">
+ <post-create-function>glade_hdy_carousel_post_create</post-create-function>
+ <add-child-function>glade_hdy_carousel_add_child</add-child-function>
+ <remove-child-function>glade_hdy_carousel_remove_child</remove-child-function>
+ <replace-child-function>glade_hdy_carousel_replace_child</replace-child-function>
+ <child-action-activate-function>glade_hdy_carousel_child_action_activate</child-action-activate-function>
+ <get-property-function>glade_hdy_carousel_get_property</get-property-function>
+ <set-property-function>glade_hdy_carousel_set_property</set-property-function>
+ <child-set-property-function>glade_hdy_carousel_set_child_property</child-set-property-function>
+ <child-get-property-function>glade_hdy_carousel_get_child_property</child-get-property-function>
+ <verify-function>glade_hdy_carousel_verify_property</verify-function>
+ <packing-properties>
+ <property id="position" name="Position" default="0" save="False">
+ <parameter-spec>
+ <type>GParamInt</type>
+ <min>0</min>
+ </parameter-spec>
+ <tooltip>The position of the page in the carousel</tooltip>
+ </property>
+ </packing-properties>
+ <packing-actions>
+ <action id="insert_page_before" name="Insert Page Before" stock="list-add"/>
+ <action id="insert_page_after" name="Insert Page After" stock="list-add"/>
+ <action id="remove_page" name="Remove Page" stock="list-remove"/>
+ </packing-actions>
+ <properties>
+ <property id="pages" name="Number of pages" save="False" default="1">
+ <parameter-spec>
+ <type>GParamInt</type>
+ <min>1</min>
+ </parameter-spec>
+ <tooltip>The number of pages in the stack</tooltip>
+ </property>
+ <property id="page" name="Edit page" save="False" default="0">
+ <parameter-spec>
+ <type>GParamInt</type>
+ <min>0</min>
+ </parameter-spec>
+ <tooltip>Set the currently active page to edit, this property will not be saved</tooltip>
+ </property>
+ <property id="above-child" disabled="True" />
+ <property id="visible-window" disabled="True" />
+ </properties>
+ </glade-widget-class>
+ <glade-widget-class name="HdyCarouselIndicatorDots" generic-name="carouselindicatordots" title="Carousel Indicator Dots" since="1.0"/>
+ <glade-widget-class name="HdyCarouselIndicatorLines" generic-name="carouselindicatorlines" title="Carousel Indicator Lines" since="1.0"/>
+ <glade-widget-class name="HdyClamp" generic-name="clamp" title="Clamp" since="1.0"/>
+ <glade-widget-class name="HdyComboRow" generic-name="comborow" title="Combo Row" since="0.0.6"/>
+ <glade-widget-class name="HdyDeck" generic-name="deck" title="Deck" since="1.0">
+ <post-create-function>glade_hdy_leaflet_post_create</post-create-function>
+ <add-child-function>glade_hdy_leaflet_add_child</add-child-function>
+ <remove-child-function>glade_hdy_leaflet_remove_child</remove-child-function>
+ <replace-child-function>glade_hdy_leaflet_replace_child</replace-child-function>
+ <child-action-activate-function>glade_hdy_leaflet_child_action_activate</child-action-activate-function>
+ <get-property-function>glade_hdy_leaflet_get_property</get-property-function>
+ <set-property-function>glade_hdy_leaflet_set_property</set-property-function>
+ <child-set-property-function>glade_hdy_leaflet_set_child_property</child-set-property-function>
+ <child-get-property-function>glade_hdy_leaflet_get_child_property</child-get-property-function>
+ <verify-function>glade_hdy_leaflet_verify_property</verify-function>
+ <packing-properties>
+ <property id="position" name="Position" default="0" save="False">
+ <parameter-spec>
+ <type>GParamInt</type>
+ <min>0</min>
+ </parameter-spec>
+ <tooltip>The position of the page in the deck</tooltip>
+ </property>
+ </packing-properties>
+ <packing-actions>
+ <action id="insert_page_before" name="Insert Page Before" stock="list-add"/>
+ <action id="insert_page_after" name="Insert Page After" stock="list-add"/>
+ <action id="remove_page" name="Remove Page" stock="list-remove"/>
+ </packing-actions>
+ <properties>
+ <property id="pages" name="Number of pages" save="False" default="1">
+ <parameter-spec>
+ <type>GParamInt</type>
+ <min>1</min>
+ </parameter-spec>
+ <tooltip>The number of pages in the deck</tooltip>
+ </property>
+ <property id="page" name="Edit page" save="False" default="0">
+ <parameter-spec>
+ <type>GParamInt</type>
+ <min>0</min>
+ </parameter-spec>
+ <tooltip>Set the currently active page to edit, this property will not be saved</tooltip>
+ </property>
+ <property id="transition-type">
+ <displayable-values>
+ <!-- HdyDeckTransitionType enumeration value -->
+ <value id="HDY_DECK_TRANSITION_TYPE_OVER" name="Over"/>
+ <!-- HdyDeckTransitionType enumeration value -->
+ <value id="HDY_DECK_TRANSITION_TYPE_UNDER" name="Under"/>
+ <!-- HdyDeckTransitionType enumeration value -->
+ <value id="HDY_DECK_TRANSITION_TYPE_SLIDE" name="Slide"/>
+ </displayable-values>
+ </property>
+ </properties>
+ </glade-widget-class>
+ <glade-widget-class name="HdyExpanderRow" generic-name="expanderrow" title="Expander Row" since="0.0.6" use-placeholders="False">
+ <post-create-function>glade_hdy_expander_row_post_create</post-create-function>
+ <add-child-verify-function>glade_hdy_expander_row_add_verify</add-child-verify-function>
+ <add-child-function>glade_hdy_expander_row_add_child</add-child-function>
+ <remove-child-function>glade_hdy_expander_row_remove_child</remove-child-function>
+ <child-set-property-function>glade_hdy_expander_row_set_child_property</child-set-property-function>
+ <child-get-property-function>glade_hdy_expander_row_get_child_property</child-get-property-function>
+ <packing-properties>
+ <property id="position" name="Position" default="0" save="False">
+ <parameter-spec>
+ <type>GParamInt</type>
+ <min>0</min>
+ </parameter-spec>
+ <tooltip>The position of the list row in the expander row</tooltip>
+ </property>
+ </packing-properties>
+ <properties>
+ <property id="expanded" save="True" ignore="True"/>
+ <property id="enable-expansion" save="True" ignore="True"/>
+ </properties>
+ </glade-widget-class>
+ <glade-widget-class name="HdyHeaderBar" generic-name="headerbar" title="Header Bar" since="0.0.10">
+ <post-create-function>glade_hdy_header_bar_post_create</post-create-function>
+ <add-child-function>glade_hdy_header_bar_add_child</add-child-function>
+ <remove-child-function>glade_hdy_header_bar_remove_child</remove-child-function>
+ <replace-child-function>glade_hdy_header_bar_replace_child</replace-child-function>
+ <get-children-function>glade_hdy_header_bar_get_children</get-children-function>
+ <child-action-activate-function>glade_hdy_header_bar_child_action_activate</child-action-activate-function>
+ <get-property-function>glade_hdy_header_bar_get_property</get-property-function>
+ <set-property-function>glade_hdy_header_bar_set_property</set-property-function>
+ <child-set-property-function>glade_hdy_header_bar_child_set_property</child-set-property-function>
+ <action-activate-function>glade_hdy_header_bar_action_activate</action-activate-function>
+ <verify-function>glade_hdy_header_bar_verify_property</verify-function>
+ <special-child-type>type</special-child-type>
+ <packing-properties>
+ <property id="pack-type" transfer-on-paste="True" />
+ </packing-properties>
+ <packing-actions>
+ <action id="remove_slot" name="Remove Slot" stock="gtk-remove"/>
+ </packing-actions>
+ <properties>
+ <property id="title" translatable="True"/>
+ <property id="subtitle" translatable="True"/>
+ <property id="has-subtitle" name="Reserve space for subtitle">
+ <tooltip>Keep the headerbar height the same as the subtitle changes dynamically.</tooltip>
+ </property>
+ <property id="show-close-button" name="Show window controls" needs-sync="True"/>
+ <property id="spacing"/>
+ <property id="decoration-layout"/>
+ <property id="decoration-layout-set" disabled="True"/>
+ <property id="custom-title" disabled="True"/>
+ <property id="use-custom-title" name="Custom Title" default="FALSE" visible="True" save="False">
+ <parameter-spec>
+ <type>GParamBoolean</type>
+ </parameter-spec>
+ </property>
+ <property visible="True" save="False" id="size" default="1" name="Number of items">
+ <parameter-spec>
+ <type>GParamInt</type>
+ <min>0</min>
+ </parameter-spec>
+ <tooltip>The number of items in the header bar</tooltip>
+ </property>
+ <property id="centering-policy">
+ <displayable-values>
+ <!-- HdyCenteringPolicy enumeration value -->
+ <value id="HDY_CENTERING_POLICY_LOOSE" name="Loose"/>
+ <!-- HdyCenteringPolicy enumeration value -->
+ <value id="HDY_CENTERING_POLICY_STRICT" name="Strict"/>
+ </displayable-values>
+ </property>
+ </properties>
+ <actions>
+ <action id="add_slot" name="Add Slot" stock="list-add"/>
+ </actions>
+ </glade-widget-class>
+ <glade-widget-class name="HdyHeaderGroup" generic-name="headergroup" title="Header Group" toplevel="True">
+ <read-widget-function>glade_hdy_header_group_read_widget</read-widget-function>
+ <write-widget-function>glade_hdy_header_group_write_widget</write-widget-function>
+ <set-property-function>glade_hdy_header_group_set_property</set-property-function>
+ <properties>
+ <property id="headerbars" name="Headerbars" save="False">
+ <parameter-spec>
+ <type>GladeParamObjects</type>
+ <value-type>HdyHeaderBar</value-type>
+ </parameter-spec>
+ <tooltip>List of headerbars in this group</tooltip>
+ </property>
+ </properties>
+ </glade-widget-class>
+ <glade-widget-class name="HdyKeypad" generic-name="keypad" title="Keypad"/>
+ <glade-widget-class name="HdyLeaflet" generic-name="leaflet" title="Leaflet">
+ <post-create-function>glade_hdy_leaflet_post_create</post-create-function>
+ <add-child-function>glade_hdy_leaflet_add_child</add-child-function>
+ <remove-child-function>glade_hdy_leaflet_remove_child</remove-child-function>
+ <replace-child-function>glade_hdy_leaflet_replace_child</replace-child-function>
+ <child-action-activate-function>glade_hdy_leaflet_child_action_activate</child-action-activate-function>
+ <get-property-function>glade_hdy_leaflet_get_property</get-property-function>
+ <set-property-function>glade_hdy_leaflet_set_property</set-property-function>
+ <child-set-property-function>glade_hdy_leaflet_set_child_property</child-set-property-function>
+ <child-get-property-function>glade_hdy_leaflet_get_child_property</child-get-property-function>
+ <verify-function>glade_hdy_leaflet_verify_property</verify-function>
+ <packing-properties>
+ <property id="position" name="Position" default="0" save="False">
+ <parameter-spec>
+ <type>GParamInt</type>
+ <min>0</min>
+ </parameter-spec>
+ <tooltip>The position of the page in the leaflet</tooltip>
+ </property>
+ </packing-properties>
+ <packing-actions>
+ <action id="insert_page_before" name="Insert Page Before" stock="list-add"/>
+ <action id="insert_page_after" name="Insert Page After" stock="list-add"/>
+ <action id="remove_page" name="Remove Page" stock="list-remove"/>
+ </packing-actions>
+ <properties>
+ <property id="pages" name="Number of pages" save="False" default="1">
+ <parameter-spec>
+ <type>GParamInt</type>
+ <min>1</min>
+ </parameter-spec>
+ <tooltip>The number of pages in the leaflet</tooltip>
+ </property>
+ <property id="page" name="Edit page" save="False" default="0">
+ <parameter-spec>
+ <type>GParamInt</type>
+ <min>0</min>
+ </parameter-spec>
+ <tooltip>Set the currently active page to edit, this property will not be saved</tooltip>
+ </property>
+ <property id="transition-type">
+ <displayable-values>
+ <!-- HdyLeafletTransitionType enumeration value -->
+ <value id="HDY_LEAFLET_TRANSITION_TYPE_OVER" name="Over"/>
+ <!-- HdyLeafletTransitionType enumeration value -->
+ <value id="HDY_LEAFLET_TRANSITION_TYPE_UNDER" name="Under"/>
+ <!-- HdyLeafletTransitionType enumeration value -->
+ <value id="HDY_LEAFLET_TRANSITION_TYPE_SLIDE" name="Slide"/>
+ </displayable-values>
+ </property>
+ </properties>
+ </glade-widget-class>
+ <glade-widget-class name="HdyPreferencesGroup" generic-name="preferencesgroup" title="Preferences Group" since="0.0.10" use-placeholders="False">
+ <properties>
+ <property id="title" translatable="True" />
+ <property id="description" translatable="True" />
+ </properties>
+ </glade-widget-class>
+ <glade-widget-class name="HdyPreferencesPage" generic-name="preferencespage" title="Preferences Page" since="0.0.10" use-placeholders="False">
+ <post-create-function>glade_hdy_preferences_page_post_create</post-create-function>
+ <add-child-verify-function>glade_hdy_preferences_page_add_verify</add-child-verify-function>
+ <add-child-function>glade_hdy_preferences_page_add_child</add-child-function>
+ <remove-child-function>glade_hdy_preferences_page_remove_child</remove-child-function>
+ <replace-child-function>glade_hdy_preferences_page_replace_child</replace-child-function>
+ <get-children-function>glade_hdy_preferences_page_get_children</get-children-function>
+ <child-set-property-function>glade_hdy_preferences_page_child_set_property</child-set-property-function>
+ <child-get-property-function>glade_hdy_preferences_page_child_get_property</child-get-property-function>
+ <action-activate-function>glade_hdy_preferences_page_action_activate</action-activate-function>
+ <packing-properties>
+ <property id="position" name="Position" default="-1" save="False">
+ <parameter-spec>
+ <type>GParamInt</type>
+ <min>-1</min>
+ </parameter-spec>
+ <tooltip>The position of the group in the preferences page</tooltip>
+ </property>
+ </packing-properties>
+ <properties>
+ <property id="icon-name" themed-icon="True" />
+ <property id="title" translatable="True" />
+ </properties>
+ <actions>
+ <action id="add_group" name="Add Group" stock="list-add" important="True"/>
+ </actions>
+ </glade-widget-class>
+ <glade-widget-class name="HdyPreferencesRow" generic-name="preferencesrow" title="Preferences Row" since="0.0.10"/>
+ <glade-widget-class name="HdyPreferencesWindow" generic-name="preferenceswindow" title="Preferences Window" since="0.0.10" use-placeholders="False">
+ <post-create-function>glade_hdy_preferences_window_post_create</post-create-function>
+ <add-child-verify-function>glade_hdy_preferences_window_add_verify</add-child-verify-function>
+ <add-child-function>glade_hdy_preferences_window_add_child</add-child-function>
+ <remove-child-function>glade_hdy_preferences_window_remove_child</remove-child-function>
+ <replace-child-function>glade_hdy_preferences_window_replace_child</replace-child-function>
+ <get-children-function>glade_hdy_preferences_window_get_children</get-children-function>
+ <child-set-property-function>glade_hdy_preferences_window_child_set_property</child-set-property-function>
+ <child-get-property-function>glade_hdy_preferences_window_child_get_property</child-get-property-function>
+ <action-activate-function>glade_hdy_preferences_window_action_activate</action-activate-function>
+ <packing-properties>
+ <property id="position" name="Position" default="-1" save="False">
+ <parameter-spec>
+ <type>GParamInt</type>
+ <min>-1</min>
+ </parameter-spec>
+ <tooltip>The position of the page in the preferences window</tooltip>
+ </property>
+ </packing-properties>
+ <properties>
+ <property id="use-csd" disabled="True" />
+ </properties>
+ <actions>
+ <action id="add_page" name="Add Page" stock="list-add" important="True"/>
+ </actions>
+ </glade-widget-class>
+ <glade-widget-class name="HdySearchBar" generic-name="searchbar" title="Search Bar" since="0.0.6">
+ <post-create-function>glade_hdy_search_bar_post_create</post-create-function>
+ <add-child-verify-function>glade_hdy_search_bar_add_verify</add-child-verify-function>
+ <add-child-function>glade_hdy_search_bar_add_child</add-child-function>
+ <remove-child-function>glade_hdy_search_bar_remove_child</remove-child-function>
+ <replace-child-function>glade_hdy_search_bar_replace_child</replace-child-function>
+ <get-children-function>glade_hdy_search_bar_get_children</get-children-function>
+ <properties>
+ <property id="search-mode-enabled" save="True" ignore="True"/>
+ <property id="show-close-button" save="True" ignore="True"/>
+ </properties>
+ </glade-widget-class>
+ <glade-widget-class name="HdySqueezer" generic-name="squeezer" title="Squeezer" since="0.0.10"/>
+ <glade-widget-class name="HdySwipeGroup" generic-name="swipegroup" title="Swipe Group" toplevel="True">
+ <read-widget-function>glade_hdy_swipe_group_read_widget</read-widget-function>
+ <write-widget-function>glade_hdy_swipe_group_write_widget</write-widget-function>
+ <set-property-function>glade_hdy_swipe_group_set_property</set-property-function>
+ <properties>
+ <property id="swipeables" name="Widgets" save="False">
+ <parameter-spec>
+ <type>GladeParamObjects</type>
+ <value-type>HdySwipeable</value-type>
+ </parameter-spec>
+ <tooltip>List of widgets in this group</tooltip>
+ </property>
+ </properties>
+ </glade-widget-class>
+ <glade-widget-class name="HdyTitleBar" generic-name="titlebar" title="Title Bar"/>
+ <glade-widget-class name="HdyViewSwitcher" generic-name="viewswitcher" title="View Switcher" since="0.0.10"/>
+ <glade-widget-class name="HdyViewSwitcherBar" generic-name="viewswitcherbar" title="View Switcher Bar" since="0.0.10"/>
+ <glade-widget-class name="HdyViewSwitcherTitle" generic-name="viewswitchertitle" title="View Switcher Title" since="1.0">
+ <properties>
+ <property id="policy">
+ <displayable-values>
+ <!-- HdyViewSwitcherPolicy enumeration value -->
+ <value id="HDY_VIEW_SWITCHER_POLICY_AUTO" name="Auto"/>
+ <!-- HdyViewSwitcherPolicy enumeration value -->
+ <value id="HDY_VIEW_SWITCHER_POLICY_NARROW" name="Narrow"/>
+ <!-- HdyViewSwitcherPolicy enumeration value -->
+ <value id="HDY_VIEW_SWITCHER_POLICY_WIDE" name="Wide"/>
+ </displayable-values>
+ </property>
+ </properties>
+ </glade-widget-class>
+ <glade-widget-class name="HdyWindow" generic-name="window" title="Window" since="1.0" use-placeholders="False">
+ <post-create-function>glade_hdy_window_post_create</post-create-function>
+ <add-child-verify-function>glade_hdy_window_add_verify</add-child-verify-function>
+ <add-child-function>glade_hdy_window_add_child</add-child-function>
+ <remove-child-function>glade_hdy_window_remove_child</remove-child-function>
+ <replace-child-function>glade_hdy_window_replace_child</replace-child-function>
+ <get-children-function>glade_hdy_window_get_children</get-children-function>
+ <properties>
+ <property id="use-csd" disabled="True" />
+ </properties>
+ </glade-widget-class>
+ <glade-widget-class name="HdyWindowHandle" generic-name="windowhandle" title="Window Handle" since="1.0">
+ <properties>
+ <property id="above-child" disabled="True" />
+ <property id="visible-window" disabled="True" />
+ </properties>
+ </glade-widget-class>
+ </glade-widget-classes>
+
+ <glade-widget-group name="handy" title="Libhandy Widgets">
+ <glade-widget-class-ref name="HdyActionRow"/>
+ <glade-widget-class-ref name="HdyApplicationWindow"/>
+ <glade-widget-class-ref name="HdyAvatar"/>
+ <glade-widget-class-ref name="HdyCarousel"/>
+ <glade-widget-class-ref name="HdyCarouselIndicatorDots"/>
+ <glade-widget-class-ref name="HdyCarouselIndicatorLines"/>
+ <glade-widget-class-ref name="HdyClamp"/>
+ <glade-widget-class-ref name="HdyComboRow"/>
+ <glade-widget-class-ref name="HdyDeck"/>
+ <glade-widget-class-ref name="HdyExpanderRow"/>
+ <glade-widget-class-ref name="HdyHeaderBar"/>
+ <glade-widget-class-ref name="HdyHeaderGroup"/>
+ <glade-widget-class-ref name="HdyKeypad"/>
+ <glade-widget-class-ref name="HdyLeaflet"/>
+ <glade-widget-class-ref name="HdyPreferencesGroup"/>
+ <glade-widget-class-ref name="HdyPreferencesPage"/>
+ <glade-widget-class-ref name="HdyPreferencesRow"/>
+ <glade-widget-class-ref name="HdyPreferencesWindow"/>
+ <glade-widget-class-ref name="HdySearchBar"/>
+ <glade-widget-class-ref name="HdySqueezer"/>
+ <glade-widget-class-ref name="HdySwipeGroup"/>
+ <glade-widget-class-ref name="HdyTitleBar"/>
+ <glade-widget-class-ref name="HdyViewSwitcher"/>
+ <glade-widget-class-ref name="HdyViewSwitcherBar"/>
+ <glade-widget-class-ref name="HdyViewSwitcherTitle"/>
+ <glade-widget-class-ref name="HdyWindow"/>
+ <glade-widget-class-ref name="HdyWindowHandle"/>
+ </glade-widget-group>
+</glade-catalog>
diff --git a/subprojects/libhandy/glade/meson.build b/subprojects/libhandy/glade/meson.build
new file mode 100644
index 0000000..cba9a36
--- /dev/null
+++ b/subprojects/libhandy/glade/meson.build
@@ -0,0 +1,59 @@
+if glade_catalog
+
+glade_xml = 'libhandy.xml'
+module_dir = gladeui_dep.get_pkgconfig_variable('moduledir',
+ define_variable: ['libdir', get_option('libdir')])
+dtd = meson.current_source_dir() / 'glade-catalog.dtd'
+glade_catalogdir = gladeui_dep.get_pkgconfig_variable('catalogdir',
+ define_variable: ['datadir', get_option('datadir')])
+
+libglade_hdy_sources = [
+ 'glade-hdy-carousel.c',
+ 'glade-hdy-expander-row.c',
+ 'glade-hdy-header-bar.c',
+ 'glade-hdy-header-group.c',
+ 'glade-hdy-leaflet.c',
+ 'glade-hdy-preferences-page.c',
+ 'glade-hdy-preferences-window.c',
+ 'glade-hdy-search-bar.c',
+ 'glade-hdy-swipe-group.c',
+ 'glade-hdy-window.c',
+ 'glade-hdy-utils.c',
+]
+
+libglade_hdy_deps = [
+ libhandy_dep,
+ gladeui_dep,
+]
+
+libglade_hdy_args = []
+# Our custom glade module
+libglade_hdy = shared_library(
+ 'glade-handy-' + apiversion,
+ libglade_hdy_sources,
+ c_args: libglade_hdy_args,
+ dependencies: libglade_hdy_deps,
+ include_directories: [ root_inc, src_inc ],
+ install: true,
+ install_dir: module_dir,
+)
+
+# Validate glade catalog
+xmllint = find_program('xmllint', required: true)
+if xmllint.found()
+ custom_target(
+ 'xmllint',
+ build_by_default: true,
+ input: glade_xml,
+ output: 'doesnotexist',
+ command: [xmllint, '--dtdvalid', dtd, '--noout', '@INPUT@'],
+ )
+endif
+
+# Install glade catalog
+install_data(
+ glade_xml,
+ rename: 'libhandy-@0@.xml'.format(apiversion),
+ install_dir: glade_catalogdir)
+
+endif
diff --git a/subprojects/libhandy/glade/rename-id.patch b/subprojects/libhandy/glade/rename-id.patch
new file mode 100644
index 0000000..0dc3ab1
--- /dev/null
+++ b/subprojects/libhandy/glade/rename-id.patch
@@ -0,0 +1,28 @@
+From f4711b392e26d36266a88df4371230418e24cfc9 Mon Sep 17 00:00:00 2001
+From: Adrien Plazas <kekun.plazas@laposte.net>
+Date: Mon, 11 May 2020 10:45:17 +0200
+Subject: [PATCH] Rename the ID to sm.puri.Handy.Glade
+
+Make the GtkApplication ID match the Flatpak ID, which is required for
+the app to start.
+
+---
+ src/main.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/main.c b/src/main.c
+index 8a81771f..7a505255 100644
+--- a/src/main.c
++++ b/src/main.c
+@@ -161,7 +161,7 @@ main (int argc, char *argv[])
+ return -1;
+ }
+
+- app = gtk_application_new ("org.gnome.Glade", G_APPLICATION_HANDLES_OPEN);
++ app = gtk_application_new ("sm.puri.Handy.Glade", G_APPLICATION_HANDLES_OPEN);
+
+ g_application_set_option_context_summary (G_APPLICATION (app),
+ N_("Create or edit user interface designs for GTK+ or GNOME applications."));
+--
+2.26.0
+
diff --git a/subprojects/libhandy/glade/sm.puri.Handy.Glade.json b/subprojects/libhandy/glade/sm.puri.Handy.Glade.json
new file mode 100644
index 0000000..ef73458
--- /dev/null
+++ b/subprojects/libhandy/glade/sm.puri.Handy.Glade.json
@@ -0,0 +1,83 @@
+{
+ "app-id" : "sm.puri.Handy.Glade",
+ "runtime" : "org.gnome.Platform",
+ "runtime-version" : "master",
+ "sdk" : "org.gnome.Sdk",
+ "command" : "glade",
+ "rename-desktop-file" : "org.gnome.Glade.desktop",
+ "rename-appdata-file" : "org.gnome.Glade.appdata.xml",
+ "rename-icon" : "org.gnome.Glade",
+ "copy-icon" : true,
+ "desktop-file-name-suffix" : " (Handy Nightly)",
+ "finish-args" : [
+ /* X11 + XShm access */
+ "--share=ipc", "--socket=fallback-x11",
+ /* Wayland access */
+ "--socket=wayland",
+ /* We want full fs access so we can read the files */
+ "--filesystem=host",
+ /* Support GL widgets */
+ "--device=dri"
+ ],
+ "cleanup" : ["/include", "/lib/pkgconfig",
+ "/share/pkgconfig", "/share/aclocal",
+ "/man", "/share/man", "/share/gtk-doc",
+ "/share/vala",
+ "*.la", "*.a"],
+ "modules" : [
+ {
+ "name" : "gnome-common",
+ "sources" : [
+ {
+ "type" : "git",
+ "url" : "https://gitlab.gnome.org/GNOME/gnome-common.git"
+ }
+ ]
+ },
+ {
+ "name" : "intltool",
+ "sources" : [
+ {
+ "type" : "git",
+ "url" : "https://gitlab.gnome.org/World/intltool.git"
+ }
+ ]
+ },
+ {
+ "name" : "glade",
+ "config-opts": [
+ "--disable-man-pages",
+ "--disable-introspection"
+ ],
+ "sources" : [
+ {
+ "type" : "git",
+ "url" : "https://gitlab.gnome.org/GNOME/glade.git",
+ "tag" : "GLADE_3_36_0"
+ },
+ {
+ "type" : "patch",
+ "path" : "rename-id.patch"
+ }
+ ]
+ },
+ {
+ "name" : "libhandy",
+ "buildsystem" : "meson",
+ "builddir" : true,
+ "config-opts" : [
+ "-Dexamples=false",
+ "-Dglade_catalog=enabled",
+ "-Dintrospection=disabled",
+ "-Dtests=false",
+ "-Dvapi=false"
+ ],
+ "sources" : [
+ {
+ "type" : "git",
+ "url" : "https://gitlab.gnome.org/GNOME/libhandy.git"
+ }
+ ]
+ }
+ ]
+}