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;
|
||||
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
|
||||
|
|
|
@ -14,7 +14,21 @@
|
|||
<!-- Translated dynamically: -->
|
||||
<property name="description">Restrict {username} from using the following installed applications.</property>
|
||||
<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>
|
||||
</object>
|
||||
</child>
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -26,4 +26,15 @@
|
|||
<object class="GListStore" id="apps">
|
||||
<property name="item-type">GAppInfo</property>
|
||||
</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>
|
||||
|
|
Loading…
Reference in New Issue