[libpanel/wip/chergert/fix-14: 2/2] layout: implement PanelLayout and PanelLayoutItem




commit 9585d8ba9f952ed783937aed21f3b772824b64f3
Author: Christian Hergert <chergert redhat com>
Date:   Mon Sep 12 16:19:53 2022 -0700

    layout: implement PanelLayout and PanelLayoutItem
    
    This creates a layout object which can be populated with items. The items
    have an id, position, type-hint, and metadata. One of those items or more
    must be set for the item to be of any use.
    
    The goal here is for the PanelDock to use this structure to populate state
    about the interface and save it to GVariant to be restored later.

 src/libpanel.h                  |   2 +
 src/meson.build                 |   4 +
 src/panel-layout-item-private.h |  32 +++
 src/panel-layout-item.c         | 549 ++++++++++++++++++++++++++++++++++++++++
 src/panel-layout-item.h         |  80 ++++++
 src/panel-layout-private.h      |  29 +++
 src/panel-layout.c              | 276 ++++++++++++++++++++
 src/panel-layout.h              |  64 +++++
 testsuite/meson.build           |   9 +-
 testsuite/test-layout.c         |  89 +++++++
 10 files changed, 1132 insertions(+), 2 deletions(-)
---
diff --git a/src/libpanel.h b/src/libpanel.h
index 5d01ccb..16c90ee 100644
--- a/src/libpanel.h
+++ b/src/libpanel.h
@@ -31,6 +31,8 @@
 # include "panel-grid.h"
 # include "panel-grid-column.h"
 # include "panel-init.h"
+# include "panel-layout.h"
+# include "panel-layout-item.h"
 # include "panel-omni-bar.h"
 # include "panel-paned.h"
 # include "panel-position.h"
diff --git a/src/meson.build b/src/meson.build
index b018115..c6280a7 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -40,6 +40,8 @@ libpanel_sources = [
   'panel-grid.c',
   'panel-grid-column.c',
   'panel-init.c',
+  'panel-layout.c',
+  'panel-layout-item.c',
   'panel-omni-bar.c',
   'panel-paned.c',
   'panel-position.c',
@@ -62,6 +64,8 @@ libpanel_headers = [
   'panel-grid.h',
   'panel-grid-column.h',
   'panel-init.h',
+  'panel-layout.h',
+  'panel-layout-item.h',
   'panel-omni-bar.h',
   'panel-paned.h',
   'panel-position.h',
diff --git a/src/panel-layout-item-private.h b/src/panel-layout-item-private.h
new file mode 100644
index 0000000..f622f1f
--- /dev/null
+++ b/src/panel-layout-item-private.h
@@ -0,0 +1,32 @@
+/* panel-layout-item-private.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "panel-layout-item.h"
+
+G_BEGIN_DECLS
+
+PanelLayoutItem *_panel_layout_item_new_from_variant (GVariant         *variant,
+                                                      GError          **error);
+void             _panel_layout_item_to_variant       (PanelLayoutItem  *self,
+                                                      GVariantBuilder  *builder);
+
+G_END_DECLS
diff --git a/src/panel-layout-item.c b/src/panel-layout-item.c
new file mode 100644
index 0000000..db052e7
--- /dev/null
+++ b/src/panel-layout-item.c
@@ -0,0 +1,549 @@
+/* panel-layout-item.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#include "config.h"
+
+#include "panel-layout-item-private.h"
+#include "panel-macros.h"
+#include "panel-position-private.h"
+
+struct _PanelLayoutItem
+{
+  GObject parent_instance;
+  PanelPosition *position;
+  char *id;
+  char *type_hint;
+  GHashTable *metadata;
+};
+
+enum {
+  PROP_0,
+  PROP_ID,
+  PROP_POSITION,
+  PROP_TYPE_HINT,
+  N_PROPS
+};
+
+G_DEFINE_FINAL_TYPE (PanelLayoutItem, panel_layout_item, G_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+panel_layout_item_dispose (GObject *object)
+{
+  PanelLayoutItem *self = (PanelLayoutItem *)object;
+
+  g_clear_object (&self->position);
+
+  g_clear_pointer (&self->id, g_free);
+  g_clear_pointer (&self->type_hint, g_free);
+  g_clear_pointer (&self->metadata, g_hash_table_unref);
+
+  G_OBJECT_CLASS (panel_layout_item_parent_class)->dispose (object);
+}
+
+static void
+panel_layout_item_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  PanelLayoutItem *self = PANEL_LAYOUT_ITEM (object);
+
+  switch (prop_id)
+    {
+    case PROP_ID:
+      g_value_set_string (value, panel_layout_item_get_id (self));
+      break;
+
+    case PROP_POSITION:
+      g_value_set_object (value, panel_layout_item_get_position (self));
+      break;
+
+    case PROP_TYPE_HINT:
+      g_value_set_string (value, panel_layout_item_get_type_hint (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+panel_layout_item_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  PanelLayoutItem *self = PANEL_LAYOUT_ITEM (object);
+
+  switch (prop_id)
+    {
+    case PROP_ID:
+      panel_layout_item_set_id (self, g_value_get_string (value));
+      break;
+
+    case PROP_POSITION:
+      panel_layout_item_set_position (self, g_value_get_object (value));
+      break;
+
+    case PROP_TYPE_HINT:
+      panel_layout_item_set_type_hint (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+panel_layout_item_class_init (PanelLayoutItemClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = panel_layout_item_dispose;
+  object_class->get_property = panel_layout_item_get_property;
+  object_class->set_property = panel_layout_item_set_property;
+
+  properties [PROP_ID] =
+    g_param_spec_string ("id", NULL, NULL, NULL,
+                         (G_PARAM_READWRITE |
+                          G_PARAM_EXPLICIT_NOTIFY |
+                          G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_POSITION] =
+    g_param_spec_object ("position", NULL, NULL,
+                         PANEL_TYPE_POSITION,
+                         (G_PARAM_READWRITE |
+                          G_PARAM_EXPLICIT_NOTIFY |
+                          G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_TYPE_HINT] =
+    g_param_spec_string ("type-hint", NULL, NULL, NULL,
+                         (G_PARAM_READWRITE |
+                          G_PARAM_EXPLICIT_NOTIFY |
+                          G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+panel_layout_item_init (PanelLayoutItem *self)
+{
+}
+
+/**
+ * panel_layout_item_get_id:
+ * @self: a #PanelLayoutItem
+ *
+ * Gets the id for the layout item, if any.
+ *
+ * Returns: (nullable): a string containing the id; otherwise %NULL
+ */
+const char *
+panel_layout_item_get_id (PanelLayoutItem *self)
+{
+  g_return_val_if_fail (PANEL_IS_LAYOUT_ITEM (self), NULL);
+
+  return self->id;
+}
+
+/**
+ * panel_layout_item_set_id:
+ * @self: a #PanelLayoutItem
+ * @id: (nullable): an optional identifier for the item
+ *
+ * Sets the identifier for the item.
+ *
+ * The identifier should generally be global to the layout as it would
+ * not be expected to come across multiple items with the same id.
+ */
+void
+panel_layout_item_set_id (PanelLayoutItem *self,
+                          const char      *id)
+{
+  g_return_if_fail (PANEL_IS_LAYOUT_ITEM (self));
+
+  if (panel_set_string (&self->id, id))
+    g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ID]);
+}
+
+/**
+ * panel_layout_item_get_type_hint:
+ * @self: a #PanelLayoutItem
+ *
+ * Gets the type hint for an item.
+ *
+ * Returns: (nullable): a type-hint or %NULL
+ */
+const char *
+panel_layout_item_get_type_hint (PanelLayoutItem *self)
+{
+  g_return_val_if_fail (PANEL_IS_LAYOUT_ITEM (self), NULL);
+
+  return self->type_hint;
+}
+
+/**
+ * panel_layout_item_set_type_hint:
+ * @self: a #PanelLayoutItem
+ * @type_hint: (nullable): a type hint string for the item
+ *
+ * Sets the type-hint value for the item.
+ *
+ * This is generally used to help inflate the right version of
+ * an object when loading layout items.
+ */
+void
+panel_layout_item_set_type_hint (PanelLayoutItem *self,
+                                 const char      *type_hint)
+{
+  g_return_if_fail (PANEL_IS_LAYOUT_ITEM (self));
+
+  if (panel_set_string (&self->type_hint, type_hint))
+    g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TYPE_HINT]);
+}
+
+/**
+ * panel_layout_item_get_position:
+ * @self: a #PanelLayoutItem
+ *
+ * Gets the #PanelPosition for the item.
+ *
+ * Returns: (transfer none) (nullable): a #PanelPosition or %NULL
+ */
+PanelPosition *
+panel_layout_item_get_position (PanelLayoutItem *self)
+{
+  g_return_val_if_fail (PANEL_IS_LAYOUT_ITEM (self), NULL);
+
+  return self->position;
+}
+
+/**
+ * panel_layout_item_set_position:
+ * @self: a #PanelLayoutItem
+ * @position: (nullable): a #PanelPosition or %NULL
+ *
+ * Sets the position for @self, if any.
+ */
+void
+panel_layout_item_set_position (PanelLayoutItem *self,
+                                PanelPosition   *position)
+{
+  g_return_if_fail (PANEL_IS_LAYOUT_ITEM (self));
+  g_return_if_fail (!position || PANEL_IS_POSITION (position));
+
+  if (g_set_object (&self->position, position))
+    g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_POSITION]);
+}
+
+/**
+ * panel_layout_item_set_metadata: (skip)
+ * @self: a #PanelLayoutItem
+ *
+ * A variadic helper to set metadata.
+ *
+ * The format should be identical to g_variant_new().
+ */
+void
+panel_layout_item_set_metadata (PanelLayoutItem *self,
+                                const char      *key,
+                                const char      *format,
+                                ...)
+{
+  GVariant *value;
+  va_list args;
+
+  g_return_if_fail (PANEL_IS_LAYOUT_ITEM (self));
+  g_return_if_fail (key != NULL);
+
+  va_start (args, format);
+  value = g_variant_new_va (format, NULL, &args);
+  va_end (args);
+
+  g_return_if_fail (value != NULL);
+
+  panel_layout_item_set_metadata_value (self, key, value);
+}
+
+/**
+ * panel_layout_item_has_metadata:
+ * @self: a #PanelLayoutItem
+ * @key: the name of the metadata
+ * @value_type: (out) (nullable): a location for a #GVariantType or %NULL
+ *
+ * If the item contains a metadata value for @key.
+ *
+ * Checks if a value exists for a metadata key and retrieves the #GVariantType
+ * for that key.
+ *
+ * Returns: %TRUE if @self contains metadata named @key and @value_type is set
+ *   to the value's #GVariantType. Otherwise %FALSE and @value_type is unchanged.
+ */
+gboolean
+panel_layout_item_has_metadata (PanelLayoutItem     *self,
+                                const char          *key,
+                                const GVariantType **value_type)
+{
+  g_autoptr(GVariant) value = NULL;
+
+  g_return_val_if_fail (PANEL_IS_LAYOUT_ITEM (self), FALSE);
+  g_return_val_if_fail (key != NULL, FALSE);
+
+  if ((value = panel_layout_item_get_metadata_value (self, key, NULL)))
+    {
+      g_assert (!g_variant_is_floating (value));
+
+      if (value_type != NULL)
+        *value_type = g_variant_get_type (value);
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+/**
+ * panel_layout_item_has_metadata_with_type:
+ * @self: a #PanelLayoutItem
+ * @key: the metadata key
+ * @expected_type: the #GVariantType to check for @key
+ *
+ * Checks if the item contains metadata @key with @expected_type.
+ *
+ * Returns: %TRUE if a value was found for @key matching @expected_typed;
+ *   otherwise %FALSE is returned.
+ */
+gboolean
+panel_layout_item_has_metadata_with_type (PanelLayoutItem    *self,
+                                          const char         *key,
+                                          const GVariantType *expected_type)
+{
+  const GVariantType *value_type = NULL;
+
+  g_return_val_if_fail (PANEL_IS_LAYOUT_ITEM (self), FALSE);
+  g_return_val_if_fail (key != NULL, FALSE);
+  g_return_val_if_fail (expected_type != NULL, FALSE);
+
+  if (panel_layout_item_has_metadata (self, key, &value_type))
+    return g_variant_type_equal (value_type, expected_type);
+
+  return FALSE;
+}
+
+/**
+ * panel_layout_item_get_metadata: (skip)
+ * @self: a #PanelLayoutItem
+ * @key: the key for the metadata value
+ * @format: the format of the value
+ *
+ * Extract a metadata value matching @format.
+ *
+ * It is an error to use this function on untrusted data where you have not
+ * checked the type of the value of @key using panel_layout_item_has_metadata()
+ * or panel_layout_item_has_metadata_with_type().
+ */
+void
+panel_layout_item_get_metadata (PanelLayoutItem *self,
+                                const char      *key,
+                                const char      *format,
+                                ...)
+{
+  g_autoptr(GVariant) value = NULL;
+  va_list args;
+
+  g_return_if_fail (PANEL_IS_LAYOUT_ITEM (self));
+  g_return_if_fail (key != NULL);
+  g_return_if_fail (format != NULL);
+  g_return_if_fail (g_variant_type_string_is_valid (format));
+  g_return_if_fail (panel_layout_item_has_metadata (self, key, NULL));
+
+  value = panel_layout_item_get_metadata_value (self, key, NULL);
+
+  g_return_if_fail (value != NULL);
+
+  va_start (args, format);
+  g_variant_get_va (value, format, NULL, &args);
+  va_end (args);
+}
+
+/**
+ * panel_layout_item_get_metadata_value:
+ * @self: a #PanelLayoutItem
+ * @key: the metadata key
+ * @expected_type: (nullable): a #GVariantType or %NULL
+ *
+ * Retrieves the metadata value for @key.
+ *
+ * If @expected_type is non-%NULL, any non-%NULL value returned from this
+ * function will match @expected_type.
+ *
+ * Returns: (transfer full): a non-floating #GVariant which should be
+ *   released with g_variant_unref(); otherwise %NULL.
+ */
+GVariant *
+panel_layout_item_get_metadata_value (PanelLayoutItem    *self,
+                                      const char         *key,
+                                      const GVariantType *expected_type)
+{
+  GVariant *ret;
+
+  g_return_val_if_fail (PANEL_IS_LAYOUT_ITEM (self), NULL);
+  g_return_val_if_fail (key != NULL, NULL);
+
+  if (self->metadata == NULL)
+    return NULL;
+
+  if ((ret = g_hash_table_lookup (self->metadata, key)))
+    {
+      if (expected_type == NULL || g_variant_is_of_type (ret, expected_type))
+        return g_variant_ref (ret);
+    }
+
+  return NULL;
+}
+
+/**
+ * panel_layout_item_set_metadata_value:
+ * @self: a #PanelLayoutItem
+ * @key: the metadata key
+ * @value: (nullable): the value for @key or %NULL
+ *
+ * Sets the value for metadata @key.
+ *
+ * If @value is %NULL, the metadata key is unset.
+ */
+void
+panel_layout_item_set_metadata_value (PanelLayoutItem *self,
+                                      const char      *key,
+                                      GVariant        *value)
+{
+  g_return_if_fail (PANEL_IS_LAYOUT_ITEM (self));
+  g_return_if_fail (key != NULL);
+
+  if (value != NULL)
+    {
+      if G_UNLIKELY (self->metadata == NULL)
+        self->metadata = g_hash_table_new_full (g_str_hash,
+                                                g_str_equal,
+                                                g_free,
+                                                (GDestroyNotify)g_variant_unref);
+      g_hash_table_insert (self->metadata,
+                           g_strdup (key),
+                           g_variant_ref_sink (value));
+    }
+  else
+    {
+      if (self->metadata != NULL)
+        g_hash_table_remove (self->metadata, key);
+    }
+}
+
+void
+_panel_layout_item_to_variant (PanelLayoutItem *self,
+                               GVariantBuilder *builder)
+{
+  g_return_if_fail (PANEL_IS_LAYOUT_ITEM (self));
+  g_return_if_fail (builder != NULL);
+
+  g_variant_builder_open (builder, G_VARIANT_TYPE ("v"));
+  g_variant_builder_open (builder, G_VARIANT_TYPE ("a{sv}"));
+
+  if (self->position != NULL)
+    g_variant_builder_add_parsed (builder,
+                                  "{'position',<%v>}",
+                                  _panel_position_to_variant (self->position));
+
+  if (self->id != NULL)
+    g_variant_builder_add_parsed (builder, "{'id',<%s>}", self->id);
+
+  if (self->type_hint != NULL)
+    g_variant_builder_add_parsed (builder, "{'type-hint',<%s>}", self->type_hint);
+
+  if (self->metadata != NULL && g_hash_table_size (self->metadata) > 0)
+    {
+      GHashTableIter iter;
+      const char *key;
+      GVariant *value;
+
+      g_variant_builder_open (builder, G_VARIANT_TYPE ("{sv}"));
+      g_variant_builder_add (builder, "s", "metadata");
+      g_variant_builder_open (builder, G_VARIANT_TYPE ("v"));
+      g_variant_builder_open (builder, G_VARIANT_TYPE ("a{sv}"));
+
+      g_hash_table_iter_init (&iter, self->metadata);
+      while (g_hash_table_iter_next (&iter, (gpointer *)&key, (gpointer *)&value))
+        g_variant_builder_add_parsed (builder, "{%s,<%v>}", key, value);
+
+      g_variant_builder_close (builder);
+      g_variant_builder_close (builder);
+      g_variant_builder_close (builder);
+    }
+
+  g_variant_builder_close (builder);
+  g_variant_builder_close (builder);
+}
+
+PanelLayoutItem *
+panel_layout_item_new (void)
+{
+  return g_object_new (PANEL_TYPE_LAYOUT_ITEM, NULL);
+}
+
+PanelLayoutItem *
+_panel_layout_item_new_from_variant (GVariant  *variant,
+                                     GError   **error)
+{
+  g_autoptr(GVariant) positionv = NULL;
+  g_autoptr(GVariant) metadatav = NULL;
+  PanelLayoutItem *self;
+
+  g_return_val_if_fail (variant != NULL, NULL);
+  g_return_val_if_fail (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARDICT), NULL);
+
+  self = g_object_new (PANEL_TYPE_LAYOUT_ITEM, NULL);
+
+  g_variant_lookup (variant, "id", "s", &self->id);
+  g_variant_lookup (variant, "type-hint", "s", &self->type_hint);
+
+  if ((positionv = g_variant_lookup_value (variant, "position", NULL)))
+    {
+      g_autoptr(GVariant) child = g_variant_get_variant (positionv);
+      self->position = _panel_position_new_from_variant (child);
+    }
+
+  if ((metadatav = g_variant_lookup_value (variant, "metadata", G_VARIANT_TYPE_VARDICT)))
+    {
+      GVariantIter iter;
+      GVariant *value;
+      char *key;
+
+      g_variant_iter_init (&iter, metadatav);
+
+      while (g_variant_iter_loop (&iter, "{sv}", &key, &value))
+        {
+          g_autoptr(GVariant) unwrapped = g_variant_get_variant (value);
+          panel_layout_item_set_metadata_value (self, key, unwrapped);
+        }
+    }
+
+  return self;
+}
diff --git a/src/panel-layout-item.h b/src/panel-layout-item.h
new file mode 100644
index 0000000..4ba4efd
--- /dev/null
+++ b/src/panel-layout-item.h
@@ -0,0 +1,80 @@
+/* panel-layout-item.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+
+#pragma once
+
+#include <glib-object.h>
+
+#include "panel-position.h"
+#include "panel-version-macros.h"
+
+G_BEGIN_DECLS
+
+#define PANEL_TYPE_LAYOUT_ITEM (panel_layout_item_get_type())
+
+PANEL_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (PanelLayoutItem, panel_layout_item, PANEL, LAYOUT_ITEM, GObject)
+
+PANEL_AVAILABLE_IN_ALL
+PanelLayoutItem *panel_layout_item_new                    (void);
+PANEL_AVAILABLE_IN_ALL
+PanelPosition   *panel_layout_item_get_position           (PanelLayoutItem     *self);
+PANEL_AVAILABLE_IN_ALL
+void             panel_layout_item_set_position           (PanelLayoutItem     *self,
+                                                           PanelPosition       *position);
+PANEL_AVAILABLE_IN_ALL
+const char      *panel_layout_item_get_id                 (PanelLayoutItem     *self);
+PANEL_AVAILABLE_IN_ALL
+void             panel_layout_item_set_id                 (PanelLayoutItem     *self,
+                                                           const char          *id);
+PANEL_AVAILABLE_IN_ALL
+const char      *panel_layout_item_get_type_hint          (PanelLayoutItem     *self);
+PANEL_AVAILABLE_IN_ALL
+void             panel_layout_item_set_type_hint          (PanelLayoutItem     *self,
+                                                           const char          *type_hint);
+PANEL_AVAILABLE_IN_ALL
+gboolean         panel_layout_item_has_metadata           (PanelLayoutItem     *self,
+                                                           const char          *key,
+                                                           const GVariantType **value_type);
+PANEL_AVAILABLE_IN_ALL
+gboolean         panel_layout_item_has_metadata_with_type (PanelLayoutItem     *self,
+                                                           const char          *key,
+                                                           const GVariantType  *expected_type);
+PANEL_AVAILABLE_IN_ALL
+void             panel_layout_item_get_metadata           (PanelLayoutItem     *self,
+                                                           const char          *key,
+                                                           const char          *format,
+                                                           ...);
+PANEL_AVAILABLE_IN_ALL
+void             panel_layout_item_set_metadata           (PanelLayoutItem     *self,
+                                                           const char          *key,
+                                                           const char          *format,
+                                                           ...);
+PANEL_AVAILABLE_IN_ALL
+GVariant        *panel_layout_item_get_metadata_value     (PanelLayoutItem     *self,
+                                                           const char          *key,
+                                                           const GVariantType  *expected_type);
+PANEL_AVAILABLE_IN_ALL
+void             panel_layout_item_set_metadata_value     (PanelLayoutItem     *self,
+                                                           const char          *key,
+                                                           GVariant            *value);
+
+G_END_DECLS
diff --git a/src/panel-layout-private.h b/src/panel-layout-private.h
new file mode 100644
index 0000000..01345db
--- /dev/null
+++ b/src/panel-layout-private.h
@@ -0,0 +1,29 @@
+/* panel-layout-private.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "panel-layout.h"
+
+G_BEGIN_DECLS
+
+
+
+G_END_DECLS
diff --git a/src/panel-layout.c b/src/panel-layout.c
new file mode 100644
index 0000000..3c19f3a
--- /dev/null
+++ b/src/panel-layout.c
@@ -0,0 +1,276 @@
+/* panel-layout.c
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "config.h"
+
+#include "panel-layout.h"
+#include "panel-layout-item-private.h"
+#include "panel-position-private.h"
+
+struct _PanelLayout
+{
+  GObject    parent_instance;
+  GPtrArray *items;
+};
+
+G_DEFINE_FINAL_TYPE (PanelLayout, panel_layout, G_TYPE_OBJECT)
+
+static void
+panel_layout_dispose (GObject *object)
+{
+  PanelLayout *self = (PanelLayout *)object;
+
+  g_clear_pointer (&self->items, g_ptr_array_unref);
+
+  G_OBJECT_CLASS (panel_layout_parent_class)->dispose (object);
+}
+
+static void
+panel_layout_class_init (PanelLayoutClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = panel_layout_dispose;
+}
+
+static void
+panel_layout_init (PanelLayout *self)
+{
+  self->items = g_ptr_array_new_with_free_func (g_object_unref);
+}
+
+/**
+ * panel_layout_to_variant:
+ * @self: a #PanelLayout
+ *
+ * Serializes a #PanelLayout as a #GVariant
+ *
+ * The result of this function may be passed to
+ * panel_layout_new_from_variant() to recreate a #PanelLayout.
+ *
+ * Returns: (transfer full): a #GVariant
+ */
+GVariant *
+panel_layout_to_variant (PanelLayout *self)
+{
+  GVariantBuilder builder;
+
+  g_return_val_if_fail (PANEL_IS_LAYOUT (self), NULL);
+
+  g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
+    g_variant_builder_add_parsed (&builder, "{'version',<%u>}", 1);
+    g_variant_builder_open (&builder, G_VARIANT_TYPE ("{sv}"));
+      g_variant_builder_add (&builder, "s", "items");
+      g_variant_builder_open (&builder, G_VARIANT_TYPE ("v"));
+        g_variant_builder_open (&builder, G_VARIANT_TYPE ("av"));
+        for (guint i = 0; i < self->items->len; i++)
+          {
+            PanelLayoutItem *item = g_ptr_array_index (self->items, i);
+
+            _panel_layout_item_to_variant (item, &builder);
+          }
+        g_variant_builder_close (&builder);
+      g_variant_builder_close (&builder);
+    g_variant_builder_close (&builder);
+  return g_variant_builder_end (&builder);
+}
+
+static gboolean
+panel_layout_load_1 (PanelLayout  *self,
+                     GVariant     *variant,
+                     GError      **error)
+{
+  g_autoptr(GVariant) items = NULL;
+
+  g_assert (PANEL_IS_LAYOUT (self));
+  g_assert (variant != NULL);
+  g_assert (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARDICT));
+
+  if ((items = g_variant_lookup_value (variant, "items", G_VARIANT_TYPE ("av"))))
+    {
+      gsize n_children = g_variant_n_children (items);
+
+      for (gsize i = 0; i < n_children; i++)
+        {
+          g_autoptr(GVariant) itemv = g_variant_get_child_value (items, i);
+          g_autoptr(GVariant) infov = g_variant_get_variant (itemv);
+          PanelLayoutItem *item = _panel_layout_item_new_from_variant (infov, error);
+
+          if (item == NULL)
+            return FALSE;
+
+          g_ptr_array_add (self->items, g_steal_pointer (&item));
+        }
+
+      return TRUE;
+    }
+  else
+    {
+      g_set_error_literal (error,
+                           G_IO_ERROR,
+                           G_IO_ERROR_INVALID_DATA,
+                           "items missing from variant");
+      return FALSE;
+    }
+}
+
+static gboolean
+panel_layout_load (PanelLayout  *self,
+                   GVariant     *variant,
+                   GError      **error)
+{
+  guint version = 0;
+
+  g_assert (PANEL_IS_LAYOUT (self));
+  g_assert (variant != NULL);
+  g_assert (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARDICT));
+
+  if (g_variant_lookup (variant, "version", "u", &version))
+    {
+      if (version == 1)
+        return panel_layout_load_1 (self, variant, error);
+    }
+
+  g_set_error_literal (error,
+                       G_IO_ERROR,
+                       G_IO_ERROR_INVALID_DATA,
+                       "Invalid version number in serialized layout");
+
+  return FALSE;
+}
+
+/**
+ * panel_layout_new_from_variant:
+ * @variant: a #GVariant from panel_layout_to_variant()
+ * @error: a location for a #GError, or %NULL
+ *
+ * Creates a new #PanelLayout from a #GVariant.
+ *
+ * This creates a new #PanelLayout instance from a previous layout
+ * which had been serialized to @variant.
+ *
+ * Returns: (transfer full): a #PanelLayout
+ */
+PanelLayout *
+panel_layout_new_from_variant (GVariant  *variant,
+                               GError   **error)
+{
+  PanelLayout *self;
+
+  g_return_val_if_fail (variant != NULL, NULL);
+  g_return_val_if_fail (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARDICT), NULL);
+
+  self = g_object_new (PANEL_TYPE_LAYOUT, NULL);
+
+  if (!panel_layout_load (self, variant, error))
+    g_clear_object (&self);
+
+  return self;
+}
+
+PanelLayout *
+panel_layout_new (void)
+{
+  return g_object_new (PANEL_TYPE_LAYOUT, NULL);
+}
+
+guint
+panel_layout_get_n_items (PanelLayout *self)
+{
+  g_return_val_if_fail (PANEL_IS_LAYOUT (self), 0);
+
+  return self->items->len;
+}
+
+/**
+ * panel_layout_get_item:
+ * @self: a #PanelLayout
+ * @position: the index of the item
+ *
+ * Gets the item at @position.
+ *
+ * Returns: (transfer none) (nullable): The #PanelLayoutItem at @position
+ *   or %NULL if there is no item at that position.
+ */
+PanelLayoutItem *
+panel_layout_get_item (PanelLayout *self,
+                       guint        position)
+{
+  g_return_val_if_fail (PANEL_IS_LAYOUT (self), NULL);
+
+  if (position >= self->items->len)
+    return NULL;
+
+  return g_ptr_array_index (self->items, position);
+}
+
+void
+panel_layout_remove (PanelLayout     *self,
+                     PanelLayoutItem *item)
+{
+  guint position;
+
+  g_return_if_fail (PANEL_IS_LAYOUT (self));
+  g_return_if_fail (PANEL_IS_LAYOUT_ITEM (item));
+
+  if (g_ptr_array_find (self->items, item, &position))
+    panel_layout_remove_at (self, position);
+}
+
+void
+panel_layout_remove_at (PanelLayout *self,
+                        guint        position)
+{
+  g_return_if_fail (PANEL_IS_LAYOUT (self));
+  g_return_if_fail (position < self->items->len);
+
+  g_ptr_array_remove_index (self->items, position);
+}
+
+void
+panel_layout_append (PanelLayout     *self,
+                     PanelLayoutItem *item)
+{
+  g_return_if_fail (PANEL_IS_LAYOUT (self));
+  g_return_if_fail (PANEL_IS_LAYOUT_ITEM (item));
+
+  g_ptr_array_add (self->items, g_object_ref (item));
+}
+
+void
+panel_layout_prepend (PanelLayout     *self,
+                      PanelLayoutItem *item)
+{
+  g_return_if_fail (PANEL_IS_LAYOUT (self));
+  g_return_if_fail (PANEL_IS_LAYOUT_ITEM (item));
+
+  g_ptr_array_insert (self->items, 0, g_object_ref (item));
+}
+
+void
+panel_layout_insert (PanelLayout     *self,
+                     guint            position,
+                     PanelLayoutItem *item)
+{
+  g_return_if_fail (PANEL_IS_LAYOUT (self));
+  g_return_if_fail (PANEL_IS_LAYOUT_ITEM (item));
+
+  g_ptr_array_insert (self->items, position, g_object_ref (item));
+}
diff --git a/src/panel-layout.h b/src/panel-layout.h
new file mode 100644
index 0000000..aa996fe
--- /dev/null
+++ b/src/panel-layout.h
@@ -0,0 +1,64 @@
+/* panel-layout.h
+ *
+ * Copyright 2022 Christian Hergert <chergert redhat com>
+ *
+ * This file is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+#include "panel-layout-item.h"
+#include "panel-version-macros.h"
+
+G_BEGIN_DECLS
+
+#define PANEL_TYPE_LAYOUT (panel_layout_get_type())
+
+PANEL_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (PanelLayout, panel_layout, PANEL, LAYOUT, GObject)
+
+PANEL_AVAILABLE_IN_ALL
+PanelLayout     *panel_layout_new              (void);
+PANEL_AVAILABLE_IN_ALL
+void             panel_layout_append           (PanelLayout      *self,
+                                                PanelLayoutItem  *item);
+PANEL_AVAILABLE_IN_ALL
+void             panel_layout_prepend          (PanelLayout      *self,
+                                                PanelLayoutItem  *item);
+PANEL_AVAILABLE_IN_ALL
+void             panel_layout_insert           (PanelLayout      *self,
+                                                guint             position,
+                                                PanelLayoutItem  *item);
+PANEL_AVAILABLE_IN_ALL
+void             panel_layout_remove           (PanelLayout      *self,
+                                                PanelLayoutItem  *item);
+PANEL_AVAILABLE_IN_ALL
+void             panel_layout_remove_at        (PanelLayout      *self,
+                                                guint             position);
+PANEL_AVAILABLE_IN_ALL
+guint            panel_layout_get_n_items      (PanelLayout      *self);
+PANEL_AVAILABLE_IN_ALL
+PanelLayoutItem *panel_layout_get_item         (PanelLayout      *self,
+                                                guint             position);
+PANEL_AVAILABLE_IN_ALL
+PanelLayout     *panel_layout_new_from_variant (GVariant         *variant,
+                                                GError          **error);
+PANEL_AVAILABLE_IN_ALL
+GVariant        *panel_layout_to_variant       (PanelLayout      *self);
+
+G_END_DECLS
diff --git a/testsuite/meson.build b/testsuite/meson.build
index 651c113..b6173ec 100644
--- a/testsuite/meson.build
+++ b/testsuite/meson.build
@@ -1,2 +1,7 @@
-executable('test-dock', 'test-dock.c', dependencies: [libpanel_static_dep])
-executable('test-paned', 'test-paned.c', dependencies: [libpanel_static_dep])
+test_dock = executable('test-dock', 'test-dock.c', dependencies: [libpanel_static_dep])
+test_paned = executable('test-paned', 'test-paned.c', dependencies: [libpanel_static_dep])
+
+test_layout = executable('test-layout', 'test-layout.c', dependencies: [libpanel_static_dep])
+test('test-layout', test_layout)
+
+
diff --git a/testsuite/test-layout.c b/testsuite/test-layout.c
new file mode 100644
index 0000000..74ab17d
--- /dev/null
+++ b/testsuite/test-layout.c
@@ -0,0 +1,89 @@
+#include <libpanel.h>
+
+#include "panel-layout-private.h"
+
+static gboolean
+position_equal (PanelPosition *a,
+                PanelPosition *b)
+{
+  return panel_position_get_area_set (a) == panel_position_get_area_set (b) &&
+         panel_position_get_column_set (a) == panel_position_get_column_set (b) &&
+         panel_position_get_row_set (a) == panel_position_get_row_set (b) &&
+         panel_position_get_depth_set (a) == panel_position_get_depth_set (b) &&
+         panel_position_get_area (a) == panel_position_get_area (b) &&
+         panel_position_get_column (a) == panel_position_get_column (b) &&
+         panel_position_get_row (a) == panel_position_get_row (b) &&
+         panel_position_get_depth (a) == panel_position_get_depth (b);
+}
+
+static void
+test_layout (void)
+{
+  PanelLayoutItem *item1, *alt_item1;
+  PanelPosition *position1, *alt_position1;
+  PanelLayout *layout;
+  PanelLayout *recreated;
+  GVariant *variant;
+  GError *error = NULL;
+
+  layout = g_object_new (PANEL_TYPE_LAYOUT, NULL);
+
+  position1 = panel_position_new ();
+  panel_position_set_area (position1, PANEL_AREA_CENTER);
+  panel_position_set_column (position1, 1);
+  panel_position_set_row (position1, 2);
+  panel_position_set_depth (position1, 3);
+
+  item1 = panel_layout_item_new ();
+  panel_layout_item_set_id (item1, "item-1");
+  panel_layout_item_set_type_hint (item1, "Item1");
+  panel_layout_item_set_metadata (item1, "Item1:Key", "s", "Item1:Value");
+  panel_layout_item_set_position (item1, position1);
+  panel_layout_append (layout, item1);
+
+  variant = panel_layout_to_variant (layout);
+  g_assert_nonnull (variant);
+  g_assert_true (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARDICT));
+
+  recreated = panel_layout_new_from_variant (variant, &error);
+  g_assert_no_error (error);
+  g_assert_true (PANEL_IS_LAYOUT (recreated));
+
+  g_assert_cmpint (panel_layout_get_n_items (recreated), >, 0);
+  g_assert_cmpint (panel_layout_get_n_items (recreated), ==, panel_layout_get_n_items (layout));
+
+  alt_item1 = panel_layout_get_item (recreated, 0);
+
+  g_assert_nonnull (alt_item1);
+  g_assert_cmpstr (panel_layout_item_get_id (item1),
+                   ==,
+                   panel_layout_item_get_id (alt_item1));
+  g_assert_cmpstr (panel_layout_item_get_type_hint (item1),
+                   ==,
+                   panel_layout_item_get_type_hint  (alt_item1));
+
+  alt_position1 = panel_layout_item_get_position (alt_item1);
+  g_assert_nonnull (alt_position1);
+  g_assert_true (position_equal (position1, alt_position1));
+
+  g_assert_finalize_object (g_steal_pointer (&layout));
+  g_assert_finalize_object (g_steal_pointer (&recreated));
+  g_assert_finalize_object (g_steal_pointer (&item1));
+  g_assert_finalize_object (g_steal_pointer (&position1));
+  g_clear_pointer (&variant, g_variant_unref);
+
+  g_assert_null (error);
+  g_assert_null (layout);
+  g_assert_null (recreated);
+  g_assert_null (variant);
+}
+
+int
+main (int argc,
+      char *argv[])
+{
+  gtk_init ();
+  g_test_init (&argc, &argv, NULL);
+  g_test_add_func ("/Layout/basic", test_layout);
+  return g_test_run ();
+}


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