From 537d4bf7910433991bdbb2e1fed143be8840f1b1 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Fri, 13 Oct 2023 11:23:31 +0100 Subject: [PATCH 1/6] restrict-applications-selector: Move GListStore to UI file This simplifies the code a little and will allow binding to it in an upcoming commit. It introduces no functional changes. Signed-off-by: Philip Withnall Helps: #31 --- libmalcontent-ui/restrict-applications-selector.c | 7 ++----- libmalcontent-ui/restrict-applications-selector.ui | 4 ++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/libmalcontent-ui/restrict-applications-selector.c b/libmalcontent-ui/restrict-applications-selector.c index 2da2eee..735dc96 100644 --- a/libmalcontent-ui/restrict-applications-selector.c +++ b/libmalcontent-ui/restrict-applications-selector.c @@ -64,7 +64,7 @@ struct _MctRestrictApplicationsSelector GtkLabel *placeholder; GList *cached_apps; /* (nullable) (owned) (element-type GAppInfo) */ - GListStore *apps; /* (owned) */ + GListStore *apps; GAppInfoMonitor *app_info_monitor; /* (owned) */ gulong app_info_monitor_changed_id; GHashTable *blocklisted_apps; /* (owned) (element-type GAppInfo) */ @@ -156,7 +156,6 @@ mct_restrict_applications_selector_dispose (GObject *object) MctRestrictApplicationsSelector *self = (MctRestrictApplicationsSelector *)object; g_clear_pointer (&self->blocklisted_apps, g_hash_table_unref); - g_clear_object (&self->apps); g_clear_list (&self->cached_apps, g_object_unref); if (self->app_info_monitor != NULL && self->app_info_monitor_changed_id != 0) @@ -225,6 +224,7 @@ mct_restrict_applications_selector_class_init (MctRestrictApplicationsSelectorCl gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsSelector, listbox); gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsSelector, placeholder); + gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsSelector, apps); } static void @@ -234,9 +234,6 @@ mct_restrict_applications_selector_init (MctRestrictApplicationsSelector *self) gtk_widget_init_template (GTK_WIDGET (self)); - self->apps = g_list_store_new (G_TYPE_APP_INFO); - self->cached_apps = NULL; - self->app_info_monitor = g_app_info_monitor_get (); self->app_info_monitor_changed_id = g_signal_connect (self->app_info_monitor, "changed", diff --git a/libmalcontent-ui/restrict-applications-selector.ui b/libmalcontent-ui/restrict-applications-selector.ui index aabda5c..52adf25 100644 --- a/libmalcontent-ui/restrict-applications-selector.ui +++ b/libmalcontent-ui/restrict-applications-selector.ui @@ -20,4 +20,8 @@ + + + GAppInfo + From 13c8a88e17fc80ec5a9a875b1846fb47af2fb29f Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Fri, 13 Oct 2023 11:24:50 +0100 Subject: [PATCH 2/6] restrict-applications-selector: Fix display of placeholder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `GtkListBox` already correctly works out when to show/hide the placeholder; we don’t need to do that ourselves. It actually breaks things if we hide the placeholder, since `GtkListBox` then doesn’t re-show it. Signed-off-by: Philip Withnall --- libmalcontent-ui/restrict-applications-selector.c | 6 ------ libmalcontent-ui/restrict-applications-selector.ui | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/libmalcontent-ui/restrict-applications-selector.c b/libmalcontent-ui/restrict-applications-selector.c index 735dc96..30f2dd9 100644 --- a/libmalcontent-ui/restrict-applications-selector.c +++ b/libmalcontent-ui/restrict-applications-selector.c @@ -61,7 +61,6 @@ struct _MctRestrictApplicationsSelector GtkBox parent_instance; GtkListBox *listbox; - GtkLabel *placeholder; GList *cached_apps; /* (nullable) (owned) (element-type GAppInfo) */ GListStore *apps; @@ -223,7 +222,6 @@ mct_restrict_applications_selector_class_init (MctRestrictApplicationsSelectorCl gtk_widget_class_set_template_from_resource (widget_class, "/org/freedesktop/MalcontentUi/ui/restrict-applications-selector.ui"); gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsSelector, listbox); - gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsSelector, placeholder); gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsSelector, apps); } @@ -245,10 +243,6 @@ mct_restrict_applications_selector_init (MctRestrictApplicationsSelector *self) self, NULL); - /* Hide placeholder if not empty */ - n_apps = g_list_model_get_n_items (G_LIST_MODEL (self->apps)); - gtk_widget_set_visible (GTK_WIDGET (self->placeholder), n_apps != 0); - self->blocklisted_apps = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, diff --git a/libmalcontent-ui/restrict-applications-selector.ui b/libmalcontent-ui/restrict-applications-selector.ui index 52adf25..ccb7e92 100644 --- a/libmalcontent-ui/restrict-applications-selector.ui +++ b/libmalcontent-ui/restrict-applications-selector.ui @@ -9,7 +9,7 @@ none - + No applications found to restrict. From a58c660dca01dccfdc5edd21ef64d37bd6b2b263 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Fri, 13 Oct 2023 11:25:48 +0100 Subject: [PATCH 3/6] restrict-applications-selector: Add more spacing around placeholder label This makes it look nicer. Signed-off-by: Philip Withnall --- libmalcontent-ui/restrict-applications-selector.ui | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libmalcontent-ui/restrict-applications-selector.ui b/libmalcontent-ui/restrict-applications-selector.ui index ccb7e92..a45ea64 100644 --- a/libmalcontent-ui/restrict-applications-selector.ui +++ b/libmalcontent-ui/restrict-applications-selector.ui @@ -11,6 +11,8 @@ No applications found to restrict. + 12 + 12 From 4a1cc8a830c2aec505c78d3db84287731e1b12f7 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Fri, 13 Oct 2023 11:32:13 +0100 Subject: [PATCH 4/6] user-controls: Fix a minor string leak Signed-off-by: Philip Withnall --- libmalcontent-ui/user-controls.c | 1 + 1 file changed, 1 insertion(+) diff --git a/libmalcontent-ui/user-controls.c b/libmalcontent-ui/user-controls.c index 99908b8..27b65aa 100644 --- a/libmalcontent-ui/user-controls.c +++ b/libmalcontent-ui/user-controls.c @@ -702,6 +702,7 @@ mct_user_controls_finalize (GObject *object) g_clear_object (&self->user); g_clear_pointer (&self->user_locale, g_free); g_clear_pointer (&self->user_display_name, g_free); + g_clear_pointer (&self->description, g_free); if (self->permission != NULL && self->permission_allowed_id != 0) { From 69253a2b2ce62d9ab3babfbce67ae0fdbc3007cb Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Fri, 13 Oct 2023 11:34:01 +0100 Subject: [PATCH 5/6] build: Post-release version bump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We’re about to add new API, so bump the version number in preparation. Signed-off-by: Philip Withnall --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 038dd35..4f0cabd 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('malcontent', 'c', - version : '0.11.1', + version : '0.12.0', meson_version : '>= 0.59.0', license: ['LGPL-2.1-or-later', 'GPL-2.0-or-later'], default_options : [ From 73d8343a3e3a61c259967268becc8de3ba9659eb Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Fri, 13 Oct 2023 11:32:54 +0100 Subject: [PATCH 6/6] restrict-applications-dialog: Add support for searching the app list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a type-ahead search which filters the app list by app name. It’s always visible because search is a very obvious interaction the user might want to do to find an app in the app list. That’s why `GtkSearchBar` is not being used here. Signed-off-by: Philip Withnall Fixes: #31 --- .../restrict-applications-dialog.c | 52 ++++++++++ .../restrict-applications-dialog.ui | 16 ++- .../restrict-applications-selector.c | 99 ++++++++++++++++++- .../restrict-applications-selector.h | 4 + .../restrict-applications-selector.ui | 11 +++ 5 files changed, 177 insertions(+), 5 deletions(-) diff --git a/libmalcontent-ui/restrict-applications-dialog.c b/libmalcontent-ui/restrict-applications-dialog.c index 83e3da2..d09b0cb 100644 --- a/libmalcontent-ui/restrict-applications-dialog.c +++ b/libmalcontent-ui/restrict-applications-dialog.c @@ -55,11 +55,18 @@ struct _MctRestrictApplicationsDialog MctRestrictApplicationsSelector *selector; AdwPreferencesGroup *group; + GtkSearchEntry *search_entry; MctAppFilter *app_filter; /* (owned) (not nullable) */ gchar *user_display_name; /* (owned) (nullable) */ }; +static void search_entry_stop_search_cb (GtkSearchEntry *search_entry, + gpointer user_data); +static gboolean focus_search_cb (GtkWidget *widget, + GVariant *arguments, + gpointer user_data); + G_DEFINE_TYPE (MctRestrictApplicationsDialog, mct_restrict_applications_dialog, ADW_TYPE_PREFERENCES_WINDOW) typedef enum @@ -140,6 +147,19 @@ mct_restrict_applications_dialog_dispose (GObject *object) G_OBJECT_CLASS (mct_restrict_applications_dialog_parent_class)->dispose (object); } +static void +mct_restrict_applications_dialog_map (GtkWidget *widget) +{ + MctRestrictApplicationsDialog *self = (MctRestrictApplicationsDialog *)widget; + + GTK_WIDGET_CLASS (mct_restrict_applications_dialog_parent_class)->map (widget); + + /* Clear and focus the search entry, in case the dialogue is being shown for + * a second time. */ + gtk_editable_set_text (GTK_EDITABLE (self->search_entry), ""); + gtk_widget_grab_focus (GTK_WIDGET (self->search_entry)); +} + static void mct_restrict_applications_dialog_class_init (MctRestrictApplicationsDialogClass *klass) { @@ -151,6 +171,8 @@ mct_restrict_applications_dialog_class_init (MctRestrictApplicationsDialogClass object_class->set_property = mct_restrict_applications_dialog_set_property; object_class->dispose = mct_restrict_applications_dialog_dispose; + widget_class->map = mct_restrict_applications_dialog_map; + /** * MctRestrictApplicationsDialog:app-filter: (not nullable) * @@ -197,6 +219,15 @@ mct_restrict_applications_dialog_class_init (MctRestrictApplicationsDialogClass gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsDialog, selector); gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsDialog, group); + gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsDialog, search_entry); + + gtk_widget_class_bind_template_callback (widget_class, search_entry_stop_search_cb); + + gtk_widget_class_add_binding (widget_class, + GDK_KEY_f, GDK_CONTROL_MASK, + focus_search_cb, + NULL); + } static void @@ -206,6 +237,8 @@ mct_restrict_applications_dialog_init (MctRestrictApplicationsDialog *self) g_type_ensure (MCT_TYPE_RESTRICT_APPLICATIONS_SELECTOR); gtk_widget_init_template (GTK_WIDGET (self)); + + gtk_search_entry_set_key_capture_widget (self->search_entry, GTK_WIDGET (self)); } static void @@ -225,6 +258,25 @@ update_description (MctRestrictApplicationsDialog *self) adw_preferences_group_set_description (self->group, description); } +static void +search_entry_stop_search_cb (GtkSearchEntry *search_entry, + gpointer user_data) +{ + /* Clear the search text as the search filtering is bound to that. */ + gtk_editable_set_text (GTK_EDITABLE (search_entry), ""); +} + +static gboolean +focus_search_cb (GtkWidget *widget, + GVariant *arguments, + gpointer user_data) +{ + MctRestrictApplicationsDialog *self = MCT_RESTRICT_APPLICATIONS_DIALOG (widget); + + gtk_widget_grab_focus (GTK_WIDGET (self->search_entry)); + return TRUE; +} + /** * mct_restrict_applications_dialog_new: * @app_filter: (transfer none): the initial app filter configuration to show diff --git a/libmalcontent-ui/restrict-applications-dialog.ui b/libmalcontent-ui/restrict-applications-dialog.ui index 2fd728f..744d3a5 100644 --- a/libmalcontent-ui/restrict-applications-dialog.ui +++ b/libmalcontent-ui/restrict-applications-dialog.ui @@ -14,7 +14,21 @@ Restrict {username} from using the following installed applications. - + + vertical + 12 + + + Search for applications… + + + + + + + + + diff --git a/libmalcontent-ui/restrict-applications-selector.c b/libmalcontent-ui/restrict-applications-selector.c index 30f2dd9..c151066 100644 --- a/libmalcontent-ui/restrict-applications-selector.c +++ b/libmalcontent-ui/restrict-applications-selector.c @@ -41,6 +41,7 @@ static void app_info_changed_cb (GAppInfoMonitor *monitor, static void reload_apps (MctRestrictApplicationsSelector *self); static GtkWidget *create_row_for_app_cb (gpointer item, gpointer user_data); +static char *app_info_dup_name (GAppInfo *app_info); /** * MctRestrictApplicationsSelector: @@ -54,6 +55,11 @@ static GtkWidget *create_row_for_app_cb (gpointer item, * #MctAppFilterBuilder using * mct_restrict_applications_selector_build_app_filter(). * + * Search terms may be applied using #MctRestrictApplicationsSelector:search. + * These will filter the list of displayed apps so that only ones matching the + * search terms (by name, using UTF-8 normalisation and casefolding) will be + * displayed. + * * Since: 0.5.0 */ struct _MctRestrictApplicationsSelector @@ -64,6 +70,8 @@ struct _MctRestrictApplicationsSelector GList *cached_apps; /* (nullable) (owned) (element-type GAppInfo) */ GListStore *apps; + GtkFilterListModel *filtered_apps; + GtkStringFilter *search_filter; GAppInfoMonitor *app_info_monitor; /* (owned) */ gulong app_info_monitor_changed_id; GHashTable *blocklisted_apps; /* (owned) (element-type GAppInfo) */ @@ -74,6 +82,8 @@ struct _MctRestrictApplicationsSelector FlatpakInstallation *user_installation; /* (owned) */ GtkCssProvider *css_provider; /* (owned) */ + + gchar *search; /* (nullable) (owned) */ }; G_DEFINE_TYPE (MctRestrictApplicationsSelector, mct_restrict_applications_selector, GTK_TYPE_BOX) @@ -81,9 +91,10 @@ G_DEFINE_TYPE (MctRestrictApplicationsSelector, mct_restrict_applications_select typedef enum { PROP_APP_FILTER = 1, + PROP_SEARCH, } MctRestrictApplicationsSelectorProperty; -static GParamSpec *properties[PROP_APP_FILTER + 1]; +static GParamSpec *properties[PROP_SEARCH + 1]; enum { SIGNAL_CHANGED, @@ -124,6 +135,9 @@ mct_restrict_applications_selector_get_property (GObject *object, case PROP_APP_FILTER: g_value_set_boxed (value, self->app_filter); break; + case PROP_SEARCH: + g_value_set_string (value, self->search); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -143,6 +157,9 @@ mct_restrict_applications_selector_set_property (GObject *object, case PROP_APP_FILTER: mct_restrict_applications_selector_set_app_filter (self, g_value_get_boxed (value)); break; + case PROP_SEARCH: + mct_restrict_applications_selector_set_search (self, g_value_get_string (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -167,6 +184,7 @@ mct_restrict_applications_selector_dispose (GObject *object) g_clear_object (&self->system_installation); g_clear_object (&self->user_installation); g_clear_object (&self->css_provider); + g_clear_pointer (&self->search, g_free); G_OBJECT_CLASS (mct_restrict_applications_selector_parent_class)->dispose (object); } @@ -201,6 +219,23 @@ mct_restrict_applications_selector_class_init (MctRestrictApplicationsSelectorCl G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + /** + * MctRestrictApplicationsSelector:search: (nullable) + * + * Search terms to filter the displayed list of apps by, or %NULL to not + * filter the search. + * + * Since: 0.12.0 + */ + properties[PROP_SEARCH] = + g_param_spec_string ("search", + "Search", + "Search terms to filter the displayed list of apps by.", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | + G_PARAM_EXPLICIT_NOTIFY); + g_object_class_install_properties (object_class, G_N_ELEMENTS (properties), properties); /** @@ -223,12 +258,15 @@ mct_restrict_applications_selector_class_init (MctRestrictApplicationsSelectorCl gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsSelector, listbox); gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsSelector, apps); + gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsSelector, filtered_apps); + gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsSelector, search_filter); + + gtk_widget_class_bind_template_callback (widget_class, app_info_dup_name); } static void mct_restrict_applications_selector_init (MctRestrictApplicationsSelector *self) { - guint n_apps; gtk_widget_init_template (GTK_WIDGET (self)); @@ -238,7 +276,7 @@ mct_restrict_applications_selector_init (MctRestrictApplicationsSelector *self) (GCallback) app_info_changed_cb, self); gtk_list_box_bind_model (self->listbox, - G_LIST_MODEL (self->apps), + G_LIST_MODEL (self->filtered_apps), create_row_for_app_cb, self, NULL); @@ -359,6 +397,12 @@ create_row_for_app_cb (gpointer item, return row; } +static char * +app_info_dup_name (GAppInfo *app_info) +{ + return g_strdup (g_app_info_get_name (app_info)); +} + static gint compare_app_info_cb (gconstpointer a, gconstpointer b, @@ -767,7 +811,7 @@ mct_restrict_applications_selector_set_app_filter (MctRestrictApplicationsSelect self->app_filter = mct_app_filter_ref (app_filter); /* Update the status of each app row. */ - n_apps = g_list_model_get_n_items (G_LIST_MODEL (self->apps)); + n_apps = g_list_model_get_n_items (G_LIST_MODEL (self->filtered_apps)); for (guint i = 0; i < n_apps; i++) { @@ -786,3 +830,50 @@ mct_restrict_applications_selector_set_app_filter (MctRestrictApplicationsSelect g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_APP_FILTER]); } + +/** + * mct_restrict_applications_selector_get_search: + * @self: an #MctRestrictApplicationsSelector + * + * Get the value of #MctRestrictApplicationsSelector:search. + * + * Returns: current search terms, or %NULL if no search filtering is active + * Since: 0.12.0 + */ +const gchar * +mct_restrict_applications_selector_get_search (MctRestrictApplicationsSelector *self) +{ + g_return_val_if_fail (MCT_IS_RESTRICT_APPLICATIONS_SELECTOR (self), NULL); + + return self->search; +} + +/** + * mct_restrict_applications_selector_set_search: + * @self: an #MctRestrictApplicationsSelector + * @search: (nullable): search terms, or %NULL to not filter the app list + * + * Set the value of #MctRestrictApplicationsSelector:search, or clear it to + * %NULL. + * + * Since: 0.12.0 + */ +void +mct_restrict_applications_selector_set_search (MctRestrictApplicationsSelector *self, + const gchar *search) +{ + g_return_if_fail (MCT_IS_RESTRICT_APPLICATIONS_SELECTOR (self)); + + /* Squash empty search terms down to nothing. */ + if (search != NULL && *search == '\0') + search = NULL; + + if (g_strcmp0 (search, self->search) == 0) + return; + + g_clear_pointer (&self->search, g_free); + self->search = g_strdup (search); + + gtk_string_filter_set_search (self->search_filter, self->search); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEARCH]); +} diff --git a/libmalcontent-ui/restrict-applications-selector.h b/libmalcontent-ui/restrict-applications-selector.h index e02a10a..8a37916 100644 --- a/libmalcontent-ui/restrict-applications-selector.h +++ b/libmalcontent-ui/restrict-applications-selector.h @@ -41,4 +41,8 @@ void mct_restrict_applications_selector_set_app_filter (MctRestrictAppl void mct_restrict_applications_selector_build_app_filter (MctRestrictApplicationsSelector *self, MctAppFilterBuilder *builder); +const gchar *mct_restrict_applications_selector_get_search (MctRestrictApplicationsSelector *self); +void mct_restrict_applications_selector_set_search (MctRestrictApplicationsSelector *self, + const gchar *search); + G_END_DECLS diff --git a/libmalcontent-ui/restrict-applications-selector.ui b/libmalcontent-ui/restrict-applications-selector.ui index a45ea64..0c75dfb 100644 --- a/libmalcontent-ui/restrict-applications-selector.ui +++ b/libmalcontent-ui/restrict-applications-selector.ui @@ -26,4 +26,15 @@ GAppInfo + + + apps + search_filter + + + + + + +