widget: Add state saving plumbing

Add ::save-state and ::restore signals that let widgets
save and restore their state to a GVariant.
This commit is contained in:
Matthias Clasen
2021-06-02 21:46:42 -04:00
parent a1789ee2b8
commit 8db1b89ace
3 changed files with 253 additions and 2 deletions

View File

@@ -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);
}

View File

@@ -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__ */

View File

@@ -198,6 +198,9 @@ struct _GtkWidgetPrivate
/* Accessibility */
GtkAccessibleRole accessible_role;
GtkATContext *at_context;
/* State saving */
char *save_id;
};
typedef struct