[gtk/state-saving: 1/14] widget: Add state saving plumbing




commit 8db1b89ace5adffbff7a66df0c25b7ba146eaf24
Author: Matthias Clasen <mclasen redhat com>
Date:   Wed Jun 2 21:46:42 2021 -0400

    widget: Add state saving plumbing
    
    Add ::save-state and ::restore signals that let widgets
    save and restore their state to a GVariant.

 gtk/gtkwidget.c        | 228 ++++++++++++++++++++++++++++++++++++++++++++++++-
 gtk/gtkwidget.h        |  24 +++++-
 gtk/gtkwidgetprivate.h |   3 +
 3 files changed, 253 insertions(+), 2 deletions(-)
---
diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c
index 459aeba2fe..a6b4071514 100644
--- a/gtk/gtkwidget.c
+++ b/gtk/gtkwidget.c
@@ -479,6 +479,8 @@ enum {
   MOVE_FOCUS,
   KEYNAV_FAILED,
   QUERY_TOOLTIP,
+  SAVE_STATE,
+  RESTORE_STATE,
   LAST_SIGNAL
 };
 
@@ -518,6 +520,7 @@ enum {
   PROP_CSS_NAME,
   PROP_CSS_CLASSES,
   PROP_LAYOUT_MANAGER,
+  PROP_SAVE_ID,
   NUM_PROPERTIES,
 
   /* GtkAccessible */
@@ -996,6 +999,9 @@ gtk_widget_set_property (GObject      *object,
     case PROP_ACCESSIBLE_ROLE:
       gtk_widget_set_accessible_role (widget, g_value_get_enum (value));
       break;
+    case PROP_SAVE_ID:
+      gtk_widget_set_save_id (widget, g_value_get_string (value));
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -1129,6 +1135,9 @@ gtk_widget_get_property (GObject    *object,
     case PROP_ACCESSIBLE_ROLE:
       g_value_set_enum (value, gtk_widget_get_accessible_role (widget));
       break;
+    case PROP_SAVE_ID:
+      g_value_set_string (value, priv->save_id);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -1672,6 +1681,21 @@ gtk_widget_class_init (GtkWidgetClass *klass)
                          GTK_TYPE_LAYOUT_MANAGER,
                          GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
 
+  /**
+   * GtkWidget:save-id: (attributes org.gtk.Property.get=gtk_widget_get_save_id 
org.gtk.Property.set=gtk_widget_set_save_id)
+   *
+   * The ID under which persistent state of this widget is saved.
+   *
+   * In order for a widget to have its state saved (and restored), the widget
+   * and all its ancestors must have a `save-id`.
+   */
+  widget_props[PROP_SAVE_ID] =
+      g_param_spec_string ("save-id",
+                           P_("Save ID"),
+                           P_("The ID to save the widget state under"),
+                           NULL,
+                           GTK_PARAM_READWRITE);
+
   g_object_class_install_properties (gobject_class, NUM_PROPERTIES, widget_props);
 
   g_object_class_override_property (gobject_class, PROP_ACCESSIBLE_ROLE, "accessible-role");
@@ -1955,6 +1979,53 @@ gtk_widget_class_init (GtkWidgetClass *klass)
                               G_TYPE_FROM_CLASS (klass),
                               _gtk_marshal_BOOLEAN__INT_INT_BOOLEAN_OBJECTv);
 
+  /**
+   * GtkWidget:save-state:
+   * @widget: the object which received the signal
+   * @dict: a `GVariantDict`
+   * @save_children: (out): return location for whether to save children
+   *
+   * The handler for this signal should persist any state of @widget
+   * into @dict, and set @save_children if the child widgets may have
+   * state worth saving too.
+   *
+   * See [signal@Gtk.Widget:restore-state].
+   *
+   * Returns: %TRUE to stop stop further handlers from running
+   */
+  widget_signals[SAVE_STATE] =
+    g_signal_new (I_("save-state"),
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (GtkWidgetClass, save_state),
+                  _gtk_boolean_handled_accumulator, NULL,
+                  NULL,
+                  G_TYPE_BOOLEAN, 2,
+                  G_TYPE_VARIANT_DICT,
+                  G_TYPE_POINTER);
+
+  /**
+   * GtkWidget:restore-state:
+   * @widget: the object which received the signal
+   * @state: an "a{sv}" `GVariant` with state to restore
+   *
+   * The handler for this signal should do the opposite of what the
+   * corresponding handler for [signal@Gtk.Widget:save-state] does.
+   *
+   * See [signal@Gtk.Widget:save-state].
+   *
+   * Returns: %TRUE to stop stop further handlers from running
+   */
+  widget_signals[RESTORE_STATE] =
+    g_signal_new (I_("restore-state"),
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (GtkWidgetClass, restore_state),
+                  _gtk_boolean_handled_accumulator, NULL,
+                  NULL,
+                  G_TYPE_BOOLEAN, 1,
+                  G_TYPE_VARIANT);
+
   gtk_widget_class_set_css_name (klass, I_("widget"));
   gtk_widget_class_set_accessible_role (klass, GTK_ACCESSIBLE_ROLE_WIDGET);
 }
@@ -2034,7 +2105,6 @@ _gtk_widget_emulate_press (GtkWidget      *widget,
                                    NULL,
                                    gdk_touch_event_get_emulating_pointer (event));
       break;
-    case GDK_BUTTON_PRESS:
     case GDK_BUTTON_RELEASE:
       press = gdk_button_event_new (GDK_BUTTON_PRESS,
                                     gdk_event_get_surface (event),
@@ -7576,6 +7646,7 @@ gtk_widget_finalize (GObject *object)
   g_free (priv->name);
   g_free (priv->tooltip_markup);
   g_free (priv->tooltip_text);
+  g_free (priv->save_id);
 
   g_clear_pointer (&priv->transform, gsk_transform_unref);
   g_clear_pointer (&priv->allocated_transform, gsk_transform_unref);
@@ -12928,3 +12999,158 @@ gtk_widget_set_active_state (GtkWidget *widget,
         gtk_widget_unset_state_flags (widget, GTK_STATE_FLAG_ACTIVE);
     }
 }
+
+void
+gtk_widget_set_save_id (GtkWidget  *widget,
+                        const char *id)
+{
+  GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget);
+
+  g_free (priv->save_id);
+  priv->save_id = g_strdup (id);
+
+  g_object_notify_by_pspec (G_OBJECT (widget), widget_props[PROP_SAVE_ID]);
+}
+
+const char *
+gtk_widget_get_save_id (GtkWidget *widget)
+{
+  GtkWidgetPrivate *priv = gtk_widget_get_instance_private (widget);
+
+  return priv->save_id;
+}
+
+static void
+gtk_widget_collect_child_state (GtkWidget    *widget,
+                                GVariantDict *dict)
+{
+  const char *id;
+  GVariant *state;
+
+  id = gtk_widget_get_save_id (widget);
+  if (!id)
+    return;
+
+  if (g_variant_dict_contains (dict, id))
+    {
+      g_warning ("Duplicate save-id %s", id);
+      return;
+    }
+
+  state = gtk_widget_save_state (widget);
+
+  if (state)
+    g_variant_dict_insert_value (dict, id, state);
+}
+
+/**
+ * gtk_widget_save_state:
+ * @widget: a `GtkWidget`
+ *
+ * Save the state of @widget and its children to a `GVariant`.
+ *
+ * In order a widgets state to be saved by this, the widget and
+ * all its ancestors must have a [property@Gtk.Widget:save-id].
+ *
+ * See [signal@Gtk.Widget::save-state] for how to override what
+ * state is saved.
+ *
+ * This function is used by `GtkApplication` to implement automatic
+ * state saving. It is recommended that you use that functionality.
+ *
+ * Returns: (transfer full) (nullable): A `GVariant` with the saved state
+ *
+ * Since: 4.4
+ */
+GVariant *
+gtk_widget_save_state (GtkWidget *widget)
+{
+  const char *id;
+  GVariantDict *dict;
+  GVariantBuilder data;
+  gboolean save_children = TRUE;
+  gboolean ret;
+  GVariant *v;
+
+  id = gtk_widget_get_save_id (widget);
+  if (id == NULL)
+    return NULL;
+
+  dict = g_variant_dict_new (NULL);
+
+  g_signal_emit (widget, widget_signals[SAVE_STATE], 0, dict, &save_children, &ret);
+
+  g_variant_builder_init (&data, G_VARIANT_TYPE_VARDICT);
+
+  v = g_variant_dict_end (dict);
+  if (g_variant_n_children (v) > 0)
+    g_variant_builder_add (&data, "{sv}", "data", v);
+  else
+    g_variant_unref (v);
+
+  if (save_children)
+    {
+      g_variant_dict_init (dict, NULL);
+      gtk_widget_forall (widget, (GtkCallback) gtk_widget_collect_child_state, dict);
+      v = g_variant_dict_end (dict);
+      if (g_variant_n_children (v) > 0)
+        g_variant_builder_add (&data, "{sv}", "children", v);
+      else
+        g_variant_unref (v);
+    }
+
+  g_variant_dict_unref (dict);
+
+  v = g_variant_builder_end (&data);
+  if (g_variant_n_children (v) > 0)
+    return v;
+
+  g_variant_unref (v);
+  return NULL;
+}
+
+static void
+gtk_widget_restore_child_state (GtkWidget *widget,
+                                GVariant  *data)
+{
+  const char *id;
+  GVariant *v;
+
+  id = gtk_widget_get_save_id (widget);
+  if (!id)
+    return;
+
+  v = g_variant_lookup_value (data, id, G_VARIANT_TYPE_VARDICT);
+  if (v)
+    gtk_widget_restore_state (widget, v);
+}
+
+/**
+ * gtk_widget_restore_state:
+ * @widget: a `GtkWidget`
+ * @state: an "a{sv}" `GVariant` as returned by gtk_widget_save_state()
+ *
+ * Restores state of @widget and its children.
+ *
+ * See [method@Gtk.Widget.save_state] for how to save state.
+ *
+ * This function is used by `GtkApplication` to implement automatic
+ * state restoration. It is recommended that you use that functionality.
+ *
+ * Since: 4.4
+ */
+void
+gtk_widget_restore_state (GtkWidget *widget,
+                          GVariant  *state)
+{
+  GVariant *data;
+  gboolean ret;
+
+  data = g_variant_lookup_value (state, "data", G_VARIANT_TYPE_VARDICT);
+  if (data)
+    g_signal_emit (widget, widget_signals[RESTORE_STATE], 0, data, &ret);
+
+  data = g_variant_lookup_value (state, "children", G_VARIANT_TYPE_VARDICT);
+  if (data)
+    gtk_widget_forall (widget, (GtkCallback) gtk_widget_restore_child_state, data);
+}
diff --git a/gtk/gtkwidget.h b/gtk/gtkwidget.h
index fd1cc031f5..2e2dfc8881 100644
--- a/gtk/gtkwidget.h
+++ b/gtk/gtkwidget.h
@@ -257,11 +257,18 @@ struct _GtkWidgetClass
                                                 double     x,
                                                 double     y);
 
+  gboolean     (* save_state)                  (GtkWidget    *widget,
+                                                GVariantDict *dict,
+                                                gboolean     *save_children);
+
+  gboolean     (* restore_state)               (GtkWidget    *widget,
+                                                GVariant     *data);
+
   /*< private >*/
 
   GtkWidgetClassPrivate *priv;
 
-  gpointer padding[8];
+  gpointer padding[6];
 };
 
 
@@ -977,6 +984,21 @@ GtkAccessibleRole       gtk_widget_class_get_accessible_role    (GtkWidgetClass
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkWidget, g_object_unref)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkRequisition, gtk_requisition_free)
 
+
+GDK_AVAILABLE_IN_4_4
+void                    gtk_widget_set_save_id   (GtkWidget  *widget,
+                                                  const char *id);
+
+GDK_AVAILABLE_IN_4_4
+const char *            gtk_widget_get_save_id   (GtkWidget  *widget);
+
+GDK_AVAILABLE_IN_4_4
+GVariant *              gtk_widget_save_state    (GtkWidget  *widget);
+
+GDK_AVAILABLE_IN_4_4
+void                    gtk_widget_restore_state (GtkWidget  *widget,
+                                                  GVariant   *state);
+
 G_END_DECLS
 
 #endif /* __GTK_WIDGET_H__ */
diff --git a/gtk/gtkwidgetprivate.h b/gtk/gtkwidgetprivate.h
index bc6eb0bf7f..f831fae611 100644
--- a/gtk/gtkwidgetprivate.h
+++ b/gtk/gtkwidgetprivate.h
@@ -198,6 +198,9 @@ struct _GtkWidgetPrivate
   /* Accessibility */
   GtkAccessibleRole accessible_role;
   GtkATContext *at_context;
+
+  /* State saving */
+  char *save_id;
 };
 
 typedef struct


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