restrict-applications-dialog: Add support for searching the app list
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 <pwithnall@endlessos.org> Fixes: #31
This commit is contained in:
parent
69253a2b2c
commit
73d8343a3e
|
@ -55,11 +55,18 @@ struct _MctRestrictApplicationsDialog
|
||||||
|
|
||||||
MctRestrictApplicationsSelector *selector;
|
MctRestrictApplicationsSelector *selector;
|
||||||
AdwPreferencesGroup *group;
|
AdwPreferencesGroup *group;
|
||||||
|
GtkSearchEntry *search_entry;
|
||||||
|
|
||||||
MctAppFilter *app_filter; /* (owned) (not nullable) */
|
MctAppFilter *app_filter; /* (owned) (not nullable) */
|
||||||
gchar *user_display_name; /* (owned) (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)
|
G_DEFINE_TYPE (MctRestrictApplicationsDialog, mct_restrict_applications_dialog, ADW_TYPE_PREFERENCES_WINDOW)
|
||||||
|
|
||||||
typedef enum
|
typedef enum
|
||||||
|
@ -140,6 +147,19 @@ mct_restrict_applications_dialog_dispose (GObject *object)
|
||||||
G_OBJECT_CLASS (mct_restrict_applications_dialog_parent_class)->dispose (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
|
static void
|
||||||
mct_restrict_applications_dialog_class_init (MctRestrictApplicationsDialogClass *klass)
|
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->set_property = mct_restrict_applications_dialog_set_property;
|
||||||
object_class->dispose = mct_restrict_applications_dialog_dispose;
|
object_class->dispose = mct_restrict_applications_dialog_dispose;
|
||||||
|
|
||||||
|
widget_class->map = mct_restrict_applications_dialog_map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MctRestrictApplicationsDialog:app-filter: (not nullable)
|
* 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, selector);
|
||||||
gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsDialog, group);
|
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
|
static void
|
||||||
|
@ -206,6 +237,8 @@ mct_restrict_applications_dialog_init (MctRestrictApplicationsDialog *self)
|
||||||
g_type_ensure (MCT_TYPE_RESTRICT_APPLICATIONS_SELECTOR);
|
g_type_ensure (MCT_TYPE_RESTRICT_APPLICATIONS_SELECTOR);
|
||||||
|
|
||||||
gtk_widget_init_template (GTK_WIDGET (self));
|
gtk_widget_init_template (GTK_WIDGET (self));
|
||||||
|
|
||||||
|
gtk_search_entry_set_key_capture_widget (self->search_entry, GTK_WIDGET (self));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -225,6 +258,25 @@ update_description (MctRestrictApplicationsDialog *self)
|
||||||
adw_preferences_group_set_description (self->group, description);
|
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:
|
* mct_restrict_applications_dialog_new:
|
||||||
* @app_filter: (transfer none): the initial app filter configuration to show
|
* @app_filter: (transfer none): the initial app filter configuration to show
|
||||||
|
|
|
@ -14,7 +14,21 @@
|
||||||
<!-- Translated dynamically: -->
|
<!-- Translated dynamically: -->
|
||||||
<property name="description">Restrict {username} from using the following installed applications.</property>
|
<property name="description">Restrict {username} from using the following installed applications.</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="MctRestrictApplicationsSelector" id="selector" />
|
<object class="GtkBox">
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="spacing">12</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSearchEntry" id="search_entry">
|
||||||
|
<property name="placeholder-text" translatable="yes">Search for applications…</property>
|
||||||
|
<signal name="stop-search" handler="search_entry_stop_search_cb" />
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="MctRestrictApplicationsSelector" id="selector">
|
||||||
|
<property name="search" bind-source="search_entry" bind-property="text" />
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
|
|
@ -41,6 +41,7 @@ static void app_info_changed_cb (GAppInfoMonitor *monitor,
|
||||||
static void reload_apps (MctRestrictApplicationsSelector *self);
|
static void reload_apps (MctRestrictApplicationsSelector *self);
|
||||||
static GtkWidget *create_row_for_app_cb (gpointer item,
|
static GtkWidget *create_row_for_app_cb (gpointer item,
|
||||||
gpointer user_data);
|
gpointer user_data);
|
||||||
|
static char *app_info_dup_name (GAppInfo *app_info);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MctRestrictApplicationsSelector:
|
* MctRestrictApplicationsSelector:
|
||||||
|
@ -54,6 +55,11 @@ static GtkWidget *create_row_for_app_cb (gpointer item,
|
||||||
* #MctAppFilterBuilder using
|
* #MctAppFilterBuilder using
|
||||||
* mct_restrict_applications_selector_build_app_filter().
|
* 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
|
* Since: 0.5.0
|
||||||
*/
|
*/
|
||||||
struct _MctRestrictApplicationsSelector
|
struct _MctRestrictApplicationsSelector
|
||||||
|
@ -64,6 +70,8 @@ struct _MctRestrictApplicationsSelector
|
||||||
|
|
||||||
GList *cached_apps; /* (nullable) (owned) (element-type GAppInfo) */
|
GList *cached_apps; /* (nullable) (owned) (element-type GAppInfo) */
|
||||||
GListStore *apps;
|
GListStore *apps;
|
||||||
|
GtkFilterListModel *filtered_apps;
|
||||||
|
GtkStringFilter *search_filter;
|
||||||
GAppInfoMonitor *app_info_monitor; /* (owned) */
|
GAppInfoMonitor *app_info_monitor; /* (owned) */
|
||||||
gulong app_info_monitor_changed_id;
|
gulong app_info_monitor_changed_id;
|
||||||
GHashTable *blocklisted_apps; /* (owned) (element-type GAppInfo) */
|
GHashTable *blocklisted_apps; /* (owned) (element-type GAppInfo) */
|
||||||
|
@ -74,6 +82,8 @@ struct _MctRestrictApplicationsSelector
|
||||||
FlatpakInstallation *user_installation; /* (owned) */
|
FlatpakInstallation *user_installation; /* (owned) */
|
||||||
|
|
||||||
GtkCssProvider *css_provider; /* (owned) */
|
GtkCssProvider *css_provider; /* (owned) */
|
||||||
|
|
||||||
|
gchar *search; /* (nullable) (owned) */
|
||||||
};
|
};
|
||||||
|
|
||||||
G_DEFINE_TYPE (MctRestrictApplicationsSelector, mct_restrict_applications_selector, GTK_TYPE_BOX)
|
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
|
typedef enum
|
||||||
{
|
{
|
||||||
PROP_APP_FILTER = 1,
|
PROP_APP_FILTER = 1,
|
||||||
|
PROP_SEARCH,
|
||||||
} MctRestrictApplicationsSelectorProperty;
|
} MctRestrictApplicationsSelectorProperty;
|
||||||
|
|
||||||
static GParamSpec *properties[PROP_APP_FILTER + 1];
|
static GParamSpec *properties[PROP_SEARCH + 1];
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
SIGNAL_CHANGED,
|
SIGNAL_CHANGED,
|
||||||
|
@ -124,6 +135,9 @@ mct_restrict_applications_selector_get_property (GObject *object,
|
||||||
case PROP_APP_FILTER:
|
case PROP_APP_FILTER:
|
||||||
g_value_set_boxed (value, self->app_filter);
|
g_value_set_boxed (value, self->app_filter);
|
||||||
break;
|
break;
|
||||||
|
case PROP_SEARCH:
|
||||||
|
g_value_set_string (value, self->search);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
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:
|
case PROP_APP_FILTER:
|
||||||
mct_restrict_applications_selector_set_app_filter (self, g_value_get_boxed (value));
|
mct_restrict_applications_selector_set_app_filter (self, g_value_get_boxed (value));
|
||||||
break;
|
break;
|
||||||
|
case PROP_SEARCH:
|
||||||
|
mct_restrict_applications_selector_set_search (self, g_value_get_string (value));
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
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->system_installation);
|
||||||
g_clear_object (&self->user_installation);
|
g_clear_object (&self->user_installation);
|
||||||
g_clear_object (&self->css_provider);
|
g_clear_object (&self->css_provider);
|
||||||
|
g_clear_pointer (&self->search, g_free);
|
||||||
|
|
||||||
G_OBJECT_CLASS (mct_restrict_applications_selector_parent_class)->dispose (object);
|
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_STATIC_STRINGS |
|
||||||
G_PARAM_EXPLICIT_NOTIFY);
|
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);
|
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, listbox);
|
||||||
gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsSelector, apps);
|
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
|
static void
|
||||||
mct_restrict_applications_selector_init (MctRestrictApplicationsSelector *self)
|
mct_restrict_applications_selector_init (MctRestrictApplicationsSelector *self)
|
||||||
{
|
{
|
||||||
guint n_apps;
|
|
||||||
|
|
||||||
gtk_widget_init_template (GTK_WIDGET (self));
|
gtk_widget_init_template (GTK_WIDGET (self));
|
||||||
|
|
||||||
|
@ -238,7 +276,7 @@ mct_restrict_applications_selector_init (MctRestrictApplicationsSelector *self)
|
||||||
(GCallback) app_info_changed_cb, self);
|
(GCallback) app_info_changed_cb, self);
|
||||||
|
|
||||||
gtk_list_box_bind_model (self->listbox,
|
gtk_list_box_bind_model (self->listbox,
|
||||||
G_LIST_MODEL (self->apps),
|
G_LIST_MODEL (self->filtered_apps),
|
||||||
create_row_for_app_cb,
|
create_row_for_app_cb,
|
||||||
self,
|
self,
|
||||||
NULL);
|
NULL);
|
||||||
|
@ -359,6 +397,12 @@ create_row_for_app_cb (gpointer item,
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static char *
|
||||||
|
app_info_dup_name (GAppInfo *app_info)
|
||||||
|
{
|
||||||
|
return g_strdup (g_app_info_get_name (app_info));
|
||||||
|
}
|
||||||
|
|
||||||
static gint
|
static gint
|
||||||
compare_app_info_cb (gconstpointer a,
|
compare_app_info_cb (gconstpointer a,
|
||||||
gconstpointer b,
|
gconstpointer b,
|
||||||
|
@ -767,7 +811,7 @@ mct_restrict_applications_selector_set_app_filter (MctRestrictApplicationsSelect
|
||||||
self->app_filter = mct_app_filter_ref (app_filter);
|
self->app_filter = mct_app_filter_ref (app_filter);
|
||||||
|
|
||||||
/* Update the status of each app row. */
|
/* 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++)
|
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]);
|
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]);
|
||||||
|
}
|
||||||
|
|
|
@ -41,4 +41,8 @@ void mct_restrict_applications_selector_set_app_filter (MctRestrictAppl
|
||||||
void mct_restrict_applications_selector_build_app_filter (MctRestrictApplicationsSelector *self,
|
void mct_restrict_applications_selector_build_app_filter (MctRestrictApplicationsSelector *self,
|
||||||
MctAppFilterBuilder *builder);
|
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
|
G_END_DECLS
|
||||||
|
|
|
@ -26,4 +26,15 @@
|
||||||
<object class="GListStore" id="apps">
|
<object class="GListStore" id="apps">
|
||||||
<property name="item-type">GAppInfo</property>
|
<property name="item-type">GAppInfo</property>
|
||||||
</object>
|
</object>
|
||||||
|
|
||||||
|
<object class="GtkFilterListModel" id="filtered_apps">
|
||||||
|
<property name="model">apps</property>
|
||||||
|
<property name="filter">search_filter</property>
|
||||||
|
</object>
|
||||||
|
|
||||||
|
<object class="GtkStringFilter" id="search_filter">
|
||||||
|
<property name="expression">
|
||||||
|
<closure type="gchararray" function="app_info_dup_name" />
|
||||||
|
</property>
|
||||||
|
</object>
|
||||||
</interface>
|
</interface>
|
||||||
|
|
Loading…
Reference in New Issue