[libpanel] action-muxer: add support for class actions and property actions



commit 4072afce867687c14a632a4904c9dcfb9cd5118e
Author: Christian Hergert <chergert redhat com>
Date:   Wed Jul 27 17:47:33 2022 -0700

    action-muxer: add support for class actions and property actions
    
    This gives us most of what we had from GtkWidgetClass in a convenient
    action muxer we can control. We don't get the part of action muxer that
    lets us observe, but this at least gets us something we can group/insert
    into GTK elsewhere.
    
    Beyond that, we can probably make this useful outside of just
    widgets so that we can have it in libide as well on IdeObject. Therefore
    I tried to avoid the GTK'isms on the public API itself, even if they
    are type compatible.

 src/gsettings-mapping.c          | 592 +++++++++++++++++++++++++++++++++++++++
 src/gsettings-mapping.h          |  34 +++
 src/meson.build                  |   1 +
 src/panel-action-muxer-private.h |  40 ++-
 src/panel-action-muxer.c         | 274 +++++++++++++++++-
 src/panel-frame.c                |   8 +-
 src/panel-widget-private.h       |   7 +-
 src/panel-widget.c               | 319 +++++++++++++++++++--
 src/panel-widget.h               | 111 ++++----
 9 files changed, 1300 insertions(+), 86 deletions(-)
---
diff --git a/src/gsettings-mapping.c b/src/gsettings-mapping.c
new file mode 100644
index 0000000..8e22e13
--- /dev/null
+++ b/src/gsettings-mapping.c
@@ -0,0 +1,592 @@
+/*
+ * Copyright © 2010 Novell, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Vincent Untz <vuntz gnome org>
+ */
+
+#include "config.h"
+
+#include "gsettings-mapping.h"
+
+static GVariant *
+g_settings_set_mapping_int (const GValue       *value,
+                            const GVariantType *expected_type)
+{
+  GVariant *variant = NULL;
+  gint64 l;
+
+  if (G_VALUE_HOLDS_INT (value))
+    l = g_value_get_int (value);
+  else if (G_VALUE_HOLDS_INT64 (value))
+    l = g_value_get_int64 (value);
+  else
+    return NULL;
+
+  if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_INT16))
+    {
+      if (G_MININT16 <= l && l <= G_MAXINT16)
+        variant = g_variant_new_int16 ((gint16) l);
+    }
+  else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_UINT16))
+    {
+      if (0 <= l && l <= G_MAXUINT16)
+        variant = g_variant_new_uint16 ((guint16) l);
+    }
+  else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_INT32))
+    {
+      if (G_MININT32 <= l && l <= G_MAXINT32)
+        variant = g_variant_new_int32 ((int) l);
+    }
+  else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_UINT32))
+    {
+      if (0 <= l && l <= G_MAXUINT32)
+        variant = g_variant_new_uint32 ((guint) l);
+    }
+  else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_INT64))
+    {
+      if (G_MININT64 <= l && l <= G_MAXINT64)
+        variant = g_variant_new_int64 ((gint64) l);
+    }
+  else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_UINT64))
+    {
+      if (0 <= l && l <= G_MAXUINT64)
+        variant = g_variant_new_uint64 ((guint64) l);
+    }
+  else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_HANDLE))
+    {
+      if (0 <= l && l <= G_MAXUINT32)
+        variant = g_variant_new_handle ((guint) l);
+    }
+  else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_DOUBLE))
+    variant = g_variant_new_double ((double) l);
+
+  return variant;
+}
+
+static GVariant *
+g_settings_set_mapping_float (const GValue       *value,
+                              const GVariantType *expected_type)
+{
+  GVariant *variant = NULL;
+  double d;
+  gint64 l;
+
+  if (G_VALUE_HOLDS_DOUBLE (value))
+    d = g_value_get_double (value);
+  else
+    return NULL;
+
+  l = (gint64) d;
+  if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_INT16))
+    {
+      if (G_MININT16 <= l && l <= G_MAXINT16)
+        variant = g_variant_new_int16 ((gint16) l);
+    }
+  else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_UINT16))
+    {
+      if (0 <= l && l <= G_MAXUINT16)
+        variant = g_variant_new_uint16 ((guint16) l);
+    }
+  else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_INT32))
+    {
+      if (G_MININT32 <= l && l <= G_MAXINT32)
+        variant = g_variant_new_int32 ((int) l);
+    }
+  else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_UINT32))
+    {
+      if (0 <= l && l <= G_MAXUINT32)
+        variant = g_variant_new_uint32 ((guint) l);
+    }
+  else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_INT64))
+    {
+      if (G_MININT64 <= l && l <= G_MAXINT64)
+        variant = g_variant_new_int64 ((gint64) l);
+    }
+  else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_UINT64))
+    {
+      if (0 <= l && l <= G_MAXUINT64)
+        variant = g_variant_new_uint64 ((guint64) l);
+    }
+  else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_HANDLE))
+    {
+      if (0 <= l && l <= G_MAXUINT32)
+        variant = g_variant_new_handle ((guint) l);
+    }
+  else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_DOUBLE))
+    variant = g_variant_new_double ((double) d);
+
+  return variant;
+}
+static GVariant *
+g_settings_set_mapping_unsigned_int (const GValue       *value,
+                                     const GVariantType *expected_type)
+{
+  GVariant *variant = NULL;
+  guint64 u;
+
+  if (G_VALUE_HOLDS_UINT (value))
+    u = g_value_get_uint (value);
+  else if (G_VALUE_HOLDS_UINT64 (value))
+    u = g_value_get_uint64 (value);
+  else
+    return NULL;
+
+  if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_INT16))
+    {
+      if (u <= G_MAXINT16)
+        variant = g_variant_new_int16 ((gint16) u);
+    }
+  else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_UINT16))
+    {
+      if (u <= G_MAXUINT16)
+        variant = g_variant_new_uint16 ((guint16) u);
+    }
+  else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_INT32))
+    {
+      if (u <= G_MAXINT32)
+        variant = g_variant_new_int32 ((int) u);
+    }
+  else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_UINT32))
+    {
+      if (u <= G_MAXUINT32)
+        variant = g_variant_new_uint32 ((guint) u);
+    }
+  else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_INT64))
+    {
+      if (u <= G_MAXINT64)
+        variant = g_variant_new_int64 ((gint64) u);
+    }
+  else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_UINT64))
+    {
+      if (u <= G_MAXUINT64)
+        variant = g_variant_new_uint64 ((guint64) u);
+    }
+  else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_HANDLE))
+    {
+      if (u <= G_MAXUINT32)
+        variant = g_variant_new_handle ((guint) u);
+    }
+  else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_DOUBLE))
+    variant = g_variant_new_double ((double) u);
+
+  return variant;
+}
+
+static gboolean
+g_settings_get_mapping_int (GValue   *value,
+                            GVariant *variant)
+{
+  const GVariantType *type;
+  gint64 l;
+
+  type = g_variant_get_type (variant);
+
+  if (g_variant_type_equal (type, G_VARIANT_TYPE_INT16))
+    l = g_variant_get_int16 (variant);
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT32))
+    l = g_variant_get_int32 (variant);
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_INT64))
+    l = g_variant_get_int64 (variant);
+  else
+    return FALSE;
+
+  if (G_VALUE_HOLDS_INT (value))
+    {
+      g_value_set_int (value, l);
+      return (G_MININT32 <= l && l <= G_MAXINT32);
+    }
+  else if (G_VALUE_HOLDS_UINT (value))
+    {
+      g_value_set_uint (value, l);
+      return (0 <= l && l <= G_MAXUINT32);
+    }
+  else if (G_VALUE_HOLDS_INT64 (value))
+    {
+      g_value_set_int64 (value, l);
+      return (G_MININT64 <= l && l <= G_MAXINT64);
+    }
+  else if (G_VALUE_HOLDS_UINT64 (value))
+    {
+      g_value_set_uint64 (value, l);
+      return (0 <= l && l <= G_MAXUINT64);
+    }
+  else if (G_VALUE_HOLDS_DOUBLE (value))
+    {
+      g_value_set_double (value, l);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+g_settings_get_mapping_float (GValue   *value,
+                              GVariant *variant)
+{
+  const GVariantType *type;
+  double d;
+  gint64 l;
+
+  type = g_variant_get_type (variant);
+
+  if (g_variant_type_equal (type, G_VARIANT_TYPE_DOUBLE))
+    d = g_variant_get_double (variant);
+  else
+    return FALSE;
+
+  l = (gint64)d;
+  if (G_VALUE_HOLDS_INT (value))
+    {
+      g_value_set_int (value, l);
+      return (G_MININT32 <= l && l <= G_MAXINT32);
+    }
+  else if (G_VALUE_HOLDS_UINT (value))
+    {
+      g_value_set_uint (value, l);
+      return (0 <= l && l <= G_MAXUINT32);
+    }
+  else if (G_VALUE_HOLDS_INT64 (value))
+    {
+      g_value_set_int64 (value, l);
+      return (G_MININT64 <= l && l <= G_MAXINT64);
+    }
+  else if (G_VALUE_HOLDS_UINT64 (value))
+    {
+      g_value_set_uint64 (value, l);
+      return (0 <= l && l <= G_MAXUINT64);
+    }
+  else if (G_VALUE_HOLDS_DOUBLE (value))
+    {
+      g_value_set_double (value, d);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+static gboolean
+g_settings_get_mapping_unsigned_int (GValue   *value,
+                                     GVariant *variant)
+{
+  const GVariantType *type;
+  guint64 u;
+
+  type = g_variant_get_type (variant);
+
+  if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT16))
+    u = g_variant_get_uint16 (variant);
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT32))
+    u = g_variant_get_uint32 (variant);
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_UINT64))
+    u = g_variant_get_uint64 (variant);
+  else if (g_variant_type_equal (type, G_VARIANT_TYPE_HANDLE))
+    u = g_variant_get_handle (variant);
+  else
+    return FALSE;
+
+  if (G_VALUE_HOLDS_INT (value))
+    {
+      g_value_set_int (value, u);
+      return (u <= G_MAXINT32);
+    }
+  else if (G_VALUE_HOLDS_UINT (value))
+    {
+      g_value_set_uint (value, u);
+      return (u <= G_MAXUINT32);
+    }
+  else if (G_VALUE_HOLDS_INT64 (value))
+    {
+      g_value_set_int64 (value, u);
+      return (u <= G_MAXINT64);
+    }
+  else if (G_VALUE_HOLDS_UINT64 (value))
+    {
+      g_value_set_uint64 (value, u);
+      return (u <= G_MAXUINT64);
+    }
+  else if (G_VALUE_HOLDS_DOUBLE (value))
+    {
+      g_value_set_double (value, u);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+GVariant *
+g_settings_set_mapping (const GValue       *value,
+                        const GVariantType *expected_type,
+                        gpointer            user_data)
+{
+  char *type_string;
+
+  if (G_VALUE_HOLDS_BOOLEAN (value))
+    {
+      if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_BOOLEAN))
+        return g_variant_new_boolean (g_value_get_boolean (value));
+    }
+
+  else if (G_VALUE_HOLDS_CHAR (value)  ||
+           G_VALUE_HOLDS_UCHAR (value))
+    {
+      if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_BYTE))
+        {
+          if (G_VALUE_HOLDS_CHAR (value))
+            return g_variant_new_byte (g_value_get_schar (value));
+          else
+            return g_variant_new_byte (g_value_get_uchar (value));
+        }
+    }
+
+  else if (G_VALUE_HOLDS_INT (value)   ||
+           G_VALUE_HOLDS_INT64 (value))
+    return g_settings_set_mapping_int (value, expected_type);
+
+  else if (G_VALUE_HOLDS_DOUBLE (value))
+    return g_settings_set_mapping_float (value, expected_type);
+
+  else if (G_VALUE_HOLDS_UINT (value)  ||
+           G_VALUE_HOLDS_UINT64 (value))
+    return g_settings_set_mapping_unsigned_int (value, expected_type);
+
+  else if (G_VALUE_HOLDS_STRING (value))
+    {
+      if (g_value_get_string (value) == NULL)
+        return NULL;
+      else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_STRING))
+        return g_variant_new_string (g_value_get_string (value));
+      else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_BYTESTRING))
+        return g_variant_new_bytestring (g_value_get_string (value));
+      else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_OBJECT_PATH))
+        return g_variant_new_object_path (g_value_get_string (value));
+      else if (g_variant_type_equal (expected_type, G_VARIANT_TYPE_SIGNATURE))
+        return g_variant_new_signature (g_value_get_string (value));
+    }
+
+  else if (G_VALUE_HOLDS (value, G_TYPE_STRV))
+    {
+      if (g_value_get_boxed (value) == NULL)
+        return NULL;
+      return g_variant_new_strv ((const char **) g_value_get_boxed (value),
+                                 -1);
+    }
+
+  else if (G_VALUE_HOLDS_ENUM (value))
+    {
+      GEnumValue *enumval;
+      GEnumClass *eclass;
+
+      /* GParamSpecEnum holds a ref on the class so we just peek... */
+      eclass = g_type_class_peek (G_VALUE_TYPE (value));
+      enumval = g_enum_get_value (eclass, g_value_get_enum (value));
+
+      if (enumval)
+        return g_variant_new_string (enumval->value_nick);
+      else
+        return NULL;
+    }
+
+  else if (G_VALUE_HOLDS_FLAGS (value))
+    {
+      GVariantBuilder builder;
+      GFlagsValue *flagsval;
+      GFlagsClass *fclass;
+      guint flags;
+
+      fclass = g_type_class_peek (G_VALUE_TYPE (value));
+      flags = g_value_get_flags (value);
+
+      g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
+      while (flags)
+        {
+          flagsval = g_flags_get_first_value (fclass, flags);
+
+          if (flagsval == NULL)
+            {
+              g_variant_builder_clear (&builder);
+              return NULL;
+            }
+
+          g_variant_builder_add (&builder, "s", flagsval->value_nick);
+          flags &= ~flagsval->value;
+        }
+
+      return g_variant_builder_end (&builder);
+    }
+
+  type_string = g_variant_type_dup_string (expected_type);
+  g_critical ("No GSettings bind handler for type \"%s\".", type_string);
+  g_free (type_string);
+
+  return NULL;
+}
+
+gboolean
+g_settings_get_mapping (GValue   *value,
+                        GVariant *variant,
+                        gpointer  user_data)
+{
+  if (g_variant_is_of_type (variant, G_VARIANT_TYPE_BOOLEAN))
+    {
+      if (!G_VALUE_HOLDS_BOOLEAN (value))
+        return FALSE;
+      g_value_set_boolean (value, g_variant_get_boolean (variant));
+      return TRUE;
+    }
+
+  else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_BYTE))
+    {
+      if (G_VALUE_HOLDS_UCHAR (value))
+        g_value_set_uchar (value, g_variant_get_byte (variant));
+      else if (G_VALUE_HOLDS_CHAR (value))
+        g_value_set_schar (value, (gint8)g_variant_get_byte (variant));
+      else
+        return FALSE;
+      return TRUE;
+    }
+
+  else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_INT16)  ||
+           g_variant_is_of_type (variant, G_VARIANT_TYPE_INT32)  ||
+           g_variant_is_of_type (variant, G_VARIANT_TYPE_INT64))
+    return g_settings_get_mapping_int (value, variant);
+
+  else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_DOUBLE))
+    return g_settings_get_mapping_float (value, variant);
+
+  else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_UINT16) ||
+           g_variant_is_of_type (variant, G_VARIANT_TYPE_UINT32) ||
+           g_variant_is_of_type (variant, G_VARIANT_TYPE_UINT64) ||
+           g_variant_is_of_type (variant, G_VARIANT_TYPE_HANDLE))
+    return g_settings_get_mapping_unsigned_int (value, variant);
+
+  else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING)      ||
+           g_variant_is_of_type (variant, G_VARIANT_TYPE_OBJECT_PATH) ||
+           g_variant_is_of_type (variant, G_VARIANT_TYPE_SIGNATURE))
+    {
+      if (G_VALUE_HOLDS_STRING (value))
+        {
+          g_value_set_string (value, g_variant_get_string (variant, NULL));
+          return TRUE;
+        }
+
+      else if (G_VALUE_HOLDS_ENUM (value))
+        {
+          GEnumClass *eclass;
+          GEnumValue *evalue;
+          const char *nick;
+
+          /* GParamSpecEnum holds a ref on the class so we just peek... */
+          eclass = g_type_class_peek (G_VALUE_TYPE (value));
+          nick = g_variant_get_string (variant, NULL);
+          evalue = g_enum_get_value_by_nick (eclass, nick);
+
+          if (evalue)
+            {
+             g_value_set_enum (value, evalue->value);
+             return TRUE;
+            }
+
+          g_warning ("Unable to look up enum nick ‘%s’ via GType", nick);
+          return FALSE;
+        }
+    }
+  else if (g_variant_is_of_type (variant, G_VARIANT_TYPE ("as")))
+    {
+      if (G_VALUE_HOLDS (value, G_TYPE_STRV))
+        {
+          g_value_take_boxed (value, g_variant_dup_strv (variant, NULL));
+          return TRUE;
+        }
+
+      else if (G_VALUE_HOLDS_FLAGS (value))
+        {
+          GFlagsClass *fclass;
+          GFlagsValue *fvalue;
+          const char *nick;
+          GVariantIter iter;
+          guint flags = 0;
+
+          fclass = g_type_class_peek (G_VALUE_TYPE (value));
+
+          g_variant_iter_init (&iter, variant);
+          while (g_variant_iter_next (&iter, "&s", &nick))
+            {
+              fvalue = g_flags_get_value_by_nick (fclass, nick);
+
+              if (fvalue)
+                flags |= fvalue->value;
+
+              else
+                {
+                  g_warning ("Unable to lookup flags nick '%s' via GType",
+                             nick);
+                  return FALSE;
+                }
+            }
+
+          g_value_set_flags (value, flags);
+          return TRUE;
+        }
+    }
+  else if (g_variant_is_of_type (variant, G_VARIANT_TYPE_BYTESTRING))
+    {
+      g_value_set_string (value, g_variant_get_bytestring (variant));
+      return TRUE;
+    }
+
+  g_critical ("No GSettings bind handler for type \"%s\".",
+              g_variant_get_type_string (variant));
+
+  return FALSE;
+}
+
+gboolean
+g_settings_mapping_is_compatible (GType               gvalue_type,
+                                  const GVariantType *variant_type)
+{
+  gboolean ok = FALSE;
+
+  if (gvalue_type == G_TYPE_BOOLEAN)
+    ok = g_variant_type_equal (variant_type, G_VARIANT_TYPE_BOOLEAN);
+  else if (gvalue_type == G_TYPE_CHAR  ||
+           gvalue_type == G_TYPE_UCHAR)
+    ok = g_variant_type_equal (variant_type, G_VARIANT_TYPE_BYTE);
+  else if (gvalue_type == G_TYPE_INT    ||
+           gvalue_type == G_TYPE_UINT   ||
+           gvalue_type == G_TYPE_INT64  ||
+           gvalue_type == G_TYPE_UINT64 ||
+           gvalue_type == G_TYPE_DOUBLE)
+    ok = (g_variant_type_equal (variant_type, G_VARIANT_TYPE_INT16)  ||
+          g_variant_type_equal (variant_type, G_VARIANT_TYPE_UINT16) ||
+          g_variant_type_equal (variant_type, G_VARIANT_TYPE_INT32)  ||
+          g_variant_type_equal (variant_type, G_VARIANT_TYPE_UINT32) ||
+          g_variant_type_equal (variant_type, G_VARIANT_TYPE_INT64)  ||
+          g_variant_type_equal (variant_type, G_VARIANT_TYPE_UINT64) ||
+          g_variant_type_equal (variant_type, G_VARIANT_TYPE_HANDLE) ||
+          g_variant_type_equal (variant_type, G_VARIANT_TYPE_DOUBLE));
+  else if (gvalue_type == G_TYPE_STRING)
+    ok = (g_variant_type_equal (variant_type, G_VARIANT_TYPE_STRING)      ||
+          g_variant_type_equal (variant_type, G_VARIANT_TYPE ("ay")) ||
+          g_variant_type_equal (variant_type, G_VARIANT_TYPE_OBJECT_PATH) ||
+          g_variant_type_equal (variant_type, G_VARIANT_TYPE_SIGNATURE));
+  else if (gvalue_type == G_TYPE_STRV)
+    ok = g_variant_type_equal (variant_type, G_VARIANT_TYPE ("as"));
+  else if (G_TYPE_IS_ENUM (gvalue_type))
+    ok = g_variant_type_equal (variant_type, G_VARIANT_TYPE_STRING);
+  else if (G_TYPE_IS_FLAGS (gvalue_type))
+    ok = g_variant_type_equal (variant_type, G_VARIANT_TYPE ("as"));
+
+  return ok;
+}
diff --git a/src/gsettings-mapping.h b/src/gsettings-mapping.h
new file mode 100644
index 0000000..8a26684
--- /dev/null
+++ b/src/gsettings-mapping.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright © 2010 Novell, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Vincent Untz <vuntz gnome org>
+ */
+
+#ifndef __G_SETTINGS_MAPPING_H__
+#define __G_SETTINGS_MAPPING_H__
+
+#include <glib-object.h>
+
+GVariant *              g_settings_set_mapping                          (const GValue       *value,
+                                                                         const GVariantType *expected_type,
+                                                                         gpointer            user_data);
+gboolean                g_settings_get_mapping                          (GValue             *value,
+                                                                         GVariant           *variant,
+                                                                         gpointer            user_data);
+gboolean                g_settings_mapping_is_compatible                (GType               gvalue_type,
+                                                                         const GVariantType *variant_type);
+
+#endif /* __G_SETTINGS_MAPPING_H__ */
diff --git a/src/meson.build b/src/meson.build
index e37fd38..b600160 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -18,6 +18,7 @@ panel_version_h = configure_file(
   configuration: version_data)
 
 libpanel_private_sources = [
+  'gsettings-mapping.c',
   'panel-action-muxer.c',
   'panel-binding-group.c',
   'panel-dock-child.c',
diff --git a/src/panel-action-muxer-private.h b/src/panel-action-muxer-private.h
index e694756..9dc4a65 100644
--- a/src/panel-action-muxer-private.h
+++ b/src/panel-action-muxer-private.h
@@ -24,19 +24,41 @@
 
 G_BEGIN_DECLS
 
+typedef void (*PanelActionActivateFunc) (gpointer    instance,
+                                         const char *action_name,
+                                         GVariant   *param);
+
+typedef struct _PanelAction
+{
+  const struct _PanelAction *next;
+  const char                *name;
+  GType                      owner;
+  const GVariantType        *parameter_type;
+  const GVariantType        *state_type;
+  GParamSpec                *pspec;
+  PanelActionActivateFunc    activate;
+  guint                      position;
+} PanelAction;
+
 #define PANEL_TYPE_ACTION_MUXER (panel_action_muxer_get_type())
 
 G_DECLARE_FINAL_TYPE (PanelActionMuxer, panel_action_muxer, PANEL, ACTION_MUXER, GObject)
 
 PanelActionMuxer  *panel_action_muxer_new                 (void);
-void               panel_action_muxer_insert_action_group (PanelActionMuxer *self,
-                                                           const char       *prefix,
-                                                           GActionGroup     *action_group);
-void               panel_action_muxer_remove_action_group (PanelActionMuxer *self,
-                                                           const char       *prefix);
-char             **panel_action_muxer_list_groups         (PanelActionMuxer *self);
-GActionGroup      *panel_action_muxer_get_action_group    (PanelActionMuxer *self,
-                                                           const char       *prefix);
-void               panel_action_muxer_clear               (PanelActionMuxer *self);
+void               panel_action_muxer_remove_all          (PanelActionMuxer  *self);
+void               panel_action_muxer_insert_action_group (PanelActionMuxer  *self,
+                                                           const char        *prefix,
+                                                           GActionGroup      *action_group);
+void               panel_action_muxer_remove_action_group (PanelActionMuxer  *self,
+                                                           const char        *prefix);
+char             **panel_action_muxer_list_groups         (PanelActionMuxer  *self);
+GActionGroup      *panel_action_muxer_get_action_group    (PanelActionMuxer  *self,
+                                                           const char        *prefix);
+void               panel_action_muxer_set_enabled         (PanelActionMuxer  *self,
+                                                           const PanelAction *action,
+                                                           gboolean           enabled);
+void               panel_action_muxer_connect_actions     (PanelActionMuxer  *self,
+                                                           gpointer           instance,
+                                                           const PanelAction *actions);
 
 G_END_DECLS
diff --git a/src/panel-action-muxer.c b/src/panel-action-muxer.c
index 382e61f..1ac0d98 100644
--- a/src/panel-action-muxer.c
+++ b/src/panel-action-muxer.c
@@ -20,13 +20,21 @@
 
 #include "config.h"
 
+#include <gtk/gtk.h>
+
+#include "gsettings-mapping.h"
 #include "panel-action-muxer-private.h"
 
 struct _PanelActionMuxer
 {
-  GObject    parent_instance;
-  GPtrArray *action_groups;
-  guint      n_recurse;
+  GObject            parent_instance;
+  GPtrArray         *action_groups;
+  const PanelAction *actions;
+  GtkBitset         *actions_disabled;
+  GHashTable        *pspec_name_to_action;
+  gpointer           instance;
+  gulong             instance_notify_handler;
+  guint              n_recurse;
 };
 
 typedef struct
@@ -74,14 +82,43 @@ prefixed_action_group_ref (PrefixedActionGroup *pag)
   return g_rc_box_acquire (pag);
 }
 
+static GVariant *
+get_property_state (gpointer            instance,
+                    GParamSpec         *pspec,
+                    const GVariantType *state_type)
+{
+  GValue value = G_VALUE_INIT;
+  GVariant *ret;
+
+  g_assert (G_IS_OBJECT (instance));
+  g_assert (pspec != NULL);
+  g_assert (state_type != NULL);
+
+  g_value_init (&value, pspec->value_type);
+  g_object_get_property (instance, pspec->name, &value);
+  ret = g_settings_set_mapping (&value, state_type, NULL);
+  g_value_unset (&value);
+
+  return g_variant_ref_sink (ret);
+}
+
 static void
 panel_action_muxer_dispose (GObject *object)
 {
   PanelActionMuxer *self = (PanelActionMuxer *)object;
 
+  if (self->instance != NULL)
+    {
+      g_clear_signal_handler (&self->instance_notify_handler, self->instance);
+      g_clear_weak_pointer (&self->instance);
+    }
+
   if (self->action_groups->len > 0)
     g_ptr_array_remove_range (self->action_groups, 0, self->action_groups->len);
 
+  self->actions = NULL;
+  g_clear_pointer (&self->actions_disabled, gtk_bitset_unref);
+
   G_OBJECT_CLASS (panel_action_muxer_parent_class)->finalize (object);
 }
 
@@ -108,6 +145,7 @@ static void
 panel_action_muxer_init (PanelActionMuxer *self)
 {
   self->action_groups = g_ptr_array_new_with_free_func ((GDestroyNotify)prefixed_action_group_drop);
+  self->actions_disabled = gtk_bitset_new_empty ();
 }
 
 PanelActionMuxer *
@@ -378,6 +416,12 @@ panel_action_muxer_has_action (GActionGroup *group,
 {
   PanelActionMuxer *self = PANEL_ACTION_MUXER (group);
 
+  for (const PanelAction *iter = self->actions; iter; iter = iter->next)
+    {
+      if (g_strcmp0 (iter->name, action_name) == 0)
+        return TRUE;
+    }
+
   for (guint i = 0; i < self->action_groups->len; i++)
     {
       const PrefixedActionGroup *pag = g_ptr_array_index (self->action_groups, i);
@@ -400,6 +444,12 @@ panel_action_muxer_list_actions (GActionGroup *group)
   PanelActionMuxer *self = PANEL_ACTION_MUXER (group);
   GArray *ar = g_array_new (TRUE, FALSE, sizeof (char *));
 
+  for (const PanelAction *iter = self->actions; iter; iter = iter->next)
+    {
+      char *name = g_strdup (iter->name);
+      g_array_append_val (ar, name);
+    }
+
   for (guint i = 0; i < self->action_groups->len; i++)
     {
       const PrefixedActionGroup *pag = g_ptr_array_index (self->action_groups, i);
@@ -421,6 +471,12 @@ panel_action_muxer_get_action_enabled (GActionGroup *group,
 {
   PanelActionMuxer *self = PANEL_ACTION_MUXER (group);
 
+  for (const PanelAction *iter = self->actions; iter; iter = iter->next)
+    {
+      if (g_strcmp0 (action_name, iter->name) == 0)
+        return !gtk_bitset_contains (self->actions_disabled, iter->position);
+    }
+
   for (guint i = 0; i < self->action_groups->len; i++)
     {
       const PrefixedActionGroup *pag = g_ptr_array_index (self->action_groups, i);
@@ -443,6 +499,16 @@ panel_action_muxer_get_action_state (GActionGroup *group,
 {
   PanelActionMuxer *self = PANEL_ACTION_MUXER (group);
 
+  for (const PanelAction *iter = self->actions; iter; iter = iter->next)
+    {
+      if (g_strcmp0 (iter->name, action_name) == 0)
+        {
+          if (iter->pspec != NULL && self->instance != NULL)
+            return get_property_state (self->instance, iter->pspec, iter->state_type);
+          return NULL;
+        }
+    }
+
   for (guint i = 0; i < self->action_groups->len; i++)
     {
       const PrefixedActionGroup *pag = g_ptr_array_index (self->action_groups, i);
@@ -465,6 +531,38 @@ panel_action_muxer_get_action_state_hint (GActionGroup *group,
 {
   PanelActionMuxer *self = PANEL_ACTION_MUXER (group);
 
+  for (const PanelAction *iter = self->actions; iter; iter = iter->next)
+    {
+      if (g_strcmp0 (iter->name, action_name) == 0)
+        {
+          if (iter->pspec != NULL)
+            {
+              if (iter->pspec->value_type == G_TYPE_INT)
+                {
+                  GParamSpecInt *pspec = (GParamSpecInt *)iter->pspec;
+                  return g_variant_new ("(ii)", pspec->minimum, pspec->maximum);
+                }
+              else if (iter->pspec->value_type == G_TYPE_UINT)
+                {
+                  GParamSpecUInt *pspec = (GParamSpecUInt *)iter->pspec;
+                  return g_variant_new ("(uu)", pspec->minimum, pspec->maximum);
+                }
+              else if (iter->pspec->value_type == G_TYPE_FLOAT)
+                {
+                  GParamSpecFloat *pspec = (GParamSpecFloat *)iter->pspec;
+                  return g_variant_new ("(dd)", (double)pspec->minimum, (double)pspec->maximum);
+                }
+              else if (iter->pspec->value_type == G_TYPE_DOUBLE)
+                {
+                  GParamSpecDouble *pspec = (GParamSpecDouble *)iter->pspec;
+                  return g_variant_new ("(dd)", pspec->minimum, pspec->maximum);
+                }
+            }
+
+          return NULL;
+        }
+    }
+
   for (guint i = 0; i < self->action_groups->len; i++)
     {
       const PrefixedActionGroup *pag = g_ptr_array_index (self->action_groups, i);
@@ -488,6 +586,23 @@ panel_action_muxer_change_action_state (GActionGroup *group,
 {
   PanelActionMuxer *self = PANEL_ACTION_MUXER (group);
 
+  for (const PanelAction *iter = self->actions; iter; iter = iter->next)
+    {
+      if (g_strcmp0 (iter->name, action_name) == 0)
+        {
+          if (iter->pspec != NULL && self->instance != NULL)
+            {
+              GValue gvalue = G_VALUE_INIT;
+              g_value_init (&gvalue, iter->pspec->value_type);
+              g_settings_get_mapping (&gvalue, value, NULL);
+              g_object_set_property (self->instance, iter->pspec->name, &gvalue);
+              g_value_unset (&gvalue);
+            }
+
+          return;
+        }
+    }
+
   for (guint i = 0; i < self->action_groups->len; i++)
     {
       const PrefixedActionGroup *pag = g_ptr_array_index (self->action_groups, i);
@@ -511,6 +626,12 @@ panel_action_muxer_get_action_state_type (GActionGroup *group,
 {
   PanelActionMuxer *self = PANEL_ACTION_MUXER (group);
 
+  for (const PanelAction *iter = self->actions; iter; iter = iter->next)
+    {
+      if (g_strcmp0 (iter->name, action_name) == 0)
+        return iter->state_type;
+    }
+
   for (guint i = 0; i < self->action_groups->len; i++)
     {
       const PrefixedActionGroup *pag = g_ptr_array_index (self->action_groups, i);
@@ -534,6 +655,39 @@ panel_action_muxer_activate_action (GActionGroup *group,
 {
   PanelActionMuxer *self = PANEL_ACTION_MUXER (group);
 
+  for (const PanelAction *iter = self->actions; iter; iter = iter->next)
+    {
+      if (g_strcmp0 (iter->name, action_name) == 0)
+        {
+          if (iter->pspec != NULL)
+            {
+              if (iter->pspec->value_type == G_TYPE_BOOLEAN)
+                {
+                  gboolean value;
+
+                  g_return_if_fail (parameter == NULL);
+
+                  g_object_get (self->instance, iter->pspec->name, &value, NULL);
+                  value = !value;
+                  g_object_set (self->instance, iter->pspec->name, value, NULL);
+                }
+              else
+                {
+                  g_return_if_fail (parameter != NULL && g_variant_is_of_type (parameter, iter->state_type));
+
+                  panel_action_muxer_change_action_state (group, action_name, parameter);
+                }
+
+            }
+          else
+            {
+              iter->activate (self->instance, iter->name, parameter);
+            }
+
+          return;
+        }
+    }
+
   for (guint i = 0; i < self->action_groups->len; i++)
     {
       const PrefixedActionGroup *pag = g_ptr_array_index (self->action_groups, i);
@@ -588,7 +742,7 @@ action_group_iface_init (GActionGroupInterface *iface)
 }
 
 void
-panel_action_muxer_clear (PanelActionMuxer *self)
+panel_action_muxer_remove_all (PanelActionMuxer *self)
 {
   g_auto(GStrv) action_groups = NULL;
 
@@ -600,3 +754,115 @@ panel_action_muxer_clear (PanelActionMuxer *self)
         panel_action_muxer_remove_action_group (self, action_groups[i]);
     }
 }
+
+void
+panel_action_muxer_set_enabled (PanelActionMuxer  *self,
+                                const PanelAction *action,
+                                gboolean           enabled)
+{
+  gboolean disabled = !enabled;
+
+  g_return_if_fail (PANEL_IS_ACTION_MUXER (self));
+  g_return_if_fail (action != NULL);
+
+  if (disabled != gtk_bitset_contains (self->actions_disabled, action->position))
+    {
+      if (disabled)
+        gtk_bitset_add (self->actions_disabled, action->position);
+      else
+        gtk_bitset_remove (self->actions_disabled, action->position);
+
+      g_action_group_action_enabled_changed (G_ACTION_GROUP (self), action->name, !disabled);
+    }
+}
+
+static void
+panel_action_muxer_property_action_notify_cb (PanelActionMuxer *self,
+                                              GParamSpec       *pspec,
+                                              gpointer          instance)
+{
+  g_autoptr(GVariant) state = NULL;
+  const PanelAction *action;
+
+  g_assert (PANEL_IS_ACTION_MUXER (self));
+  g_assert (pspec != NULL);
+  g_assert (G_IS_OBJECT (instance));
+
+  if (!(action = g_hash_table_lookup (self->pspec_name_to_action, pspec->name)))
+    return;
+
+  state = get_property_state (instance, action->pspec, action->state_type);
+
+  g_action_group_action_state_changed (G_ACTION_GROUP (self), action->name, state);
+}
+
+static void
+panel_action_muxer_add_property_action (PanelActionMuxer  *self,
+                                        gpointer           instance,
+                                        const PanelAction *action)
+{
+  g_assert (PANEL_IS_ACTION_MUXER (self));
+  g_assert (G_IS_OBJECT (instance));
+  g_assert (action != NULL);
+  g_assert (action->pspec != NULL);
+  g_assert (g_type_is_a (G_OBJECT_TYPE (instance), action->owner));
+
+  if (self->pspec_name_to_action == NULL)
+    self->pspec_name_to_action = g_hash_table_new (NULL, NULL);
+
+  g_hash_table_insert (self->pspec_name_to_action,
+                       (gpointer)action->pspec->name,
+                       (gpointer)action);
+
+  if (self->instance_notify_handler == 0)
+    self->instance_notify_handler =
+      g_signal_connect_object (instance,
+                               "notify",
+                               G_CALLBACK (panel_action_muxer_property_action_notify_cb),
+                               self,
+                               G_CONNECT_SWAPPED);
+
+  g_action_group_action_added (G_ACTION_GROUP (self), action->name);
+}
+
+static void
+panel_action_muxer_add_action (PanelActionMuxer  *self,
+                               gpointer           instance,
+                               const PanelAction *action)
+{
+  g_assert (PANEL_IS_ACTION_MUXER (self));
+  g_assert (G_IS_OBJECT (instance));
+  g_assert (action != NULL);
+  g_assert (g_type_is_a (G_OBJECT_TYPE (instance), action->owner));
+
+  g_action_group_action_added (G_ACTION_GROUP (self), action->name);
+}
+
+void
+panel_action_muxer_connect_actions (PanelActionMuxer  *self,
+                                    gpointer           instance,
+                                    const PanelAction *actions)
+{
+  g_return_if_fail (PANEL_IS_ACTION_MUXER (self));
+  g_return_if_fail (G_IS_OBJECT (instance));
+  g_return_if_fail (self->instance == NULL);
+
+  if (actions == NULL)
+    return;
+
+  g_set_weak_pointer (&self->instance, instance);
+
+  self->actions = actions;
+
+  for (const PanelAction *iter = actions; iter; iter = iter->next)
+    {
+      g_assert (iter->next == NULL ||
+                iter->position == iter->next->position + 1);
+      g_assert (iter->pspec != NULL || iter->activate != NULL);
+
+      if (iter->pspec != NULL)
+        panel_action_muxer_add_property_action (self, instance, iter);
+      else
+        panel_action_muxer_add_action (self, instance, iter);
+    }
+}
diff --git a/src/panel-frame.c b/src/panel-frame.c
index dfc283d..6cec0b3 100644
--- a/src/panel-frame.c
+++ b/src/panel-frame.c
@@ -231,8 +231,8 @@ static void
 panel_frame_update_actions (PanelFrame *self)
 {
   PanelFramePrivate *priv = panel_frame_get_instance_private (self);
+  PanelActionMuxer *action_group = NULL;
   PanelWidget *visible_child;
-  GActionGroup *action_group = NULL;
   GtkWidget *grid;
 
   g_assert (PANEL_IS_FRAME (self));
@@ -241,8 +241,10 @@ panel_frame_update_actions (PanelFrame *self)
   visible_child = panel_frame_get_visible_child (self);
 
   if (visible_child != NULL)
-    action_group = _panel_widget_get_action_group (visible_child);
-  gtk_widget_insert_action_group (GTK_WIDGET (self), "page", action_group);
+    action_group = _panel_widget_get_action_muxer (visible_child);
+  gtk_widget_insert_action_group (GTK_WIDGET (self),
+                                  "page",
+                                  G_ACTION_GROUP (action_group));
 
   gtk_widget_action_set_enabled (GTK_WIDGET (self), "page.move-right", grid  && visible_child);
   gtk_widget_action_set_enabled (GTK_WIDGET (self), "page.move-left", grid && visible_child);
diff --git a/src/panel-widget-private.h b/src/panel-widget-private.h
index 4e58138..4541843 100644
--- a/src/panel-widget-private.h
+++ b/src/panel-widget-private.h
@@ -20,12 +20,13 @@
 
 #pragma once
 
+#include "panel-action-muxer-private.h"
 #include "panel-widget.h"
 
 G_BEGIN_DECLS
 
-gboolean      _panel_widget_can_save         (PanelWidget *self);
-void          _panel_widget_emit_presented   (PanelWidget *self);
-GActionGroup *_panel_widget_get_action_group (PanelWidget *self);
+gboolean          _panel_widget_can_save         (PanelWidget *self);
+void              _panel_widget_emit_presented   (PanelWidget *self);
+PanelActionMuxer *_panel_widget_get_action_muxer (PanelWidget *self);
 
 G_END_DECLS
diff --git a/src/panel-widget.c b/src/panel-widget.c
index 2078ae3..b75618a 100644
--- a/src/panel-widget.c
+++ b/src/panel-widget.c
@@ -18,6 +18,30 @@
  * SPDX-License-Identifier: LGPL-3.0-or-later
  */
 
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
+ * file for a list of people on the GTK+ Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
 #include "config.h"
 
 #include "panel-action-muxer-private.h"
@@ -49,11 +73,15 @@ typedef struct
   guint             needs_attention : 1;
 } PanelWidgetPrivate;
 
-static void buildable_iface_init (GtkBuildableIface *iface);
+typedef struct
+{
+  const PanelAction *actions;
+} PanelWidgetClassPrivate;
 
-G_DEFINE_TYPE_WITH_CODE (PanelWidget, panel_widget, GTK_TYPE_WIDGET,
-                         G_ADD_PRIVATE  (PanelWidget)
-                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, buildable_iface_init))
+static void panel_widget_class_init_buildable (GtkBuildableIface *iface);
+static void panel_widget_class_init           (PanelWidgetClass  *klass);
+static void panel_widget_init                 (GTypeInstance     *instance,
+                                               gpointer           g_class);
 
 enum {
   PROP_0,
@@ -80,7 +108,65 @@ enum {
 };
 
 static GParamSpec *properties [N_PROPS];
-static guint signals [N_SIGNALS];
+static guint       signals [N_SIGNALS];
+static int         PanelWidget_private_offset;
+static gpointer    panel_widget_parent_class;
+
+static inline gpointer
+panel_widget_get_instance_private (PanelWidget *self)
+{
+  return (G_STRUCT_MEMBER_P (self, PanelWidget_private_offset));
+}
+
+static inline gpointer
+panel_widget_class_get_private (PanelWidgetClass *widget_class)
+{
+  return G_TYPE_CLASS_GET_PRIVATE (widget_class, PANEL_TYPE_WIDGET, PanelWidgetClassPrivate);
+}
+
+GType
+panel_widget_get_type (void)
+{
+  static GType widget_type = 0;
+
+  if G_UNLIKELY (widget_type == 0)
+    {
+      const GTypeInfo widget_info =
+      {
+        sizeof (PanelWidgetClass),
+        NULL,
+        NULL,
+        (GClassInitFunc)panel_widget_class_init,
+        NULL,
+        NULL,
+        sizeof (PanelWidget),
+        0,
+        panel_widget_init,
+        NULL,
+      };
+
+      const GInterfaceInfo buildable_info =
+      {
+        (GInterfaceInitFunc)panel_widget_class_init_buildable,
+        (GInterfaceFinalizeFunc)NULL,
+        NULL /* interface data */
+      };
+
+      widget_type = g_type_register_static (GTK_TYPE_WIDGET,
+                                            g_intern_static_string ("PanelWidget"),
+                                            &widget_info,
+                                            0);
+      g_type_add_class_private (widget_type,
+                                sizeof (PanelWidgetClassPrivate));
+      PanelWidget_private_offset = g_type_add_instance_private (widget_type,
+                                                                sizeof (PanelWidgetPrivate));
+      g_type_add_interface_static (widget_type,
+                                   GTK_TYPE_BUILDABLE,
+                                   &buildable_info);
+    }
+
+  return widget_type;
+}
 
 static void
 panel_widget_update_actions (PanelWidget *self)
@@ -89,9 +175,9 @@ panel_widget_update_actions (PanelWidget *self)
 
   g_assert (PANEL_IS_WIDGET (self));
 
-  gtk_widget_action_set_enabled (GTK_WIDGET (self),
-                                 "page.maximize",
-                                 !priv->maximized && panel_widget_get_can_maximize (self));
+  panel_widget_action_set_enabled (self,
+                                   "page.maximize",
+                                   !priv->maximized && panel_widget_get_can_maximize (self));
 }
 
 static void
@@ -150,6 +236,21 @@ panel_widget_size_allocate (GtkWidget *widget,
     gtk_widget_allocate (priv->child, width, height, baseline, NULL);
 }
 
+static void
+panel_widget_constructed (GObject *object)
+{
+  PanelWidget *self = PANEL_WIDGET (object);
+  PanelWidgetClass *widget_class = PANEL_WIDGET_GET_CLASS (self);
+  PanelWidgetClassPrivate *class_priv = panel_widget_class_get_private (widget_class);
+  PanelActionMuxer *muxer;
+
+  G_OBJECT_CLASS (panel_widget_parent_class)->constructed (object);
+
+  muxer = PANEL_ACTION_MUXER (_panel_widget_get_action_muxer (self));
+
+  panel_action_muxer_connect_actions (muxer, self, class_priv->actions);
+}
+
 static void
 panel_widget_dispose (GObject *object)
 {
@@ -158,7 +259,7 @@ panel_widget_dispose (GObject *object)
 
   if (priv->action_muxer != NULL)
     {
-      panel_action_muxer_clear (priv->action_muxer);
+      panel_action_muxer_remove_all (priv->action_muxer);
       g_clear_object (&priv->action_muxer);
     }
 
@@ -300,6 +401,10 @@ panel_widget_class_init (PanelWidgetClass *klass)
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
 
+  g_type_class_adjust_private_offset (klass, &PanelWidget_private_offset);
+  panel_widget_parent_class = g_type_class_peek_parent (klass);
+
+  object_class->constructed = panel_widget_constructed;
   object_class->dispose = panel_widget_dispose;
   object_class->get_property = panel_widget_get_property;
   object_class->set_property = panel_widget_set_property;
@@ -461,7 +566,7 @@ panel_widget_class_init (PanelWidgetClass *klass)
 
   gtk_widget_class_set_css_name (widget_class, "panelwidget");
 
-  gtk_widget_class_install_action (widget_class, "page.maximize", NULL, panel_widget_maximize_action);
+  panel_widget_class_install_action (klass, "page.maximize", NULL, panel_widget_maximize_action);
 
   /* Ensure we have quarks for known types */
   g_quark_from_static_string (PANEL_WIDGET_KIND_ANY);
@@ -471,8 +576,10 @@ panel_widget_class_init (PanelWidgetClass *klass)
 }
 
 static void
-panel_widget_init (PanelWidget *self)
+panel_widget_init (GTypeInstance *instance,
+                   gpointer       g_class)
 {
+  PanelWidget *self = PANEL_WIDGET (instance);
   PanelWidgetPrivate *priv = panel_widget_get_instance_private (self);
 
   panel_widget_update_actions (self);
@@ -1064,7 +1171,7 @@ panel_widget_add_child (GtkBuildable *buildable,
 }
 
 static void
-buildable_iface_init (GtkBuildableIface *iface)
+panel_widget_class_init_buildable (GtkBuildableIface *iface)
 {
   iface->add_child = panel_widget_add_child;
 }
@@ -1146,15 +1253,15 @@ _panel_widget_emit_presented (PanelWidget *self)
   g_signal_emit (self, signals [PRESENTED], 0);
 }
 
-GActionGroup *
-_panel_widget_get_action_group (PanelWidget *self)
+PanelActionMuxer *
+_panel_widget_get_action_muxer (PanelWidget *self)
 {
   PanelWidgetPrivate *priv = panel_widget_get_instance_private (self);
 
   if (priv->action_muxer == NULL)
     priv->action_muxer = panel_action_muxer_new ();
 
-  return G_ACTION_GROUP (priv->action_muxer);
+  return priv->action_muxer;
 }
 
 void
@@ -1162,12 +1269,188 @@ panel_widget_insert_action_group (PanelWidget  *self,
                                   const char   *prefix,
                                   GActionGroup *group)
 {
-  GActionGroup *muxer;
+  PanelActionMuxer *muxer;
 
   g_return_if_fail (PANEL_IS_WIDGET (self));
   g_return_if_fail (prefix != NULL);
 
-  muxer = _panel_widget_get_action_group (self);
+  if ((muxer = _panel_widget_get_action_muxer (self)))
+    panel_action_muxer_insert_action_group (muxer, prefix, group);
+}
+
+static void
+panel_widget_class_add_action (PanelWidgetClass *widget_class,
+                               PanelAction      *action)
+{
+  PanelWidgetClassPrivate *class_priv = panel_widget_class_get_private (widget_class);
+
+  g_assert (PANEL_IS_WIDGET_CLASS (widget_class));
+  g_assert (action != NULL);
+  g_assert (action->next == NULL);
+  g_assert (action->position == 0);
+
+  /* Precalculate action "position". To be stable this is the
+   * number of items from the end.
+   */
+  for (const PanelAction *iter = class_priv->actions;
+       iter != NULL;
+       iter = iter->next)
+    action->position++;
+
+  action->next = class_priv->actions;
+  class_priv->actions = action;
+}
+
+/**
+ * panel_widget_class_install_action:
+ * @widget_class: a `PanelWidgetClass`
+ * @action_name: a prefixed action name, such as "clipboard.paste"
+ * @parameter_type: (nullable): the parameter type
+ * @activate: (scope notified): callback to use when the action is activated
+ *
+ * This should be called at class initialization time to specify
+ * actions to be added for all instances of this class.
+ *
+ * Actions installed by this function are stateless. The only state
+ * they have is whether they are enabled or not.
+ */
+void
+panel_widget_class_install_action (PanelWidgetClass            *widget_class,
+                                   const char                  *action_name,
+                                   const char                  *parameter_type,
+                                   GtkWidgetActionActivateFunc  activate)
+{
+  PanelAction *action;
+
+  g_return_if_fail (PANEL_IS_WIDGET_CLASS (widget_class));
+  g_return_if_fail (action_name != NULL);
+  g_return_if_fail (activate != NULL);
 
-  panel_action_muxer_insert_action_group (PANEL_ACTION_MUXER (muxer), prefix, group);
+  action = g_new0 (PanelAction, 1);
+  action->owner = G_TYPE_FROM_CLASS (widget_class);
+  action->name = g_intern_string (action_name);
+  if (parameter_type != NULL)
+    action->parameter_type = g_variant_type_new (parameter_type);
+  action->activate = (PanelActionActivateFunc)activate;
+
+  panel_widget_class_add_action (widget_class, action);
+}
+
+static const GVariantType *
+determine_type (GParamSpec *pspec)
+{
+  if (G_TYPE_IS_ENUM (pspec->value_type))
+    return G_VARIANT_TYPE_STRING;
+
+  switch (pspec->value_type)
+    {
+    case G_TYPE_BOOLEAN:
+      return G_VARIANT_TYPE_BOOLEAN;
+
+    case G_TYPE_INT:
+      return G_VARIANT_TYPE_INT32;
+
+    case G_TYPE_UINT:
+      return G_VARIANT_TYPE_UINT32;
+
+    case G_TYPE_DOUBLE:
+    case G_TYPE_FLOAT:
+      return G_VARIANT_TYPE_DOUBLE;
+
+    case G_TYPE_STRING:
+      return G_VARIANT_TYPE_STRING;
+
+    default:
+      g_critical ("Unable to use panel_widget_class_install_property_action with property '%s:%s' of type 
'%s'",
+                  g_type_name (pspec->owner_type), pspec->name, g_type_name (pspec->value_type));
+      return NULL;
+    }
+}
+
+/**
+ * panel_widget_class_install_property_action:
+ * @widget_class: a `GtkWidgetClass`
+ * @action_name: name of the action
+ * @property_name: name of the property in instances of @widget_class
+ *   or any parent class.
+ *
+ * Installs an action called @action_name on @widget_class and
+ * binds its state to the value of the @property_name property.
+ *
+ * This function will perform a few santity checks on the property selected
+ * via @property_name. Namely, the property must exist, must be readable,
+ * writable and must not be construct-only. There are also restrictions
+ * on the type of the given property, it must be boolean, int, unsigned int,
+ * double or string. If any of these conditions are not met, a critical
+ * warning will be printed and no action will be added.
+ *
+ * The state type of the action matches the property type.
+ *
+ * If the property is boolean, the action will have no parameter and
+ * toggle the property value. Otherwise, the action will have a parameter
+ * of the same type as the property.
+ */
+void
+panel_widget_class_install_property_action (PanelWidgetClass *widget_class,
+                                            const char       *action_name,
+                                            const char       *property_name)
+{
+  const GVariantType *state_type;
+  PanelAction *action;
+  GParamSpec *pspec;
+
+  g_return_if_fail (GTK_IS_WIDGET_CLASS (widget_class));
+
+  if (!(pspec = g_object_class_find_property (G_OBJECT_CLASS (widget_class), property_name)))
+    {
+      g_critical ("Attempted to use non-existent property '%s:%s' for 
panel_widget_class_install_property_action",
+                  G_OBJECT_CLASS_NAME (widget_class), property_name);
+      return;
+    }
+
+  if (~pspec->flags & G_PARAM_READABLE || ~pspec->flags & G_PARAM_WRITABLE || pspec->flags & 
G_PARAM_CONSTRUCT_ONLY)
+    {
+      g_critical ("Property '%s:%s' used with panel_widget_class_install_property_action must be readable, 
writable, and not construct-only",
+                  G_OBJECT_CLASS_NAME (widget_class), property_name);
+      return;
+    }
+
+  state_type = determine_type (pspec);
+
+  if (!state_type)
+    return;
+
+  action = g_new0 (PanelAction, 1);
+  action->owner = G_TYPE_FROM_CLASS (widget_class);
+  action->name = g_intern_string (action_name);
+  action->pspec = pspec;
+  action->state_type = state_type;
+  if (action->pspec->value_type != G_TYPE_BOOLEAN)
+    action->parameter_type = action->state_type;
+
+  panel_widget_class_add_action (widget_class, action);
+}
+
+void
+panel_widget_action_set_enabled (PanelWidget *self,
+                                 const char  *action_name,
+                                 gboolean     enabled)
+{
+  PanelWidgetClassPrivate *class_priv;
+  PanelActionMuxer *muxer;
+
+  g_return_if_fail (PANEL_IS_WIDGET (self));
+  g_return_if_fail (action_name != NULL);
+
+  class_priv = panel_widget_class_get_private (PANEL_WIDGET_GET_CLASS (self));
+  muxer = _panel_widget_get_action_muxer (self);
+
+  for (const PanelAction *iter = class_priv->actions; iter; iter = iter->next)
+    {
+      if (g_strcmp0 (iter->name, action_name) == 0)
+        {
+          panel_action_muxer_set_enabled (muxer, iter, enabled);
+          break;
+        }
+    }
 }
diff --git a/src/panel-widget.h b/src/panel-widget.h
index f161a2b..38c6590 100644
--- a/src/panel-widget.h
+++ b/src/panel-widget.h
@@ -34,10 +34,10 @@ G_DECLARE_DERIVABLE_TYPE (PanelWidget, panel_widget, PANEL, WIDGET, GtkWidget)
 
 struct _PanelWidgetClass
 {
-  GtkWidgetClass parent_instance;
+  GtkWidgetClass  parent_instance;
 
-  GtkWidget *(*get_default_focus) (PanelWidget *self);
-  void       (*presented)         (PanelWidget *self);
+  GtkWidget      *(*get_default_focus) (PanelWidget *self);
+  void            (*presented)         (PanelWidget *self);
 
   /*< private >*/
   gpointer _reserved[8];
@@ -49,83 +49,96 @@ struct _PanelWidgetClass
 #define PANEL_WIDGET_KIND_UTILITY  "utility"
 
 PANEL_AVAILABLE_IN_ALL
-GtkWidget         *panel_widget_new                 (void);
+GtkWidget         *panel_widget_new                           (void);
 PANEL_AVAILABLE_IN_ALL
-GtkWidget         *panel_widget_get_child           (PanelWidget       *self);
+GtkWidget         *panel_widget_get_child                     (PanelWidget                 *self);
 PANEL_AVAILABLE_IN_ALL
-void               panel_widget_set_child           (PanelWidget       *self,
-                                                     GtkWidget         *child);
+void               panel_widget_set_child                     (PanelWidget                 *self,
+                                                               GtkWidget                   *child);
 PANEL_AVAILABLE_IN_ALL
-const char        *panel_widget_get_title           (PanelWidget       *self);
+const char        *panel_widget_get_title                     (PanelWidget                 *self);
 PANEL_AVAILABLE_IN_ALL
-void               panel_widget_set_title           (PanelWidget       *self,
-                                                     const char        *title);
+void               panel_widget_set_title                     (PanelWidget                 *self,
+                                                               const char                  *title);
 PANEL_AVAILABLE_IN_ALL
-GIcon             *panel_widget_get_icon            (PanelWidget       *self);
+GIcon             *panel_widget_get_icon                      (PanelWidget                 *self);
 PANEL_AVAILABLE_IN_ALL
-void               panel_widget_set_icon            (PanelWidget       *self,
-                                                     GIcon             *icon);
+void               panel_widget_set_icon                      (PanelWidget                 *self,
+                                                               GIcon                       *icon);
 PANEL_AVAILABLE_IN_ALL
-const char        *panel_widget_get_icon_name       (PanelWidget       *self);
+const char        *panel_widget_get_icon_name                 (PanelWidget                 *self);
 PANEL_AVAILABLE_IN_ALL
-void               panel_widget_set_icon_name       (PanelWidget       *self,
-                                                     const char        *icon_name);
+void               panel_widget_set_icon_name                 (PanelWidget                 *self,
+                                                               const char                  *icon_name);
 PANEL_AVAILABLE_IN_ALL
-gboolean           panel_widget_get_reorderable     (PanelWidget       *self);
+gboolean           panel_widget_get_reorderable               (PanelWidget                 *self);
 PANEL_AVAILABLE_IN_ALL
-void               panel_widget_set_reorderable     (PanelWidget       *self,
-                                                     gboolean           reorderable);
+void               panel_widget_set_reorderable               (PanelWidget                 *self,
+                                                               gboolean                     reorderable);
 PANEL_AVAILABLE_IN_ALL
-gboolean           panel_widget_get_can_maximize    (PanelWidget       *self);
+gboolean           panel_widget_get_can_maximize              (PanelWidget                 *self);
 PANEL_AVAILABLE_IN_ALL
-void               panel_widget_set_can_maximize    (PanelWidget       *self,
-                                                     gboolean           can_maximize);
+void               panel_widget_set_can_maximize              (PanelWidget                 *self,
+                                                               gboolean                     can_maximize);
 PANEL_AVAILABLE_IN_ALL
-gboolean           panel_widget_get_modified        (PanelWidget       *self);
+gboolean           panel_widget_get_modified                  (PanelWidget                 *self);
 PANEL_AVAILABLE_IN_ALL
-void               panel_widget_set_modified        (PanelWidget       *self,
-                                                     gboolean           modified);
+void               panel_widget_set_modified                  (PanelWidget                 *self,
+                                                               gboolean                     modified);
 PANEL_AVAILABLE_IN_ALL
-gboolean           panel_widget_get_needs_attention (PanelWidget       *self);
+gboolean           panel_widget_get_needs_attention           (PanelWidget                 *self);
 PANEL_AVAILABLE_IN_ALL
-void               panel_widget_set_needs_attention (PanelWidget       *self,
-                                                     gboolean           needs_attention);
+void               panel_widget_set_needs_attention           (PanelWidget                 *self,
+                                                               gboolean                     needs_attention);
 PANEL_AVAILABLE_IN_ALL
-void               panel_widget_maximize            (PanelWidget       *self);
+void               panel_widget_maximize                      (PanelWidget                 *self);
 PANEL_AVAILABLE_IN_ALL
-void               panel_widget_unmaximize          (PanelWidget       *self);
+void               panel_widget_unmaximize                    (PanelWidget                 *self);
 PANEL_AVAILABLE_IN_ALL
-const char        *panel_widget_get_kind            (PanelWidget       *self);
+const char        *panel_widget_get_kind                      (PanelWidget                 *self);
 PANEL_AVAILABLE_IN_ALL
-void               panel_widget_set_kind            (PanelWidget       *self,
-                                                     const char        *kind);
+void               panel_widget_set_kind                      (PanelWidget                 *self,
+                                                               const char                  *kind);
 PANEL_AVAILABLE_IN_ALL
-gboolean           panel_widget_get_busy            (PanelWidget       *self);
+gboolean           panel_widget_get_busy                      (PanelWidget                 *self);
 PANEL_AVAILABLE_IN_ALL
-void               panel_widget_mark_busy           (PanelWidget       *self);
+void               panel_widget_mark_busy                     (PanelWidget                 *self);
 PANEL_AVAILABLE_IN_ALL
-void               panel_widget_unmark_busy         (PanelWidget       *self);
+void               panel_widget_unmark_busy                   (PanelWidget                 *self);
 PANEL_AVAILABLE_IN_ALL
-GMenuModel        *panel_widget_get_menu_model      (PanelWidget       *self);
+GMenuModel        *panel_widget_get_menu_model                (PanelWidget                 *self);
 PANEL_AVAILABLE_IN_ALL
-void               panel_widget_set_menu_model      (PanelWidget       *self,
-                                                     GMenuModel        *menu_model);
+void               panel_widget_set_menu_model                (PanelWidget                 *self,
+                                                               GMenuModel                  *menu_model);
 PANEL_AVAILABLE_IN_ALL
-void               panel_widget_raise               (PanelWidget       *self);
+void               panel_widget_raise                         (PanelWidget                 *self);
 PANEL_AVAILABLE_IN_ALL
-GtkWidget         *panel_widget_get_default_focus   (PanelWidget       *self);
+GtkWidget         *panel_widget_get_default_focus             (PanelWidget                 *self);
 PANEL_AVAILABLE_IN_ALL
-gboolean           panel_widget_focus_default       (PanelWidget       *self);
+gboolean           panel_widget_focus_default                 (PanelWidget                 *self);
 PANEL_AVAILABLE_IN_ALL
-PanelSaveDelegate *panel_widget_get_save_delegate   (PanelWidget       *self);
+PanelSaveDelegate *panel_widget_get_save_delegate             (PanelWidget                 *self);
 PANEL_AVAILABLE_IN_ALL
-void               panel_widget_set_save_delegate   (PanelWidget       *self,
-                                                     PanelSaveDelegate *save_delegate);
+void               panel_widget_set_save_delegate             (PanelWidget                 *self,
+                                                               PanelSaveDelegate           *save_delegate);
 PANEL_AVAILABLE_IN_ALL
-void               panel_widget_close               (PanelWidget       *self);
+void               panel_widget_close                         (PanelWidget                 *self);
 PANEL_AVAILABLE_IN_ALL
-void               panel_widget_insert_action_group (PanelWidget       *self,
-                                                     const char        *prefix,
-                                                     GActionGroup      *group);
+void               panel_widget_insert_action_group           (PanelWidget                 *self,
+                                                               const char                  *prefix,
+                                                               GActionGroup                *group);
+PANEL_AVAILABLE_IN_ALL
+void               panel_widget_class_install_action          (PanelWidgetClass            *widget_class,
+                                                               const char                  *action_name,
+                                                               const char                  *parameter_type,
+                                                               GtkWidgetActionActivateFunc  activate);
+PANEL_AVAILABLE_IN_ALL
+void               panel_widget_action_set_enabled            (PanelWidget                 *widget,
+                                                               const char                  *action_name,
+                                                               gboolean                     enabled);
+PANEL_AVAILABLE_IN_ALL
+void               panel_widget_class_install_property_action (PanelWidgetClass            *widget_class,
+                                                               const char                  *action_name,
+                                                               const char                  *property_name);
 
 G_END_DECLS


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