Skip to content

Add option to sort extensions by enabled status #130

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions data/com.mattjakeman.ExtensionManager.gschema.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,10 @@
<summary>Last-Used Version</summary>
<description>The last-used version of the app. If the saved version does not match, the "What's New" dialog is shown.</description>
</key>
<key name="sort-enabled-first" type="b">
<default>false</default>
<summary>Sort Enabled First</summary>
<description>Display enabled extensions first in the installed view.</description>
</key>
</schema>
</schemalist>
3 changes: 3 additions & 0 deletions src/exm-application.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand Down
70 changes: 55 additions & 15 deletions src/exm-extension-row.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
{
Expand All @@ -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,
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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;

Expand All @@ -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
Expand All @@ -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",
Expand Down
118 changes: 94 additions & 24 deletions src/exm-installed-page.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 <glib/gi18n.h>

Expand All @@ -17,18 +21,24 @@ 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)

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)
{
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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);
Expand All @@ -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);
}
4 changes: 4 additions & 0 deletions src/exm-window.blp
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Loading