From 5bc42b734455b80b6fa4574a856a2cd737b3edb8 Mon Sep 17 00:00:00 2001 From: Matthew Jakeman Date: Sun, 20 Mar 2022 01:12:52 +1300 Subject: [PATCH 1/6] settings: Add gsettings key for sorting extensions by enabled --- data/com.mattjakeman.ExtensionManager.gschema.xml.in | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/data/com.mattjakeman.ExtensionManager.gschema.xml.in b/data/com.mattjakeman.ExtensionManager.gschema.xml.in index 0714ec00..2c14997d 100644 --- a/data/com.mattjakeman.ExtensionManager.gschema.xml.in +++ b/data/com.mattjakeman.ExtensionManager.gschema.xml.in @@ -16,5 +16,10 @@ Last-Used Version The last-used version of the app. If the saved version does not match, the "What's New" dialog is shown. + + false + Sort Enabled First + Display enabled extensions first in the installed view. + From 447675b4f54da79bf9ba9f7e18ace58956386dcd Mon Sep 17 00:00:00 2001 From: Matthew Jakeman Date: Sun, 20 Mar 2022 01:13:13 +1300 Subject: [PATCH 2/6] installed-page: Display enabled extensions first --- src/exm-installed-page.c | 58 ++++++++++++++++++++++++++-------------- src/local/exm-manager.c | 7 +++++ 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/src/exm-installed-page.c b/src/exm-installed-page.c index eb65b55b..c8fa447b 100644 --- a/src/exm-installed-page.c +++ b/src/exm-installed-page.c @@ -3,6 +3,8 @@ #include "exm-extension-row.h" #include "local/exm-manager.h" +#include "exm-enums.h" +#include "exm-types.h" #include @@ -119,18 +121,51 @@ widget_factory (ExmExtension* extension) return GTK_WIDGET (row); } +static int +compare_enabled (ExmExtension *this, ExmExtension *other) +{ + g_return_if_fail (EXM_IS_EXTENSION (this)); + g_return_if_fail (EXM_IS_EXTENSION (other)); + + ExmExtensionState this_state; + ExmExtensionState other_state; + + g_object_get (this, "state", &this_state, NULL); + g_object_get (other, "state", &other_state, NULL); + + gboolean this_enabled = (this_state == EXM_EXTENSION_STATE_ENABLED); + gboolean other_enabled = (other_state == EXM_EXTENSION_STATE_ENABLED); + + if ((this_enabled && other_enabled) || (!this_enabled && !other_enabled)) + return 0; + else if (this_enabled && !other_enabled) + return -1; + else if (!this_enabled && other_enabled) + return 1; +} + static void bind_list_box (GtkListBox *list_box, GListModel *model) { GtkExpression *expression; - GtkStringSorter *sorter; + GtkCustomSorter *enabled_sorter; + GtkStringSorter *alphabetical_sorter; + GtkMultiSorter *multi_sorter; GtkSortListModel *sorted_model; + // Sort by enabled + enabled_sorter = gtk_custom_sorter_new (compare_enabled, NULL, NULL); + // Sort alphabetically expression = gtk_property_expression_new (EXM_TYPE_EXTENSION, NULL, "display-name"); - sorter = gtk_string_sorter_new (expression); - sorted_model = gtk_sort_list_model_new (model, GTK_SORTER (sorter)); + alphabetical_sorter = gtk_string_sorter_new (expression); + + multi_sorter = gtk_multi_sorter_new (); + gtk_multi_sorter_append (multi_sorter, enabled_sorter); + gtk_multi_sorter_append (multi_sorter, alphabetical_sorter); + + sorted_model = gtk_sort_list_model_new (model, GTK_SORTER (multi_sorter)); gtk_list_box_bind_model (list_box, G_LIST_MODEL (sorted_model), (GtkListBoxCreateWidgetFunc) widget_factory, @@ -230,28 +265,11 @@ exm_installed_page_class_init (ExmInstalledPageClass *klass) gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); } -static GtkWidget * -create_placeholder (const gchar *text) -{ - GtkWidget *label; - - label = gtk_label_new (text); - gtk_widget_add_css_class (label, "placeholder"); - - return label; -} - static void exm_installed_page_init (ExmInstalledPage *self) { gtk_widget_init_template (GTK_WIDGET (self)); - gtk_list_box_set_placeholder (self->user_list_box, - create_placeholder (_("There are no user extensions installed."))); - - gtk_list_box_set_placeholder (self->system_list_box, - create_placeholder (_("There are no system extensions installed."))); - g_signal_connect (self, "notify::manager", G_CALLBACK (on_bind_manager), diff --git a/src/local/exm-manager.c b/src/local/exm-manager.c index cac45260..5d3795d4 100644 --- a/src/local/exm-manager.c +++ b/src/local/exm-manager.c @@ -696,6 +696,13 @@ on_state_changed (ShellExtensions *object, return; } + // Emit items-changed signal to re-sort extension list + { + guint position; + if (g_list_store_find_with_equal_func (list_store, extension, (GEqualFunc)is_extension_equal, &position)) + g_list_model_items_changed (G_LIST_MODEL (list_store), position, 1, 1); + } + // If the extension that has changed has an update, then // one or more extensions have updates available. Lazily // check the exact number and emit the 'updates-available' From 3acb74c4c8fd05c7207876304ceb18dd4718227b Mon Sep 17 00:00:00 2001 From: Matthew Jakeman Date: Mon, 21 Mar 2022 20:20:40 +1300 Subject: [PATCH 3/6] settings: Connect sort by enabled preference --- src/exm-application.c | 3 ++ src/exm-installed-page.c | 73 ++++++++++++++++++++++++++++++++++------ src/exm-window.blp | 4 +++ 3 files changed, 69 insertions(+), 11 deletions(-) diff --git a/src/exm-application.c b/src/exm-application.c index 97e34f56..fd4418e6 100644 --- a/src/exm-application.c +++ b/src/exm-application.c @@ -197,6 +197,9 @@ exm_application_init (ExmApplication *self) g_signal_connect_swapped (logout_action, "activate", G_CALLBACK (request_logout), self); g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (logout_action)); + GAction *sort_enabled_first_action = g_settings_create_action (settings, "sort-enabled-first"); + g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (sort_enabled_first_action)); + GAction *style_variant_action = g_settings_create_action (settings, "style-variant"); g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (style_variant_action)); diff --git a/src/exm-installed-page.c b/src/exm-installed-page.c index c8fa447b..433191de 100644 --- a/src/exm-installed-page.c +++ b/src/exm-installed-page.c @@ -6,6 +6,8 @@ #include "exm-enums.h" #include "exm-types.h" +#include "exm-config.h" + #include struct _ExmInstalledPage @@ -19,6 +21,8 @@ struct _ExmInstalledPage GtkListBox *system_list_box; GtkLabel *num_updates_label; GtkRevealer *updates_action_bar; + + gboolean sort_enabled_first; }; G_DEFINE_FINAL_TYPE (ExmInstalledPage, exm_installed_page, GTK_TYPE_WIDGET) @@ -26,11 +30,15 @@ G_DEFINE_FINAL_TYPE (ExmInstalledPage, exm_installed_page, GTK_TYPE_WIDGET) enum { PROP_0, PROP_MANAGER, + PROP_SORT_ENABLED_FIRST, N_PROPS }; static GParamSpec *properties [N_PROPS]; +static void +invalidate_model_bindings (ExmInstalledPage *self); + ExmInstalledPage * exm_installed_page_new (void) { @@ -58,6 +66,9 @@ exm_installed_page_get_property (GObject *object, case PROP_MANAGER: g_value_set_object (value, self->manager); break; + case PROP_SORT_ENABLED_FIRST: + g_value_set_boolean (value, self->sort_enabled_first); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -76,6 +87,10 @@ exm_installed_page_set_property (GObject *object, case PROP_MANAGER: self->manager = g_value_get_object (value); break; + case PROP_SORT_ENABLED_FIRST: + self->sort_enabled_first = g_value_get_boolean (value); + invalidate_model_bindings (self); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -146,7 +161,8 @@ compare_enabled (ExmExtension *this, ExmExtension *other) static void bind_list_box (GtkListBox *list_box, - GListModel *model) + GListModel *model, + gboolean sort_enabled_first) { GtkExpression *expression; GtkCustomSorter *enabled_sorter; @@ -154,18 +170,25 @@ bind_list_box (GtkListBox *list_box, GtkMultiSorter *multi_sorter; GtkSortListModel *sorted_model; - // Sort by enabled - enabled_sorter = gtk_custom_sorter_new (compare_enabled, NULL, NULL); - // Sort alphabetically expression = gtk_property_expression_new (EXM_TYPE_EXTENSION, NULL, "display-name"); alphabetical_sorter = gtk_string_sorter_new (expression); - multi_sorter = gtk_multi_sorter_new (); - gtk_multi_sorter_append (multi_sorter, enabled_sorter); - gtk_multi_sorter_append (multi_sorter, alphabetical_sorter); + if (sort_enabled_first) + { + // Sort by enabled + enabled_sorter = gtk_custom_sorter_new ((GCompareDataFunc) compare_enabled, NULL, NULL); + + multi_sorter = gtk_multi_sorter_new (); + gtk_multi_sorter_append (multi_sorter, GTK_SORTER (enabled_sorter)); + gtk_multi_sorter_append (multi_sorter, GTK_SORTER (alphabetical_sorter)); - sorted_model = gtk_sort_list_model_new (model, GTK_SORTER (multi_sorter)); + sorted_model = gtk_sort_list_model_new (model, GTK_SORTER (multi_sorter)); + } + else + { + sorted_model = gtk_sort_list_model_new (model, GTK_SORTER (alphabetical_sorter)); + } gtk_list_box_bind_model (list_box, G_LIST_MODEL (sorted_model), (GtkListBoxCreateWidgetFunc) widget_factory, @@ -200,18 +223,24 @@ on_updates_available (ExmManager *manager, } static void -on_bind_manager (ExmInstalledPage *self) +invalidate_model_bindings (ExmInstalledPage *self) { GListModel *user_ext_model; GListModel *system_ext_model; + if (!self->manager) + return; + g_object_get (self->manager, "user-extensions", &user_ext_model, "system-extensions", &system_ext_model, NULL); - bind_list_box (self->user_list_box, user_ext_model); - bind_list_box (self->system_list_box, system_ext_model); + if (!user_ext_model || !system_ext_model) + return; + + bind_list_box (self->user_list_box, user_ext_model, self->sort_enabled_first); + bind_list_box (self->system_list_box, system_ext_model, self->sort_enabled_first); g_object_bind_property (self->manager, "extensions-enabled", @@ -224,6 +253,13 @@ on_bind_manager (ExmInstalledPage *self) self->system_list_box, "sensitive", G_BINDING_SYNC_CREATE); +} + +static void +on_bind_manager (ExmInstalledPage *self) +{ + // Bind (or rebind) models + invalidate_model_bindings (self); g_signal_connect (self->manager, "updates-available", @@ -252,6 +288,13 @@ exm_installed_page_class_init (ExmInstalledPageClass *klass) EXM_TYPE_MANAGER, G_PARAM_READWRITE); + properties [PROP_SORT_ENABLED_FIRST] + = g_param_spec_boolean ("sort-enabled-first", + "Sort Enabled First", + "Sort Enabled First", + FALSE, + G_PARAM_READWRITE); + g_object_class_install_properties (object_class, N_PROPS, properties); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); @@ -268,10 +311,18 @@ exm_installed_page_class_init (ExmInstalledPageClass *klass) static void exm_installed_page_init (ExmInstalledPage *self) { + GSettings *settings; + gtk_widget_init_template (GTK_WIDGET (self)); g_signal_connect (self, "notify::manager", G_CALLBACK (on_bind_manager), NULL); + + settings = g_settings_new (APP_ID); + + g_settings_bind (settings, "sort-enabled-first", + self, "sort-enabled-first", + G_SETTINGS_BIND_GET); } diff --git a/src/exm-window.blp b/src/exm-window.blp index 439c9507..2db632ed 100644 --- a/src/exm-window.blp +++ b/src/exm-window.blp @@ -91,6 +91,10 @@ menu primary_menu { label: _("Release Notes"); action: "win.show-release-notes"; } + item { + label: _("Sort Enabled First"); + action: "app.sort-enabled-first"; + } item { label: _("About Extension Manager"); action: "app.about"; From b04736a18604b3daff34f949d5c5ea4bdb3055f1 Mon Sep 17 00:00:00 2001 From: Matthew Jakeman Date: Mon, 21 Mar 2022 20:33:43 +1300 Subject: [PATCH 4/6] extension-row: Rebind extension data when necessary --- src/exm-extension-row.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/exm-extension-row.c b/src/exm-extension-row.c index bdfdc49a..0efb7a14 100644 --- a/src/exm-extension-row.c +++ b/src/exm-extension-row.c @@ -168,7 +168,8 @@ set_error_label_visible (ExmExtensionRow *self, } static void -exm_extension_row_constructed (GObject *object) +exm_extension_row_bind_extension (GObject *object, + GParamSpec *pspec) { // TODO: This big block of property assignments is currently copy/pasted // from ExmExtension. We can replace this with GtkExpression lookups @@ -177,6 +178,11 @@ exm_extension_row_constructed (GObject *object) ExmExtensionRow *self = EXM_EXTENSION_ROW (object); + g_return_if_fail (pspec == properties [PROP_EXTENSION]); + + if (self->extension == NULL) + return; + gchar *name, *uuid, *description, *version, *error_msg; gboolean has_prefs, has_update, is_user; ExmExtensionState state; @@ -226,8 +232,6 @@ exm_extension_row_constructed (GObject *object) g_simple_action_set_enabled (G_SIMPLE_ACTION (action), is_user); update_state (self->extension, NULL, self); - - G_OBJECT_CLASS (exm_extension_row_parent_class)->constructed (object); } static void @@ -238,7 +242,6 @@ exm_extension_row_class_init (ExmExtensionRowClass *klass) object_class->finalize = exm_extension_row_finalize; object_class->get_property = exm_extension_row_get_property; object_class->set_property = exm_extension_row_set_property; - object_class->constructed = exm_extension_row_constructed; properties [PROP_EXTENSION] = g_param_spec_object ("extension", @@ -320,6 +323,11 @@ exm_extension_row_init (ExmExtensionRow *self) gtk_widget_init_template (GTK_WIDGET (self)); + g_signal_connect (self, + "notify::extension", + G_CALLBACK (exm_extension_row_bind_extension), + NULL); + // Define Actions self->action_group = g_simple_action_group_new (); From d6691b213d5efb55dec4639d6c1e169b4434636f Mon Sep 17 00:00:00 2001 From: Matthew Jakeman Date: Mon, 21 Mar 2022 20:44:24 +1300 Subject: [PATCH 5/6] extension-row: Make row binding more robust --- src/exm-extension-row.c | 45 ++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/src/exm-extension-row.c b/src/exm-extension-row.c index 0efb7a14..ec395ac3 100644 --- a/src/exm-extension-row.c +++ b/src/exm-extension-row.c @@ -25,6 +25,8 @@ struct _ExmExtensionRow GtkImage *update_icon; GtkImage *error_icon; GtkImage *out_of_date_icon; + + guint signal_handler; }; G_DEFINE_FINAL_TYPE (ExmExtensionRow, exm_extension_row, ADW_TYPE_EXPANDER_ROW) @@ -37,6 +39,10 @@ enum { static GParamSpec *properties [N_PROPS]; +static void +bind_extension (ExmExtensionRow *self, + ExmExtension *extension); + ExmExtensionRow * exm_extension_row_new (ExmExtension *extension) { @@ -82,14 +88,7 @@ exm_extension_row_set_property (GObject *object, switch (prop_id) { case PROP_EXTENSION: - self->extension = g_value_get_object (value); - if (self->extension) - { - // TODO: Bind here, rather than in constructed() - g_object_get (self->extension, - "uuid", &self->uuid, - NULL); - } + bind_extension (self, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -168,17 +167,26 @@ set_error_label_visible (ExmExtensionRow *self, } static void -exm_extension_row_bind_extension (GObject *object, - GParamSpec *pspec) +bind_extension (ExmExtensionRow *self, + ExmExtension *extension) { // TODO: This big block of property assignments is currently copy/pasted // from ExmExtension. We can replace this with GtkExpression lookups // once blueprint-compiler supports expressions. // (See https://gitlab.gnome.org/jwestman/blueprint-compiler/-/issues/5) - ExmExtensionRow *self = EXM_EXTENSION_ROW (object); + g_return_if_fail (EXM_IS_EXTENSION_ROW (self)); + + // First, remove traces of the old extension + if (self->extension != NULL) + { + g_signal_handler_disconnect (self->extension, self->signal_handler); + g_clear_object (&self->extension); + g_clear_pointer (&self->uuid, g_free); + } - g_return_if_fail (pspec == properties [PROP_EXTENSION]); + // Now, bind the new one + self->extension = extension; if (self->extension == NULL) return; @@ -198,6 +206,8 @@ exm_extension_row_bind_extension (GObject *object, "error-msg", &error_msg, NULL); + self->uuid = g_strdup (uuid); + g_object_set (self, "title", name, "subtitle", uuid, NULL); g_object_set (self->prefs_btn, "visible", has_prefs, NULL); g_object_set (self->remove_btn, "visible", is_user, NULL); @@ -214,11 +224,13 @@ exm_extension_row_bind_extension (GObject *object, gboolean has_error = (error_msg != NULL) && (strlen(error_msg) != 0); set_error_label_visible (self, has_error); - gtk_actionable_set_action_target (GTK_ACTIONABLE (self->details_btn), "s", uuid); // One way binding from extension ("source of truth") to switch - g_signal_connect (self->extension, "notify::state", G_CALLBACK (update_state), self); + self->signal_handler = g_signal_connect (self->extension, + "notify::state", + G_CALLBACK (update_state), + self); GAction *action; @@ -323,11 +335,6 @@ exm_extension_row_init (ExmExtensionRow *self) gtk_widget_init_template (GTK_WIDGET (self)); - g_signal_connect (self, - "notify::extension", - G_CALLBACK (exm_extension_row_bind_extension), - NULL); - // Define Actions self->action_group = g_simple_action_group_new (); From 4a642888bc16d0c3702158557108fa770fff1a2d Mon Sep 17 00:00:00 2001 From: Matthew Jakeman Date: Mon, 21 Mar 2022 23:49:18 +1300 Subject: [PATCH 6/6] extension-row: Fix crash caused by reference cycle --- src/exm-extension-row.c | 39 ++++++++++++++++++++++++++++++++------- src/exm-installed-page.c | 5 +++-- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/exm-extension-row.c b/src/exm-extension-row.c index ec395ac3..cc07a6af 100644 --- a/src/exm-extension-row.c +++ b/src/exm-extension-row.c @@ -43,6 +43,9 @@ static void bind_extension (ExmExtensionRow *self, ExmExtension *extension); +static void +unbind_extension (ExmExtensionRow *self); + ExmExtensionRow * exm_extension_row_new (ExmExtension *extension) { @@ -59,6 +62,16 @@ exm_extension_row_finalize (GObject *object) G_OBJECT_CLASS (exm_extension_row_parent_class)->finalize (object); } +static void +exm_extension_row_dispose (GObject *object) +{ + ExmExtensionRow *self = (ExmExtensionRow *)object; + + unbind_extension (self); + + G_OBJECT_CLASS (exm_extension_row_parent_class)->dispose (object); +} + static void exm_extension_row_get_property (GObject *object, guint prop_id, @@ -105,6 +118,11 @@ update_state (ExmExtension *extension, // the extension. We do not want this behaviour as it messes with the global // extension toggle. + g_return_if_fail (EXM_IS_EXTENSION (extension)); + g_return_if_fail (EXM_IS_EXTENSION_ROW (row)); + + g_assert (row->extension == extension); + const gchar *uuid; ExmExtensionState new_state; GAction *action; @@ -166,6 +184,17 @@ set_error_label_visible (ExmExtensionRow *self, gtk_widget_set_visible (GTK_WIDGET (self->error_label_tag), visible); } +static void +unbind_extension (ExmExtensionRow *self) +{ + if (self->extension != NULL) + { + g_signal_handler_disconnect (self->extension, self->signal_handler); + g_clear_object (&self->extension); + g_clear_pointer (&self->uuid, g_free); + } +} + static void bind_extension (ExmExtensionRow *self, ExmExtension *extension) @@ -178,15 +207,10 @@ bind_extension (ExmExtensionRow *self, g_return_if_fail (EXM_IS_EXTENSION_ROW (self)); // First, remove traces of the old extension - if (self->extension != NULL) - { - g_signal_handler_disconnect (self->extension, self->signal_handler); - g_clear_object (&self->extension); - g_clear_pointer (&self->uuid, g_free); - } + unbind_extension (self); // Now, bind the new one - self->extension = extension; + self->extension = g_object_ref (extension); if (self->extension == NULL) return; @@ -252,6 +276,7 @@ exm_extension_row_class_init (ExmExtensionRowClass *klass) GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = exm_extension_row_finalize; + object_class->dispose = exm_extension_row_dispose; object_class->get_property = exm_extension_row_get_property; object_class->set_property = exm_extension_row_set_property; diff --git a/src/exm-installed-page.c b/src/exm-installed-page.c index 433191de..71230555 100644 --- a/src/exm-installed-page.c +++ b/src/exm-installed-page.c @@ -165,9 +165,7 @@ bind_list_box (GtkListBox *list_box, gboolean sort_enabled_first) { GtkExpression *expression; - GtkCustomSorter *enabled_sorter; GtkStringSorter *alphabetical_sorter; - GtkMultiSorter *multi_sorter; GtkSortListModel *sorted_model; // Sort alphabetically @@ -176,6 +174,9 @@ bind_list_box (GtkListBox *list_box, if (sort_enabled_first) { + GtkCustomSorter *enabled_sorter; + GtkMultiSorter *multi_sorter; + // Sort by enabled enabled_sorter = gtk_custom_sorter_new ((GCompareDataFunc) compare_enabled, NULL, NULL);