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.
-
+
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 : [