Compare commits
15 Commits
css-variab
...
state-savi
Author | SHA1 | Date | |
---|---|---|---|
|
e9ce1959b4 | ||
|
a7edd0992e | ||
|
9f35250330 | ||
|
7e8dfd5bda | ||
|
aa0df60606 | ||
|
9ee07d3974 | ||
|
1f7ca109c9 | ||
|
bc2d8bfa33 | ||
|
ee0e82610c | ||
|
26c5240dcb | ||
|
ecf6c354b5 | ||
|
fd499188bd | ||
|
0c6fb610e6 | ||
|
1962f535e8 | ||
|
8db1b89ace |
@@ -59,12 +59,14 @@ example_app_startup (GApplication *app)
|
||||
}
|
||||
|
||||
static void
|
||||
example_app_activate (GApplication *app)
|
||||
example_app_create_window (GtkApplication *app,
|
||||
const char *save_id)
|
||||
{
|
||||
ExampleAppWindow *win;
|
||||
|
||||
win = example_app_window_new (EXAMPLE_APP (app));
|
||||
gtk_window_present (GTK_WINDOW (win));
|
||||
gtk_widget_set_save_id (GTK_WIDGET (win), save_id);
|
||||
/* FIXME: differentiate save ids */
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -93,8 +95,8 @@ static void
|
||||
example_app_class_init (ExampleAppClass *class)
|
||||
{
|
||||
G_APPLICATION_CLASS (class)->startup = example_app_startup;
|
||||
G_APPLICATION_CLASS (class)->activate = example_app_activate;
|
||||
G_APPLICATION_CLASS (class)->open = example_app_open;
|
||||
GTK_APPLICATION_CLASS (class)->create_window = example_app_create_window;
|
||||
}
|
||||
|
||||
ExampleApp *
|
||||
@@ -103,5 +105,7 @@ example_app_new (void)
|
||||
return g_object_new (EXAMPLE_APP_TYPE,
|
||||
"application-id", "org.gtk.exampleapp",
|
||||
"flags", G_APPLICATION_HANDLES_OPEN,
|
||||
"register-session", TRUE,
|
||||
"save-state", TRUE,
|
||||
NULL);
|
||||
}
|
||||
|
@@ -17,6 +17,8 @@ struct _ExampleAppWindow
|
||||
GtkWidget *words;
|
||||
GtkWidget *lines;
|
||||
GtkWidget *lines_label;
|
||||
|
||||
GList *files;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (ExampleAppWindow, example_app_window, GTK_TYPE_APPLICATION_WINDOW)
|
||||
@@ -211,14 +213,68 @@ example_app_window_dispose (GObject *object)
|
||||
|
||||
g_clear_object (&win->settings);
|
||||
|
||||
g_list_free_full (win->files, g_object_unref);
|
||||
win->files = NULL;
|
||||
|
||||
G_OBJECT_CLASS (example_app_window_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
example_app_window_save_state (GtkWidget *widget,
|
||||
GVariantDict *dict,
|
||||
gboolean *save_children)
|
||||
{
|
||||
ExampleAppWindow *win = EXAMPLE_APP_WINDOW (widget);
|
||||
GVariantBuilder builder;
|
||||
|
||||
g_variant_builder_init (&builder, G_VARIANT_TYPE ("as"));
|
||||
for (GList *l = win->files; l; l = l->next)
|
||||
{
|
||||
g_variant_builder_add (&builder, "s", g_file_peek_path (G_FILE (l->data)));
|
||||
}
|
||||
|
||||
g_variant_dict_insert_value (dict, "files", g_variant_builder_end (&builder));
|
||||
|
||||
/* Save window state */
|
||||
GTK_WIDGET_CLASS (example_app_window_parent_class)->save_state (widget, dict, save_children);
|
||||
|
||||
*save_children = TRUE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
example_app_window_restore_state (GtkWidget *widget,
|
||||
GVariant *state)
|
||||
{
|
||||
ExampleAppWindow *win = EXAMPLE_APP_WINDOW (widget);
|
||||
GVariantIter *iter;
|
||||
|
||||
/* Restore window state */
|
||||
GTK_WIDGET_CLASS (example_app_window_parent_class)->restore_state (widget, state);
|
||||
|
||||
if (g_variant_lookup (state, "files", "as", &iter))
|
||||
{
|
||||
const char *path;
|
||||
|
||||
while (g_variant_iter_next (iter, "&s", &path))
|
||||
{
|
||||
GFile *file = g_file_new_for_path (path);
|
||||
example_app_window_open (win, file);
|
||||
g_object_unref (file);
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
example_app_window_class_init (ExampleAppWindowClass *class)
|
||||
{
|
||||
G_OBJECT_CLASS (class)->dispose = example_app_window_dispose;
|
||||
|
||||
GTK_WIDGET_CLASS (class)->save_state = example_app_window_save_state;
|
||||
GTK_WIDGET_CLASS (class)->restore_state = example_app_window_restore_state;
|
||||
|
||||
gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class),
|
||||
"/org/gtk/exampleapp/window.ui");
|
||||
|
||||
@@ -288,4 +344,6 @@ example_app_window_open (ExampleAppWindow *win,
|
||||
|
||||
update_words (win);
|
||||
update_lines (win);
|
||||
|
||||
win->files = g_list_append (win->files, g_object_ref (file));
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@
|
||||
<property name="title" translatable="yes">Example Application</property>
|
||||
<property name="default-width">600</property>
|
||||
<property name="default-height">400</property>
|
||||
<property name="save-id">window</property>
|
||||
<child type="titlebar">
|
||||
<object class="GtkHeaderBar" id="header">
|
||||
<child>
|
||||
@@ -26,6 +27,7 @@
|
||||
<object class="GtkToggleButton" id="search">
|
||||
<property name="sensitive">0</property>
|
||||
<property name="icon-name">edit-find-symbolic</property>
|
||||
<property name="save-id">search</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="end">
|
||||
@@ -38,6 +40,7 @@
|
||||
<child>
|
||||
<object class="GtkBox" id="content_box">
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="save-id">content_box</property>
|
||||
<child>
|
||||
<object class="GtkSearchBar" id="searchbar">
|
||||
<child>
|
||||
@@ -49,8 +52,10 @@
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="hbox">
|
||||
<property name="save-id">hbox</property>
|
||||
<child>
|
||||
<object class="GtkRevealer" id="sidebar">
|
||||
<property name="save-id">sidebar</property>
|
||||
<property name="transition-type">slide-right</property>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="sidebar-sw">
|
||||
@@ -66,6 +71,7 @@
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStack" id="stack">
|
||||
<property name="save-id">stack</property>
|
||||
<signal name="notify::visible-child" handler="visible_child_changed"/>
|
||||
</object>
|
||||
</child>
|
||||
|
@@ -27,6 +27,8 @@
|
||||
#include <gio/gunixfdlist.h>
|
||||
#endif
|
||||
|
||||
#include <glib/gstdio.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef HAVE_UNISTD_H
|
||||
@@ -119,6 +121,7 @@ enum {
|
||||
WINDOW_ADDED,
|
||||
WINDOW_REMOVED,
|
||||
QUERY_END,
|
||||
CREATE_WINDOW,
|
||||
LAST_SIGNAL
|
||||
};
|
||||
|
||||
@@ -130,6 +133,7 @@ enum {
|
||||
PROP_SCREENSAVER_ACTIVE,
|
||||
PROP_MENUBAR,
|
||||
PROP_ACTIVE_WINDOW,
|
||||
PROP_SAVE_STATE,
|
||||
NUM_PROPERTIES
|
||||
};
|
||||
|
||||
@@ -147,6 +151,7 @@ typedef struct
|
||||
|
||||
gboolean register_session;
|
||||
gboolean screensaver_active;
|
||||
gboolean save_state;
|
||||
GtkActionMuxer *muxer;
|
||||
GtkBuilder *menus_builder;
|
||||
char *help_overlay_path;
|
||||
@@ -270,6 +275,26 @@ gtk_application_startup (GApplication *g_application)
|
||||
gdk_profiler_end_mark (before, "gtk application startup", NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_application_activate (GApplication *g_application)
|
||||
{
|
||||
GtkApplication *application = GTK_APPLICATION (g_application);
|
||||
GtkApplicationPrivate *priv = gtk_application_get_instance_private (application);
|
||||
guint activate_id;
|
||||
|
||||
activate_id = g_signal_lookup ("activate", G_TYPE_APPLICATION);
|
||||
if (!g_signal_has_handler_pending (g_application, activate_id, 0, TRUE))
|
||||
{
|
||||
if (!priv->save_state ||
|
||||
!gtk_application_restore (application))
|
||||
{
|
||||
g_signal_emit (application,
|
||||
gtk_application_signals[CREATE_WINDOW], 0, NULL);
|
||||
gtk_window_present (GTK_WINDOW (priv->windows->data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_application_shutdown (GApplication *g_application)
|
||||
{
|
||||
@@ -284,6 +309,9 @@ gtk_application_shutdown (GApplication *g_application)
|
||||
|
||||
gtk_action_muxer_remove (priv->muxer, "app");
|
||||
|
||||
if (priv->save_state)
|
||||
gtk_application_save (application);
|
||||
|
||||
gtk_main_sync ();
|
||||
|
||||
G_APPLICATION_CLASS (gtk_application_parent_class)->shutdown (g_application);
|
||||
@@ -451,6 +479,10 @@ gtk_application_get_property (GObject *object,
|
||||
g_value_set_object (value, gtk_application_get_active_window (application));
|
||||
break;
|
||||
|
||||
case PROP_SAVE_STATE:
|
||||
g_value_set_boolean (value, priv->save_state);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
@@ -476,6 +508,10 @@ gtk_application_set_property (GObject *object,
|
||||
gtk_application_set_menubar (application, g_value_get_object (value));
|
||||
break;
|
||||
|
||||
case PROP_SAVE_STATE:
|
||||
priv->save_state = g_value_get_boolean (value);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
@@ -530,6 +566,7 @@ gtk_application_class_init (GtkApplicationClass *class)
|
||||
application_class->after_emit = gtk_application_after_emit;
|
||||
application_class->startup = gtk_application_startup;
|
||||
application_class->shutdown = gtk_application_shutdown;
|
||||
application_class->activate = gtk_application_activate;
|
||||
application_class->dbus_register = gtk_application_dbus_register;
|
||||
application_class->dbus_unregister = gtk_application_dbus_unregister;
|
||||
|
||||
@@ -586,6 +623,33 @@ gtk_application_class_init (GtkApplicationClass *class)
|
||||
NULL,
|
||||
G_TYPE_NONE, 0);
|
||||
|
||||
/**
|
||||
* GtkApplication::create-window:
|
||||
* @application: the `GtkApplication` which emitted the signal
|
||||
* @save_id: (nullable): save-id to set on the new window
|
||||
*
|
||||
* In response to this signal, you should create a new application window,
|
||||
* and add it to @application.
|
||||
*
|
||||
*`GtkApplication` will call [method@Gtk.Window.present] on the window.
|
||||
|
||||
* If @save_id is passed, it should be set as the [property@Gtk.Widget:save-id]
|
||||
* of the newly created window.
|
||||
*
|
||||
* You should handle this signal instead of `::activate` to make automatic
|
||||
* session saving work. See [property@Gtk.Application:save-state] for more
|
||||
* information.
|
||||
*
|
||||
* Since: 4.4
|
||||
*/
|
||||
gtk_application_signals[CREATE_WINDOW] =
|
||||
g_signal_new (I_("create-window"), GTK_TYPE_APPLICATION, G_SIGNAL_RUN_FIRST,
|
||||
G_STRUCT_OFFSET (GtkApplicationClass, create_window),
|
||||
NULL, NULL,
|
||||
NULL,
|
||||
G_TYPE_NONE, 1,
|
||||
G_TYPE_STRING);
|
||||
|
||||
/**
|
||||
* GtkApplication:register-session:
|
||||
*
|
||||
@@ -644,6 +708,29 @@ gtk_application_class_init (GtkApplicationClass *class)
|
||||
GTK_TYPE_WINDOW,
|
||||
G_PARAM_READABLE|G_PARAM_STATIC_STRINGS);
|
||||
|
||||
/**
|
||||
* GtkApplication:save-state:
|
||||
*
|
||||
* Whether to save and restore the application state automatically when the
|
||||
* application is closed or the session ends.
|
||||
*
|
||||
* In order to make automatic state restoration work, you should handle
|
||||
* [signal@Gtk.Application::create-window]. The default `::activate` handler
|
||||
* will emit that signal as needed, and call [method@Gtk.Application.restore]
|
||||
* when it finds saved state.
|
||||
*
|
||||
* In order to make state saving work, widgets need to have a
|
||||
* [property@Gtk.Widget:save-id] set.
|
||||
*
|
||||
* Since: 4.4
|
||||
*/
|
||||
gtk_application_props[PROP_SAVE_STATE] =
|
||||
g_param_spec_boolean ("save-state",
|
||||
P_("Save state"),
|
||||
P_("Whether to save application state automatically"),
|
||||
FALSE,
|
||||
G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (object_class, NUM_PROPERTIES, gtk_application_props);
|
||||
}
|
||||
|
||||
@@ -1230,3 +1317,162 @@ gtk_application_set_screensaver_active (GtkApplication *application,
|
||||
g_object_notify (G_OBJECT (application), "screensaver-active");
|
||||
}
|
||||
}
|
||||
|
||||
static char *
|
||||
get_state_file (GtkApplication *application)
|
||||
{
|
||||
const char *app_id;
|
||||
const char *dir;
|
||||
|
||||
app_id = g_application_get_application_id (G_APPLICATION (application));
|
||||
dir = g_get_user_cache_dir ();
|
||||
return g_strconcat (dir, G_DIR_SEPARATOR_S, app_id, ".state", NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_application_save:
|
||||
* @application: a `GtkApplication`
|
||||
*
|
||||
* Saves the state of application.
|
||||
*
|
||||
* See [method@Gtk.Application.restore] for a way to restore the state.
|
||||
*
|
||||
* If [property@Gtk.Application:save-state] is set, `GtkApplication` calls this
|
||||
* function automatically when the application is closed or the session ends.
|
||||
*
|
||||
* Since: 4.4
|
||||
*/
|
||||
void
|
||||
gtk_application_save (GtkApplication *application)
|
||||
{
|
||||
GtkApplicationPrivate *priv = gtk_application_get_instance_private (application);
|
||||
GList *l;
|
||||
GVariantBuilder builder;
|
||||
GVariant *state;
|
||||
|
||||
g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
|
||||
|
||||
for (l = priv->windows; l != NULL; l = l->next)
|
||||
{
|
||||
const char *id;
|
||||
GVariant *v;
|
||||
|
||||
id = gtk_widget_get_save_id (GTK_WIDGET (l->data));
|
||||
if (!id)
|
||||
continue;
|
||||
|
||||
v = gtk_widget_save_state (GTK_WIDGET (l->data));
|
||||
g_variant_builder_add (&builder, "{sv}", id, v);
|
||||
}
|
||||
|
||||
state = g_variant_builder_end (&builder);
|
||||
|
||||
if (g_variant_n_children (state) > 0)
|
||||
{
|
||||
char *file = get_state_file (application);
|
||||
|
||||
g_file_set_contents (file,
|
||||
g_variant_get_data (state),
|
||||
g_variant_get_size (state),
|
||||
NULL);
|
||||
|
||||
g_free (file);
|
||||
}
|
||||
|
||||
g_variant_unref (state);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_application_forget:
|
||||
* @application: a `GtkApplication`
|
||||
*
|
||||
* Forget state that has been previously saved.
|
||||
*
|
||||
* Note that `GtkApplication` will save state automatically
|
||||
* as long as [property@Gtk.Application:save-state] is set,
|
||||
* so you probably want to unset that property as well, if
|
||||
* your goal is to implement a “factory reset”.
|
||||
*
|
||||
* See [method@Gtk.Application.save] for more information.
|
||||
*
|
||||
* Since: 4.4
|
||||
*/
|
||||
void
|
||||
gtk_application_forget (GtkApplication *application)
|
||||
{
|
||||
char *file = get_state_file (application);
|
||||
|
||||
g_remove (file);
|
||||
|
||||
g_free (file);
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_application_restore:
|
||||
* @application: a `GtkApplication`
|
||||
*
|
||||
* Restores previously saved state.
|
||||
*
|
||||
* See [method@Gtk.Application.save] for a way to save application state.
|
||||
*
|
||||
* If [property@Gtk.Application:save-state] is set, `GtkApplication` calls this
|
||||
* function automatically in the default `::activate` handler. Note that you
|
||||
* need to handle the [signal@Gtk.Application::create-window] to make restoring
|
||||
* state work.
|
||||
*
|
||||
* Returns: %TRUE if at least one window has been restored from saved state
|
||||
*
|
||||
* Since: 4.4
|
||||
*/
|
||||
gboolean
|
||||
gtk_application_restore (GtkApplication *application)
|
||||
{
|
||||
GtkApplicationPrivate *priv = gtk_application_get_instance_private (application);
|
||||
char *file = get_state_file (application);
|
||||
char *contents;
|
||||
gsize len;
|
||||
GVariant *state;
|
||||
GVariantIter iter;
|
||||
const char *key;
|
||||
gboolean restored;
|
||||
|
||||
if (!g_file_get_contents (file, &contents, &len, NULL))
|
||||
{
|
||||
g_free (file);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
state = g_variant_new_from_data (G_VARIANT_TYPE_VARDICT, contents, len, FALSE, NULL, NULL);
|
||||
g_variant_iter_init (&iter, state);
|
||||
while (g_variant_iter_next (&iter, "{sv}", &key, NULL))
|
||||
g_signal_emit (application, gtk_application_signals[CREATE_WINDOW], 0, key);
|
||||
|
||||
restored = FALSE;
|
||||
|
||||
for (GList *l = priv->windows; l; l = l->next)
|
||||
{
|
||||
GtkWidget *window = GTK_WIDGET (l->data);
|
||||
const char *id;
|
||||
GVariant *v;
|
||||
|
||||
id = gtk_widget_get_save_id (window);
|
||||
if (id &&
|
||||
(v = g_variant_lookup_value (state, id, NULL)) != NULL)
|
||||
{
|
||||
restored = TRUE;
|
||||
gtk_widget_restore_state (window, v);
|
||||
g_variant_unref (v);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_warning ("::create-window did not set save-id");
|
||||
}
|
||||
|
||||
gtk_window_present (GTK_WINDOW (window));
|
||||
}
|
||||
|
||||
g_free (file);
|
||||
g_free (contents);
|
||||
|
||||
return restored;
|
||||
}
|
||||
|
@@ -64,8 +64,11 @@ struct _GtkApplicationClass
|
||||
void (*window_removed) (GtkApplication *application,
|
||||
GtkWindow *window);
|
||||
|
||||
void (*create_window) (GtkApplication *application,
|
||||
const char *save_id);
|
||||
|
||||
/*< private >*/
|
||||
gpointer padding[8];
|
||||
gpointer padding[7];
|
||||
};
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
@@ -135,6 +138,14 @@ GDK_AVAILABLE_IN_ALL
|
||||
GMenu * gtk_application_get_menu_by_id (GtkApplication *application,
|
||||
const char *id);
|
||||
|
||||
GDK_AVAILABLE_IN_4_4
|
||||
void gtk_application_save (GtkApplication *application);
|
||||
GDK_AVAILABLE_IN_4_4
|
||||
void gtk_application_forget (GtkApplication *application);
|
||||
GDK_AVAILABLE_IN_4_4
|
||||
gboolean gtk_application_restore (GtkApplication *application);
|
||||
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GtkApplication, g_object_unref)
|
||||
|
||||
G_END_DECLS
|
||||
|
@@ -498,6 +498,29 @@ gtk_check_button_real_activate (GtkCheckButton *self)
|
||||
gtk_check_button_set_active (self, !gtk_check_button_get_active (self));
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_check_button_save_state (GtkWidget *self,
|
||||
GVariantDict *dict,
|
||||
gboolean *save_children)
|
||||
{
|
||||
g_variant_dict_insert (dict, "active", "b", gtk_check_button_get_active (GTK_CHECK_BUTTON (self)));
|
||||
*save_children = FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_check_button_restore_state (GtkWidget *self,
|
||||
GVariant *data)
|
||||
{
|
||||
gboolean value;
|
||||
|
||||
if (g_variant_lookup (data, "active", "b", &value))
|
||||
gtk_check_button_set_active (GTK_CHECK_BUTTON (self), value);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_check_button_class_init (GtkCheckButtonClass *class)
|
||||
{
|
||||
@@ -518,6 +541,8 @@ gtk_check_button_class_init (GtkCheckButtonClass *class)
|
||||
|
||||
widget_class->state_flags_changed = gtk_check_button_state_flags_changed;
|
||||
widget_class->focus = gtk_check_button_focus;
|
||||
widget_class->save_state = gtk_check_button_save_state;
|
||||
widget_class->restore_state = gtk_check_button_restore_state;
|
||||
|
||||
class->activate = gtk_check_button_real_activate;
|
||||
|
||||
|
@@ -263,6 +263,43 @@ update_from_editor (GtkColorEditor *editor,
|
||||
g_object_notify (G_OBJECT (widget), "rgba");
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_color_chooser_save_state (GtkWidget *widget,
|
||||
GVariantDict *dict,
|
||||
gboolean *save_children)
|
||||
{
|
||||
GtkColorChooserWidget *cc = GTK_COLOR_CHOOSER_WIDGET (widget);
|
||||
GdkRGBA c;
|
||||
|
||||
gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (cc), &c);
|
||||
|
||||
g_variant_dict_insert (dict, "show-editor", "b", gtk_widget_get_visible (cc->editor));
|
||||
g_variant_dict_insert (dict, "rgba", "(dddd)", c.red, c.green, c.blue, c.alpha);
|
||||
*save_children = TRUE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_color_chooser_restore_state (GtkWidget *widget,
|
||||
GVariant *data)
|
||||
{
|
||||
GtkColorChooserWidget *cc = GTK_COLOR_CHOOSER_WIDGET (widget);
|
||||
gboolean show_editor;
|
||||
double r, g, b, a;
|
||||
|
||||
if (g_variant_lookup (data, "show-editor", "b", &show_editor))
|
||||
gtk_color_chooser_widget_set_show_editor (cc, show_editor);
|
||||
|
||||
if (g_variant_lookup (data, "rgba", "(dddd)", &r, &g, &b, &a))
|
||||
{
|
||||
GdkRGBA c = { r, g, b, a };
|
||||
gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (cc), &c);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* UI construction {{{1 */
|
||||
|
||||
static void
|
||||
@@ -706,6 +743,8 @@ gtk_color_chooser_widget_class_init (GtkColorChooserWidgetClass *class)
|
||||
|
||||
widget_class->grab_focus = gtk_widget_grab_focus_child;
|
||||
widget_class->focus = gtk_widget_focus_child;
|
||||
widget_class->save_state = gtk_color_chooser_save_state;
|
||||
widget_class->restore_state = gtk_color_chooser_restore_state;
|
||||
|
||||
g_object_class_override_property (object_class, PROP_RGBA, "rgba");
|
||||
g_object_class_override_property (object_class, PROP_USE_ALPHA, "use-alpha");
|
||||
|
@@ -390,6 +390,29 @@ gtk_paned_set_orientation (GtkPaned *self,
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_paned_save_state (GtkWidget *paned,
|
||||
GVariantDict *dict,
|
||||
gboolean *save_children)
|
||||
{
|
||||
g_variant_dict_insert (dict, "position", "i", gtk_paned_get_position (GTK_PANED (paned)));
|
||||
*save_children = TRUE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_paned_restore_state (GtkWidget *paned,
|
||||
GVariant *data)
|
||||
{
|
||||
int value;
|
||||
|
||||
if (g_variant_lookup (data, "position", "i", &value))
|
||||
gtk_paned_set_position(GTK_PANED (paned), value);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_paned_class_init (GtkPanedClass *class)
|
||||
{
|
||||
@@ -408,6 +431,8 @@ gtk_paned_class_init (GtkPanedClass *class)
|
||||
widget_class->css_changed = gtk_paned_css_changed;
|
||||
widget_class->get_request_mode = gtk_paned_get_request_mode;
|
||||
widget_class->compute_expand = gtk_paned_compute_expand;
|
||||
widget_class->save_state = gtk_paned_save_state;
|
||||
widget_class->restore_state = gtk_paned_restore_state;
|
||||
|
||||
class->cycle_child_focus = gtk_paned_cycle_child_focus;
|
||||
class->toggle_handle_focus = gtk_paned_toggle_handle_focus;
|
||||
|
@@ -415,6 +415,32 @@ gtk_password_entry_mnemonic_activate (GtkWidget *widget,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_password_entry_save_state (GtkWidget *widget,
|
||||
GVariantDict *dict,
|
||||
gboolean *save_children)
|
||||
{
|
||||
GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (widget);
|
||||
|
||||
g_variant_dict_insert (dict, "visibility", "b", gtk_text_get_visibility (GTK_TEXT (entry->entry)));
|
||||
*save_children = TRUE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_password_entry_restore_state (GtkWidget *widget,
|
||||
GVariant *data)
|
||||
{
|
||||
GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (widget);
|
||||
gboolean value;
|
||||
|
||||
if (g_variant_lookup (data, "visibility", "b", &value))
|
||||
gtk_text_set_visibility (GTK_TEXT (entry->entry), value);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_password_entry_class_init (GtkPasswordEntryClass *klass)
|
||||
{
|
||||
@@ -431,6 +457,8 @@ gtk_password_entry_class_init (GtkPasswordEntryClass *klass)
|
||||
widget_class->mnemonic_activate = gtk_password_entry_mnemonic_activate;
|
||||
widget_class->grab_focus = gtk_widget_grab_focus_child;
|
||||
widget_class->focus = gtk_widget_focus_child;
|
||||
widget_class->save_state = gtk_password_entry_save_state;
|
||||
widget_class->restore_state = gtk_password_entry_restore_state;
|
||||
|
||||
/**
|
||||
* GtkPasswordEntry:placeholder-text:
|
||||
|
@@ -245,6 +245,12 @@ static gboolean gtk_range_scroll_controller_scroll (GtkEventControllerScrol
|
||||
static void gtk_range_set_orientation (GtkRange *range,
|
||||
GtkOrientation orientation);
|
||||
|
||||
static gboolean gtk_range_save_state (GtkWidget *range,
|
||||
GVariantDict *dict,
|
||||
gboolean *save);
|
||||
static gboolean gtk_range_restore_state (GtkWidget *range,
|
||||
GVariant *data);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (GtkRange, gtk_range, GTK_TYPE_WIDGET,
|
||||
G_ADD_PRIVATE (GtkRange)
|
||||
G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE,
|
||||
@@ -271,6 +277,8 @@ gtk_range_class_init (GtkRangeClass *class)
|
||||
widget_class->size_allocate = gtk_range_size_allocate;
|
||||
widget_class->unmap = gtk_range_unmap;
|
||||
widget_class->direction_changed = gtk_range_direction_changed;
|
||||
widget_class->save_state = gtk_range_save_state;
|
||||
widget_class->restore_state = gtk_range_restore_state;
|
||||
|
||||
class->move_slider = gtk_range_move_slider;
|
||||
class->change_value = gtk_range_real_change_value;
|
||||
@@ -2859,6 +2867,29 @@ _gtk_range_get_stop_positions (GtkRange *range,
|
||||
return priv->n_marks;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_range_save_state (GtkWidget *range,
|
||||
GVariantDict *dict,
|
||||
gboolean *save_children)
|
||||
{
|
||||
g_variant_dict_insert (dict, "value", "d", gtk_range_get_value (GTK_RANGE (range)));
|
||||
*save_children = FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_range_restore_state (GtkWidget *range,
|
||||
GVariant *data)
|
||||
{
|
||||
double value;
|
||||
|
||||
if (g_variant_lookup (data, "value", "d", &value))
|
||||
gtk_range_set_value (GTK_RANGE (range), value);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* gtk_range_set_round_digits: (attributes org.gtk.Method.set_property=round-digits)
|
||||
* @range: a `GtkRange`
|
||||
|
@@ -794,6 +794,29 @@ gtk_stack_set_property (GObject *object,
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_stack_save_state (GtkWidget *widget,
|
||||
GVariantDict *dict,
|
||||
gboolean *save_children)
|
||||
{
|
||||
g_variant_dict_insert (dict, "visible-page", "s", gtk_stack_get_visible_child_name (GTK_STACK (widget)));
|
||||
*save_children = TRUE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_stack_restore_state (GtkWidget *widget,
|
||||
GVariant *data)
|
||||
{
|
||||
const char *name;
|
||||
|
||||
if (g_variant_lookup (data, "visible-page", "&s", &name))
|
||||
gtk_stack_set_visible_child_name (GTK_STACK (widget), name);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_stack_class_init (GtkStackClass *klass)
|
||||
{
|
||||
@@ -810,6 +833,8 @@ gtk_stack_class_init (GtkStackClass *klass)
|
||||
widget_class->measure = gtk_stack_measure;
|
||||
widget_class->compute_expand = gtk_stack_compute_expand;
|
||||
widget_class->get_request_mode = gtk_stack_get_request_mode;
|
||||
widget_class->save_state = gtk_stack_save_state;
|
||||
widget_class->restore_state = gtk_stack_restore_state;
|
||||
|
||||
/**
|
||||
* GtkStack:hhomogeneous: (attributes org.gtk.Property.get=gtk_stack_get_hhomogeneous org.gtk.Property.set=gtk_stack_set_hhomogeneous)
|
||||
|
@@ -506,6 +506,29 @@ state_set (GtkSwitch *self,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_switch_save_state (GtkWidget *self,
|
||||
GVariantDict *dict,
|
||||
gboolean *save_children)
|
||||
{
|
||||
g_variant_dict_insert (dict, "active", "b", gtk_switch_get_active (GTK_SWITCH (self)));
|
||||
*save_children = FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_switch_restore_state (GtkWidget *self,
|
||||
GVariant *data)
|
||||
{
|
||||
gboolean value;
|
||||
|
||||
if (g_variant_lookup (data, "active", "b", &value))
|
||||
gtk_switch_set_active (GTK_SWITCH (self), value);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_switch_class_init (GtkSwitchClass *klass)
|
||||
{
|
||||
@@ -545,6 +568,9 @@ gtk_switch_class_init (GtkSwitchClass *klass)
|
||||
|
||||
g_object_class_install_properties (gobject_class, LAST_PROP, switch_props);
|
||||
|
||||
widget_class->save_state = gtk_switch_save_state;
|
||||
widget_class->restore_state = gtk_switch_restore_state;
|
||||
|
||||
klass->activate = gtk_switch_activate;
|
||||
klass->state_set = state_set;
|
||||
|
||||
|
@@ -595,6 +595,12 @@ static void gtk_text_history_select_cb (gpointer funcs_data,
|
||||
int selection_insert,
|
||||
int selection_bound);
|
||||
|
||||
static gboolean gtk_text_save_state (GtkWidget *widget,
|
||||
GVariantDict *dict,
|
||||
gboolean *save_children);
|
||||
static gboolean gtk_text_restore_state (GtkWidget *widget,
|
||||
GVariant *data);
|
||||
|
||||
/* GtkTextContent implementation
|
||||
*/
|
||||
#define GTK_TYPE_TEXT_CONTENT (gtk_text_content_get_type ())
|
||||
@@ -734,6 +740,8 @@ gtk_text_class_init (GtkTextClass *class)
|
||||
widget_class->direction_changed = gtk_text_direction_changed;
|
||||
widget_class->state_flags_changed = gtk_text_state_flags_changed;
|
||||
widget_class->mnemonic_activate = gtk_text_mnemonic_activate;
|
||||
widget_class->save_state = gtk_text_save_state;
|
||||
widget_class->restore_state = gtk_text_restore_state;
|
||||
|
||||
class->move_cursor = gtk_text_move_cursor;
|
||||
class->insert_at_cursor = gtk_text_insert_at_cursor;
|
||||
@@ -1870,6 +1878,7 @@ gtk_text_init (GtkText *self)
|
||||
|
||||
gtk_widget_set_focusable (GTK_WIDGET (self), TRUE);
|
||||
gtk_widget_set_overflow (GTK_WIDGET (self), GTK_OVERFLOW_HIDDEN);
|
||||
gtk_widget_set_save_id (GTK_WIDGET (self), "text");
|
||||
|
||||
priv->editable = TRUE;
|
||||
priv->visible = TRUE;
|
||||
@@ -7181,3 +7190,26 @@ gtk_text_history_select_cb (gpointer funcs_data,
|
||||
selection_insert,
|
||||
selection_bound);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_text_save_state (GtkWidget *widget,
|
||||
GVariantDict *dict,
|
||||
gboolean *save_children)
|
||||
{
|
||||
g_variant_dict_insert (dict, "text", "s", gtk_editable_get_text (GTK_EDITABLE (widget)));
|
||||
*save_children = FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_text_restore_state (GtkWidget *widget,
|
||||
GVariant *data)
|
||||
{
|
||||
const char *text;
|
||||
|
||||
if (g_variant_lookup (data, "text", "&s", &text))
|
||||
gtk_editable_set_text (GTK_EDITABLE (widget), text);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
@@ -249,6 +249,29 @@ get_group_first (GtkToggleButton *self)
|
||||
return group_first;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_toggle_button_save_state (GtkWidget *self,
|
||||
GVariantDict *dict,
|
||||
gboolean *save_children)
|
||||
{
|
||||
g_variant_dict_insert (dict, "active", "b", gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self)));
|
||||
*save_children = FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_toggle_button_restore_state (GtkWidget *self,
|
||||
GVariant *data)
|
||||
{
|
||||
gboolean value;
|
||||
|
||||
if (g_variant_lookup (data, "active", "b", &value))
|
||||
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self), value);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_toggle_button_class_init (GtkToggleButtonClass *class)
|
||||
{
|
||||
@@ -261,6 +284,8 @@ gtk_toggle_button_class_init (GtkToggleButtonClass *class)
|
||||
gobject_class->get_property = gtk_toggle_button_get_property;
|
||||
|
||||
widget_class->mnemonic_activate = gtk_toggle_button_mnemonic_activate;
|
||||
widget_class->save_state = gtk_toggle_button_save_state;
|
||||
widget_class->restore_state = gtk_toggle_button_restore_state;
|
||||
|
||||
button_class->clicked = gtk_toggle_button_clicked;
|
||||
|
||||
|
228
gtk/gtkwidget.c
228
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);
|
||||
}
|
||||
|
@@ -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__ */
|
||||
|
@@ -198,6 +198,9 @@ struct _GtkWidgetPrivate
|
||||
/* Accessibility */
|
||||
GtkAccessibleRole accessible_role;
|
||||
GtkATContext *at_context;
|
||||
|
||||
/* State saving */
|
||||
char *save_id;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
|
@@ -708,6 +708,54 @@ gtk_window_get_request_mode (GtkWidget *widget)
|
||||
return GTK_SIZE_REQUEST_CONSTANT_SIZE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_window_save_state (GtkWidget *widget,
|
||||
GVariantDict *dict,
|
||||
gboolean *save_children)
|
||||
{
|
||||
GtkWindow *win = GTK_WINDOW (widget);
|
||||
int width, height;
|
||||
|
||||
g_variant_dict_insert (dict, "maximized", "b", gtk_window_is_maximized (win));
|
||||
g_variant_dict_insert (dict, "fullscreen", "b", gtk_window_is_fullscreen (win));
|
||||
|
||||
gtk_window_get_default_size (win, &width, &height);
|
||||
g_variant_dict_insert (dict, "size", "(ii)", width, height);
|
||||
|
||||
*save_children = TRUE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gtk_window_restore_state (GtkWidget *widget,
|
||||
GVariant *state)
|
||||
{
|
||||
GtkWindow *win = GTK_WINDOW (widget);
|
||||
gboolean maximized;
|
||||
gboolean fullscreen;
|
||||
int width, height;
|
||||
|
||||
if (g_variant_lookup (state, "maximized", "b", &maximized))
|
||||
{
|
||||
if (maximized)
|
||||
gtk_window_maximize (win);
|
||||
}
|
||||
|
||||
if (g_variant_lookup (state, "fullscreen", "b", &fullscreen))
|
||||
{
|
||||
if (fullscreen)
|
||||
gtk_window_fullscreen (win);
|
||||
}
|
||||
|
||||
if (g_variant_lookup (state, "size", "(ii)", &width, &height))
|
||||
{
|
||||
gtk_window_set_default_size (win, width, height);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_window_class_init (GtkWindowClass *klass)
|
||||
{
|
||||
@@ -739,6 +787,8 @@ gtk_window_class_init (GtkWindowClass *klass)
|
||||
widget_class->move_focus = gtk_window_move_focus;
|
||||
widget_class->measure = gtk_window_measure;
|
||||
widget_class->css_changed = gtk_window_css_changed;
|
||||
widget_class->save_state = gtk_window_save_state;
|
||||
widget_class->restore_state = gtk_window_restore_state;
|
||||
|
||||
klass->activate_default = gtk_window_real_activate_default;
|
||||
klass->activate_focus = gtk_window_real_activate_focus;
|
||||
|
@@ -1,5 +1,6 @@
|
||||
gtk_tests = [
|
||||
# testname, optional extra sources
|
||||
['state-saving'],
|
||||
['testpopup'],
|
||||
['testupload'],
|
||||
['testtransform'],
|
||||
|
246
tests/state-saving.c
Normal file
246
tests/state-saving.c
Normal file
@@ -0,0 +1,246 @@
|
||||
/* state-saving.c
|
||||
* Copyright (C) 2021 Red Hat, Inc
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library 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
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
|
||||
static void
|
||||
quit_cb (GtkWidget *widget,
|
||||
gpointer data)
|
||||
{
|
||||
gboolean *done = data;
|
||||
|
||||
*done = TRUE;
|
||||
|
||||
g_main_context_wakeup (NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
save_cb (GtkWidget *window)
|
||||
{
|
||||
GVariant *state;
|
||||
char *s;
|
||||
GError *error = NULL;
|
||||
|
||||
state = gtk_widget_save_state (window);
|
||||
if (!state)
|
||||
{
|
||||
g_print ("no state\n");
|
||||
return;
|
||||
}
|
||||
|
||||
s = g_variant_print (state, TRUE);
|
||||
g_print ("%s\n", s);
|
||||
g_free (s);
|
||||
|
||||
if (!g_file_set_contents ("saved-state",
|
||||
g_variant_get_data (state),
|
||||
g_variant_get_size (state),
|
||||
&error))
|
||||
{
|
||||
g_error ("Failed to save state: %s", error->message);
|
||||
g_error_free (error);
|
||||
}
|
||||
|
||||
g_variant_unref (state);
|
||||
}
|
||||
|
||||
static void
|
||||
restore_cb (GtkWidget *window)
|
||||
{
|
||||
char *contents;
|
||||
gsize len;
|
||||
GError *error = NULL;
|
||||
GVariant *state;
|
||||
|
||||
if (!g_file_get_contents ("saved-state", &contents, &len, &error))
|
||||
{
|
||||
g_print ("Error loading state: %s\n", error->message);
|
||||
g_error_free (error);
|
||||
return;
|
||||
}
|
||||
|
||||
state = g_variant_new_from_data (G_VARIANT_TYPE_VARDICT, contents, len, FALSE, NULL, NULL);
|
||||
gtk_widget_restore_state (window, state);
|
||||
g_variant_unref (state);
|
||||
}
|
||||
|
||||
static void
|
||||
add_to_list (GtkWidget *list)
|
||||
{
|
||||
GtkWidget *entry;
|
||||
int i;
|
||||
char *s;
|
||||
GtkWidget *w;
|
||||
|
||||
entry = gtk_entry_new ();
|
||||
|
||||
gtk_widget_set_save_id (entry, "entry");
|
||||
gtk_list_box_append (GTK_LIST_BOX (list), entry);
|
||||
|
||||
for (i = 0, w = gtk_widget_get_first_child (list);
|
||||
w;
|
||||
w = gtk_widget_get_next_sibling (w)) i++;
|
||||
|
||||
s = g_strdup_printf ("item%d", i);
|
||||
gtk_widget_set_save_id (gtk_widget_get_parent (entry), s);
|
||||
g_free (s);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
save_list (GtkWidget *list,
|
||||
GVariantDict *dict,
|
||||
gboolean *save_children,
|
||||
gpointer data)
|
||||
{
|
||||
int n_items;
|
||||
GtkWidget *w;
|
||||
|
||||
for (n_items = 0, w = gtk_widget_get_first_child (list);
|
||||
w;
|
||||
w = gtk_widget_get_next_sibling (w)) n_items++;
|
||||
|
||||
g_variant_dict_insert (dict, "n-items", "i", n_items);
|
||||
*save_children = TRUE;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
restore_list (GtkWidget *list,
|
||||
GVariant *data)
|
||||
{
|
||||
int n_items;
|
||||
|
||||
if (g_variant_lookup (data, "n-items", "i", &n_items))
|
||||
{
|
||||
for (int i = 0; i < n_items; i++)
|
||||
add_to_list (list);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
GtkWidget *window, *box, *hbox, *button, *scale;
|
||||
GtkWidget *entry, *cc, *sw, *stack, *switcher;
|
||||
GtkWidget *list;
|
||||
gboolean done = FALSE;
|
||||
|
||||
gtk_init ();
|
||||
|
||||
window = gtk_window_new ();
|
||||
gtk_widget_set_save_id (window, "window");
|
||||
|
||||
gtk_window_set_title (GTK_WINDOW (window), "hello world");
|
||||
gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
|
||||
g_signal_connect (window, "destroy", G_CALLBACK (quit_cb), &done);
|
||||
|
||||
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10);
|
||||
gtk_widget_set_save_id (box, "box");
|
||||
|
||||
stack = gtk_stack_new ();
|
||||
gtk_widget_set_save_id (stack, "stack");
|
||||
switcher = gtk_stack_switcher_new ();
|
||||
gtk_stack_switcher_set_stack (GTK_STACK_SWITCHER (switcher), GTK_STACK (stack));
|
||||
|
||||
gtk_box_append (GTK_BOX (box), switcher);
|
||||
gtk_box_append (GTK_BOX (box), stack);
|
||||
|
||||
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
gtk_box_append (GTK_BOX (box), hbox);
|
||||
|
||||
button = gtk_button_new_with_label ("Save");
|
||||
gtk_widget_set_hexpand (button, TRUE);
|
||||
g_signal_connect_swapped (button, "clicked", G_CALLBACK (save_cb), window);
|
||||
gtk_box_append (GTK_BOX (hbox), button);
|
||||
|
||||
button = gtk_button_new_with_label ("Restore");
|
||||
gtk_widget_set_hexpand (button, TRUE);
|
||||
g_signal_connect_swapped (button, "clicked", G_CALLBACK (restore_cb), window);
|
||||
gtk_box_append (GTK_BOX (hbox), button);
|
||||
|
||||
gtk_window_set_child (GTK_WINDOW (window), box);
|
||||
|
||||
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10);
|
||||
gtk_widget_set_save_id (box, "box");
|
||||
|
||||
scale = gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL, 0, 100, 1);
|
||||
gtk_widget_set_save_id (scale, "scale");
|
||||
gtk_box_append (GTK_BOX (box), scale);
|
||||
|
||||
entry = gtk_entry_new ();
|
||||
gtk_widget_set_save_id (entry, "entry");
|
||||
gtk_box_append (GTK_BOX (box), entry);
|
||||
|
||||
cc = gtk_color_chooser_widget_new ();
|
||||
gtk_widget_set_save_id (cc, "color");
|
||||
gtk_box_append (GTK_BOX (box), cc);
|
||||
|
||||
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
|
||||
gtk_widget_set_save_id (hbox, "hbox");
|
||||
gtk_box_append (GTK_BOX (box), hbox);
|
||||
|
||||
sw = gtk_switch_new ();
|
||||
gtk_widget_set_save_id (sw, "switch");
|
||||
gtk_widget_set_valign (sw, GTK_ALIGN_CENTER);
|
||||
gtk_box_append (GTK_BOX (hbox), sw);
|
||||
|
||||
button = gtk_check_button_new_with_label ("Check");
|
||||
gtk_widget_set_save_id (button, "check");
|
||||
gtk_box_append (GTK_BOX (hbox), button);
|
||||
|
||||
button = gtk_toggle_button_new_with_label ("Toggle");
|
||||
gtk_widget_set_save_id (button, "toggle");
|
||||
gtk_box_append (GTK_BOX (hbox), button);
|
||||
|
||||
entry = gtk_spin_button_new_with_range (0, 100, 1);
|
||||
gtk_widget_set_save_id (entry, "spin");
|
||||
gtk_box_append (GTK_BOX (hbox), entry);
|
||||
|
||||
entry = gtk_password_entry_new ();
|
||||
gtk_password_entry_set_show_peek_icon (GTK_PASSWORD_ENTRY (entry), TRUE);
|
||||
gtk_widget_set_save_id (entry, "password");
|
||||
gtk_box_append (GTK_BOX (box), entry);
|
||||
|
||||
gtk_stack_add_titled (GTK_STACK (stack), box, "page1", "Page 1");
|
||||
|
||||
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
|
||||
gtk_widget_set_save_id (box, "box2");
|
||||
|
||||
list = gtk_list_box_new ();
|
||||
gtk_widget_set_save_id (list, "list");
|
||||
g_signal_connect (list, "save-state", G_CALLBACK (save_list), NULL);
|
||||
g_signal_connect (list, "restore-state", G_CALLBACK (restore_list), NULL);
|
||||
|
||||
button = gtk_button_new_with_label ("Add");
|
||||
g_signal_connect_swapped (button, "clicked", G_CALLBACK (add_to_list), list);
|
||||
|
||||
gtk_box_append (GTK_BOX (box), button);
|
||||
gtk_box_append (GTK_BOX (box), list);
|
||||
|
||||
gtk_stack_add_titled (GTK_STACK (stack), box, "page2", "Page 2");
|
||||
|
||||
gtk_widget_show (window);
|
||||
|
||||
while (!done)
|
||||
g_main_context_iteration (NULL, TRUE);
|
||||
|
||||
return 0;
|
||||
}
|
Reference in New Issue
Block a user