Branch data Line data Source code
1 : : /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2 : : *
3 : : * Copyright © 2020 Endless Mobile, Inc.
4 : : *
5 : : * SPDX-License-Identifier: GPL-2.0-or-later
6 : : *
7 : : * This program is free software; you can redistribute it and/or modify
8 : : * it under the terms of the GNU General Public License as published by
9 : : * the Free Software Foundation; either version 2 of the License, or
10 : : * (at your option) any later version.
11 : : *
12 : : * This program is distributed in the hope that it will be useful,
13 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 : : * GNU General Public License for more details.
16 : : *
17 : : * You should have received a copy of the GNU General Public License
18 : : * along with this program; if not, see <http://www.gnu.org/licenses/>.
19 : : *
20 : : * Authors:
21 : : * - Philip Withnall <withnall@endlessm.com>
22 : : */
23 : :
24 : : #include "config.h"
25 : :
26 : : #include <gio/gio.h>
27 : : #include <glib.h>
28 : : #include <glib-object.h>
29 : : #include <glib/gi18n-lib.h>
30 : : #include <gtk/gtk.h>
31 : : #include <adwaita.h>
32 : :
33 : : #include "restrict-applications-dialog.h"
34 : : #include "restrict-applications-selector.h"
35 : :
36 : :
37 : : static void update_description (MctRestrictApplicationsDialog *self);
38 : :
39 : : /**
40 : : * MctRestrictApplicationsDialog:
41 : : *
42 : : * The ‘Restrict Applications’ dialog is a dialog which shows the available
43 : : * applications on the system alongside a column of toggle switches, which
44 : : * allows the given user to be prevented from running each application.
45 : : *
46 : : * The dialog contains a single
47 : : * [class@MalcontentUi.RestrictApplicationsSelector]. It takes a
48 : : * [property@MalcontentUi.RestrictApplicationsDialog:app-filter] as input to set
49 : : * up the UI, and returns its output as set of modifications to a given
50 : : * [type@Malcontent.AppFilterBuilder] using
51 : : * [method@MalcontentUi.RestrictApplicationsDialog.build_app_filter].
52 : : *
53 : : * Since: 0.5.0
54 : : */
55 : : struct _MctRestrictApplicationsDialog
56 : : {
57 : : AdwPreferencesDialog parent_instance;
58 : :
59 : : MctRestrictApplicationsSelector *selector;
60 : : AdwPreferencesGroup *group;
61 : : GtkSearchEntry *search_entry;
62 : :
63 : : MctAppFilter *app_filter; /* (owned) (not nullable) */
64 : : gchar *user_display_name; /* (owned) (nullable) */
65 : : };
66 : :
67 : : static void search_entry_stop_search_cb (GtkSearchEntry *search_entry,
68 : : gpointer user_data);
69 : : static gboolean focus_search_cb (GtkWidget *widget,
70 : : GVariant *arguments,
71 : : gpointer user_data);
72 : :
73 [ + + + - : 3 : G_DEFINE_TYPE (MctRestrictApplicationsDialog, mct_restrict_applications_dialog, ADW_TYPE_PREFERENCES_DIALOG)
+ - ]
74 : :
75 : : typedef enum
76 : : {
77 : : PROP_APP_FILTER = 1,
78 : : PROP_USER_DISPLAY_NAME,
79 : : } MctRestrictApplicationsDialogProperty;
80 : :
81 : : static GParamSpec *properties[PROP_USER_DISPLAY_NAME + 1];
82 : :
83 : : static void
84 : 0 : mct_restrict_applications_dialog_constructed (GObject *obj)
85 : : {
86 : 0 : MctRestrictApplicationsDialog *self = MCT_RESTRICT_APPLICATIONS_DIALOG (obj);
87 : :
88 : 0 : g_assert (self->app_filter != NULL);
89 : 0 : g_assert (self->user_display_name == NULL ||
90 : : (*self->user_display_name != '\0' &&
91 : : g_utf8_validate (self->user_display_name, -1, NULL)));
92 : :
93 : 0 : G_OBJECT_CLASS (mct_restrict_applications_dialog_parent_class)->constructed (obj);
94 : 0 : }
95 : :
96 : : static void
97 : 0 : mct_restrict_applications_dialog_get_property (GObject *object,
98 : : guint prop_id,
99 : : GValue *value,
100 : : GParamSpec *pspec)
101 : : {
102 : 0 : MctRestrictApplicationsDialog *self = MCT_RESTRICT_APPLICATIONS_DIALOG (object);
103 : :
104 [ # # # ]: 0 : switch ((MctRestrictApplicationsDialogProperty) prop_id)
105 : : {
106 : 0 : case PROP_APP_FILTER:
107 : 0 : g_value_set_boxed (value, self->app_filter);
108 : 0 : break;
109 : :
110 : 0 : case PROP_USER_DISPLAY_NAME:
111 : 0 : g_value_set_string (value, self->user_display_name);
112 : 0 : break;
113 : :
114 : 0 : default:
115 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
116 : : }
117 : 0 : }
118 : :
119 : : static void
120 : 0 : mct_restrict_applications_dialog_set_property (GObject *object,
121 : : guint prop_id,
122 : : const GValue *value,
123 : : GParamSpec *pspec)
124 : : {
125 : 0 : MctRestrictApplicationsDialog *self = MCT_RESTRICT_APPLICATIONS_DIALOG (object);
126 : :
127 [ # # # ]: 0 : switch ((MctRestrictApplicationsDialogProperty) prop_id)
128 : : {
129 : 0 : case PROP_APP_FILTER:
130 : 0 : mct_restrict_applications_dialog_set_app_filter (self, g_value_get_boxed (value));
131 : 0 : break;
132 : :
133 : 0 : case PROP_USER_DISPLAY_NAME:
134 : 0 : mct_restrict_applications_dialog_set_user_display_name (self, g_value_get_string (value));
135 : 0 : break;
136 : :
137 : 0 : default:
138 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
139 : : }
140 : 0 : }
141 : :
142 : : static void
143 : 0 : mct_restrict_applications_dialog_dispose (GObject *object)
144 : : {
145 : 0 : MctRestrictApplicationsDialog *self = (MctRestrictApplicationsDialog *)object;
146 : :
147 [ # # ]: 0 : g_clear_pointer (&self->app_filter, mct_app_filter_unref);
148 [ # # ]: 0 : g_clear_pointer (&self->user_display_name, g_free);
149 : :
150 : 0 : G_OBJECT_CLASS (mct_restrict_applications_dialog_parent_class)->dispose (object);
151 : 0 : }
152 : :
153 : : static void
154 : 0 : mct_restrict_applications_dialog_map (GtkWidget *widget)
155 : : {
156 : 0 : MctRestrictApplicationsDialog *self = (MctRestrictApplicationsDialog *)widget;
157 : :
158 : 0 : GTK_WIDGET_CLASS (mct_restrict_applications_dialog_parent_class)->map (widget);
159 : :
160 : : /* Clear and focus the search entry, in case the dialogue is being shown for
161 : : * a second time. */
162 : 0 : gtk_editable_set_text (GTK_EDITABLE (self->search_entry), "");
163 : 0 : gtk_widget_grab_focus (GTK_WIDGET (self->search_entry));
164 : 0 : }
165 : :
166 : : static void
167 : 1 : mct_restrict_applications_dialog_class_init (MctRestrictApplicationsDialogClass *klass)
168 : : {
169 : 1 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
170 : 1 : GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
171 : :
172 : 1 : object_class->constructed = mct_restrict_applications_dialog_constructed;
173 : 1 : object_class->get_property = mct_restrict_applications_dialog_get_property;
174 : 1 : object_class->set_property = mct_restrict_applications_dialog_set_property;
175 : 1 : object_class->dispose = mct_restrict_applications_dialog_dispose;
176 : :
177 : 1 : widget_class->map = mct_restrict_applications_dialog_map;
178 : :
179 : : /**
180 : : * MctRestrictApplicationsDialog:app-filter: (not nullable)
181 : : *
182 : : * The user’s current app filter, used to set up the dialog.
183 : : *
184 : : * As app filters are immutable, it is not updated as the dialog is changed.
185 : : * Use [method@MalcontentUi.RestrictApplicationsDialog.build_app_filter] to
186 : : * build the new app filter.
187 : : *
188 : : * Since: 0.5.0
189 : : */
190 : 1 : properties[PROP_APP_FILTER] =
191 : 1 : g_param_spec_boxed ("app-filter", NULL, NULL,
192 : : MCT_TYPE_APP_FILTER,
193 : : G_PARAM_READWRITE |
194 : : G_PARAM_CONSTRUCT_ONLY |
195 : : G_PARAM_STATIC_STRINGS |
196 : : G_PARAM_EXPLICIT_NOTIFY);
197 : :
198 : : /**
199 : : * MctRestrictApplicationsDialog:user-display-name: (nullable)
200 : : *
201 : : * The display name for the currently selected user account, or `NULL` if no
202 : : * user is selected.
203 : : *
204 : : * This will typically be the user’s full name (if known) or their username.
205 : : *
206 : : * If set, it must be valid UTF-8 and non-empty.
207 : : *
208 : : * Since: 0.5.0
209 : : */
210 : 1 : properties[PROP_USER_DISPLAY_NAME] =
211 : 1 : g_param_spec_string ("user-display-name", NULL, NULL,
212 : : NULL,
213 : : G_PARAM_READWRITE |
214 : : G_PARAM_STATIC_STRINGS |
215 : : G_PARAM_EXPLICIT_NOTIFY);
216 : :
217 : 1 : g_object_class_install_properties (object_class, G_N_ELEMENTS (properties), properties);
218 : :
219 : 1 : gtk_widget_class_set_template_from_resource (widget_class, "/org/freedesktop/MalcontentUi/ui/restrict-applications-dialog.ui");
220 : :
221 : 1 : gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsDialog, selector);
222 : 1 : gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsDialog, group);
223 : 1 : gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsDialog, search_entry);
224 : :
225 : 1 : gtk_widget_class_bind_template_callback (widget_class, search_entry_stop_search_cb);
226 : :
227 : 1 : gtk_widget_class_add_binding (widget_class,
228 : : GDK_KEY_f, GDK_CONTROL_MASK,
229 : : focus_search_cb,
230 : : NULL);
231 : :
232 : 1 : }
233 : :
234 : : static void
235 : 0 : mct_restrict_applications_dialog_init (MctRestrictApplicationsDialog *self)
236 : : {
237 : : /* Ensure the types used in the UI are registered. */
238 : 0 : g_type_ensure (MCT_TYPE_RESTRICT_APPLICATIONS_SELECTOR);
239 : :
240 : 0 : gtk_widget_init_template (GTK_WIDGET (self));
241 : :
242 : 0 : gtk_search_entry_set_key_capture_widget (self->search_entry, GTK_WIDGET (self));
243 : 0 : }
244 : :
245 : : static void
246 : 0 : update_description (MctRestrictApplicationsDialog *self)
247 : : {
248 [ # # ]: 0 : g_autofree gchar *description = NULL;
249 : :
250 [ # # ]: 0 : if (self->user_display_name == NULL)
251 : : {
252 : 0 : adw_preferences_group_set_description (self->group, NULL);
253 : 0 : return;
254 : : }
255 : :
256 : : /* Translators: the placeholder is a user’s full name */
257 : 0 : description = g_strdup_printf (_("Restrict %s from using the following installed applications."),
258 : : self->user_display_name);
259 : 0 : adw_preferences_group_set_description (self->group, description);
260 : : }
261 : :
262 : : static void
263 : 0 : search_entry_stop_search_cb (GtkSearchEntry *search_entry,
264 : : gpointer user_data)
265 : : {
266 : : /* Clear the search text as the search filtering is bound to that. */
267 : 0 : gtk_editable_set_text (GTK_EDITABLE (search_entry), "");
268 : 0 : }
269 : :
270 : : static gboolean
271 : 0 : focus_search_cb (GtkWidget *widget,
272 : : GVariant *arguments,
273 : : gpointer user_data)
274 : : {
275 : 0 : MctRestrictApplicationsDialog *self = MCT_RESTRICT_APPLICATIONS_DIALOG (widget);
276 : :
277 : 0 : gtk_widget_grab_focus (GTK_WIDGET (self->search_entry));
278 : 0 : return TRUE;
279 : : }
280 : :
281 : : /**
282 : : * mct_restrict_applications_dialog_new:
283 : : * @app_filter: (transfer none): the initial app filter configuration to show
284 : : * @user_display_name: (transfer none) (nullable): the display name of the user
285 : : * to show the app filter for, or `NULL` if no user is selected
286 : : *
287 : : * Create a new [class@MalcontentUi.RestrictApplicationsDialog] widget.
288 : : *
289 : : * Returns: (transfer full): a new restricted applications editing dialog
290 : : * Since: 0.5.0
291 : : */
292 : : MctRestrictApplicationsDialog *
293 : 0 : mct_restrict_applications_dialog_new (MctAppFilter *app_filter,
294 : : const gchar *user_display_name)
295 : : {
296 : 0 : g_return_val_if_fail (app_filter != NULL, NULL);
297 : 0 : g_return_val_if_fail (user_display_name == NULL ||
298 : : (*user_display_name != '\0' &&
299 : : g_utf8_validate (user_display_name, -1, NULL)), NULL);
300 : :
301 : 0 : return g_object_new (MCT_TYPE_RESTRICT_APPLICATIONS_DIALOG,
302 : : "app-filter", app_filter,
303 : : "user-display-name", user_display_name,
304 : : NULL);
305 : : }
306 : :
307 : : /**
308 : : * mct_restrict_applications_dialog_get_app_filter:
309 : : * @self: a restricted applications editing dialog
310 : : *
311 : : * Get the value of
312 : : * [property@MalcontentUi.RestrictApplicationsDialog:app-filter].
313 : : *
314 : : * If the property was originally set to `NULL`, this will be the empty app
315 : : * filter.
316 : : *
317 : : * Returns: (transfer none) (not nullable): the initial app filter used to
318 : : * populate the dialog
319 : : * Since: 0.5.0
320 : : */
321 : : MctAppFilter *
322 : 0 : mct_restrict_applications_dialog_get_app_filter (MctRestrictApplicationsDialog *self)
323 : : {
324 : 0 : g_return_val_if_fail (MCT_IS_RESTRICT_APPLICATIONS_DIALOG (self), NULL);
325 : :
326 : 0 : return self->app_filter;
327 : : }
328 : :
329 : : /**
330 : : * mct_restrict_applications_dialog_set_app_filter:
331 : : * @self: a restricted applications editing dialog
332 : : * @app_filter: (nullable) (transfer none): the app filter to configure the
333 : : * dialog from, or `NULL` to use an empty app filter
334 : : *
335 : : * Set the value of
336 : : * [property@MalcontentUi.RestrictApplicationsDialog:app-filter].
337 : : *
338 : : * Since: 0.5.0
339 : : */
340 : : void
341 : 0 : mct_restrict_applications_dialog_set_app_filter (MctRestrictApplicationsDialog *self,
342 : : MctAppFilter *app_filter)
343 : : {
344 [ # # ]: 0 : g_autoptr(MctAppFilter) owned_app_filter = NULL;
345 : :
346 : 0 : g_return_if_fail (MCT_IS_RESTRICT_APPLICATIONS_DIALOG (self));
347 : :
348 : : /* Default app filter, typically for when we’re instantiated by #GtkBuilder. */
349 [ # # ]: 0 : if (app_filter == NULL)
350 : : {
351 : 0 : g_auto(MctAppFilterBuilder) builder = MCT_APP_FILTER_BUILDER_INIT ();
352 : 0 : owned_app_filter = mct_app_filter_builder_end (&builder);
353 : 0 : app_filter = owned_app_filter;
354 : : }
355 : :
356 [ # # ]: 0 : if (app_filter == self->app_filter)
357 : 0 : return;
358 : :
359 [ # # ]: 0 : g_clear_pointer (&self->app_filter, mct_app_filter_unref);
360 : 0 : self->app_filter = mct_app_filter_ref (app_filter);
361 : :
362 : 0 : mct_restrict_applications_selector_set_app_filter (self->selector, self->app_filter);
363 : :
364 : 0 : g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_APP_FILTER]);
365 : : }
366 : :
367 : : /**
368 : : * mct_restrict_applications_dialog_get_user_display_name:
369 : : * @self: a restricted applications editing dialog
370 : : *
371 : : * Get the value of
372 : : * [property@MalcontentUi.RestrictApplicationsDialog:user-display-name].
373 : : *
374 : : * Returns: (transfer none) (nullable): the display name of the user the dialog
375 : : * is configured for, or `NULL` if unknown
376 : : * Since: 0.5.0
377 : : */
378 : : const gchar *
379 : 0 : mct_restrict_applications_dialog_get_user_display_name (MctRestrictApplicationsDialog *self)
380 : : {
381 : 0 : g_return_val_if_fail (MCT_IS_RESTRICT_APPLICATIONS_DIALOG (self), NULL);
382 : :
383 : 0 : return self->user_display_name;
384 : : }
385 : :
386 : : /**
387 : : * mct_restrict_applications_dialog_set_user_display_name:
388 : : * @self: a restricted applications editing dialog
389 : : * @user_display_name: (nullable) (transfer none): the display name of the user
390 : : * to configure the dialog for, or `NULL` if unknown
391 : : *
392 : : * Set the value of
393 : : * [property@MalcontentUi.RestrictApplicationsDialog:user-display-name].
394 : : *
395 : : * Since: 0.5.0
396 : : */
397 : : void
398 : 0 : mct_restrict_applications_dialog_set_user_display_name (MctRestrictApplicationsDialog *self,
399 : : const gchar *user_display_name)
400 : : {
401 : 0 : g_return_if_fail (MCT_IS_RESTRICT_APPLICATIONS_DIALOG (self));
402 : 0 : g_return_if_fail (user_display_name == NULL ||
403 : : (*user_display_name != '\0' &&
404 : : g_utf8_validate (user_display_name, -1, NULL)));
405 : :
406 [ # # ]: 0 : if (g_strcmp0 (self->user_display_name, user_display_name) == 0)
407 : 0 : return;
408 : :
409 [ # # ]: 0 : g_clear_pointer (&self->user_display_name, g_free);
410 : 0 : self->user_display_name = g_strdup (user_display_name);
411 : :
412 : 0 : update_description (self);
413 : 0 : g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_USER_DISPLAY_NAME]);
414 : : }
415 : :
416 : : /**
417 : : * mct_restrict_applications_dialog_build_app_filter:
418 : : * @self: a restricted applications editing dialog
419 : : * @builder: an existing [type@Malcontent.AppFilterBuilder] to modify
420 : : *
421 : : * Get the app filter settings currently configured in the dialog, by modifying
422 : : * the given @builder.
423 : : *
424 : : * Typically this will be called in the handler for
425 : : * [signal@Gtk.Dialog::response].
426 : : *
427 : : * Since: 0.5.0
428 : : */
429 : : void
430 : 0 : mct_restrict_applications_dialog_build_app_filter (MctRestrictApplicationsDialog *self,
431 : : MctAppFilterBuilder *builder)
432 : : {
433 : 0 : g_return_if_fail (MCT_IS_RESTRICT_APPLICATIONS_DIALOG (self));
434 : 0 : g_return_if_fail (builder != NULL);
435 : :
436 : 0 : mct_restrict_applications_selector_build_app_filter (self->selector, builder);
437 : : }
|