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 2da2eee..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 @@ -61,10 +67,11 @@ struct _MctRestrictApplicationsSelector GtkBox parent_instance; GtkListBox *listbox; - GtkLabel *placeholder; GList *cached_apps; /* (nullable) (owned) (element-type GAppInfo) */ - GListStore *apps; /* (owned) */ + 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) */ @@ -75,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) @@ -82,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, @@ -125,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); @@ -144,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); @@ -156,7 +172,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) @@ -169,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); } @@ -203,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); /** @@ -224,34 +257,30 @@ 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); + 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)); - 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", (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); - /* 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, @@ -368,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, @@ -776,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++) { @@ -795,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 aabda5c..0c75dfb 100644 --- a/libmalcontent-ui/restrict-applications-selector.ui +++ b/libmalcontent-ui/restrict-applications-selector.ui @@ -9,8 +9,10 @@ none - + No applications found to restrict. + 12 + 12 @@ -20,4 +22,19 @@ + + + GAppInfo + + + + apps + search_filter + + + + + + + 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) { 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 : [