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.
+
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-extension-row.c b/src/exm-extension-row.c
index bdfdc49a..cc07a6af 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,13 @@ enum {
static GParamSpec *properties [N_PROPS];
+static void
+bind_extension (ExmExtensionRow *self,
+ ExmExtension *extension);
+
+static void
+unbind_extension (ExmExtensionRow *self);
+
ExmExtensionRow *
exm_extension_row_new (ExmExtension *extension)
{
@@ -53,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,
@@ -82,14 +101,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);
@@ -106,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;
@@ -168,14 +185,35 @@ set_error_label_visible (ExmExtensionRow *self,
}
static void
-exm_extension_row_constructed (GObject *object)
+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)
{
// 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
+ unbind_extension (self);
+
+ // Now, bind the new one
+ self->extension = g_object_ref (extension);
+
+ if (self->extension == NULL)
+ return;
gchar *name, *uuid, *description, *version, *error_msg;
gboolean has_prefs, has_update, is_user;
@@ -192,6 +230,8 @@ exm_extension_row_constructed (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);
@@ -208,11 +248,13 @@ exm_extension_row_constructed (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;
@@ -226,8 +268,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
@@ -236,9 +276,9 @@ 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;
- object_class->constructed = exm_extension_row_constructed;
properties [PROP_EXTENSION] =
g_param_spec_object ("extension",
diff --git a/src/exm-installed-page.c b/src/exm-installed-page.c
index eb65b55b..71230555 100644
--- a/src/exm-installed-page.c
+++ b/src/exm-installed-page.c
@@ -3,6 +3,10 @@
#include "exm-extension-row.h"
#include "local/exm-manager.h"
+#include "exm-enums.h"
+#include "exm-types.h"
+
+#include "exm-config.h"
#include
@@ -17,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)
@@ -24,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)
{
@@ -56,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);
}
@@ -74,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);
}
@@ -119,18 +136,60 @@ 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)
+ GListModel *model,
+ gboolean sort_enabled_first)
{
GtkExpression *expression;
- GtkStringSorter *sorter;
+ GtkStringSorter *alphabetical_sorter;
GtkSortListModel *sorted_model;
// 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);
+
+ if (sort_enabled_first)
+ {
+ GtkCustomSorter *enabled_sorter;
+ GtkMultiSorter *multi_sorter;
+
+ // 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));
+ }
+ 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,
@@ -165,18 +224,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",
@@ -189,6 +254,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",
@@ -217,6 +289,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);
@@ -230,30 +309,21 @@ 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.")));
+ GSettings *settings;
- gtk_list_box_set_placeholder (self->system_list_box,
- create_placeholder (_("There are no system extensions installed.")));
+ 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";
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'