1 | /* GTK - The GIMP Toolkit |
2 | * Copyright (C) 2010 Red Hat, Inc. |
3 | * Author: Matthias Clasen |
4 | * |
5 | * This library is free software; you can redistribute it and/or |
6 | * modify it under the terms of the GNU Library General Public |
7 | * License as published by the Free Software Foundation; either |
8 | * version 2 of the License, or (at your option) any later version. |
9 | * |
10 | * This library is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | * Library General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU Library General Public |
16 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
17 | */ |
18 | |
19 | #include "config.h" |
20 | |
21 | #include "gtklockbuttonprivate.h" |
22 | |
23 | #include "gtkbox.h" |
24 | #include "gtkimage.h" |
25 | #include "gtkintl.h" |
26 | #include "gtklabel.h" |
27 | #include "gtksizegroup.h" |
28 | #include "gtkstack.h" |
29 | |
30 | /** |
31 | * GtkLockButton: |
32 | * |
33 | * `GtkLockButton` is a widget to obtain and revoke authorizations |
34 | * needed to operate the controls. |
35 | * |
36 | * ![An example GtkLockButton](lock-button.png) |
37 | * |
38 | * It is typically used in preference dialogs or control panels. |
39 | * |
40 | * The required authorization is represented by a `GPermission` object. |
41 | * Concrete implementations of `GPermission` may use PolicyKit or some |
42 | * other authorization framework. To obtain a PolicyKit-based |
43 | * `GPermission`, use `polkit_permission_new()`. |
44 | * |
45 | * If the user is not currently allowed to perform the action, but can |
46 | * obtain the permission, the widget looks like this: |
47 | * |
48 | * ![](lockbutton-locked.png) |
49 | * |
50 | * and the user can click the button to request the permission. Depending |
51 | * on the platform, this may pop up an authentication dialog or ask the user |
52 | * to authenticate in some other way. Once the user has obtained the permission, |
53 | * the widget changes to this: |
54 | * |
55 | * ![](lockbutton-unlocked.png) |
56 | * |
57 | * and the permission can be dropped again by clicking the button. If the user |
58 | * is not able to obtain the permission at all, the widget looks like this: |
59 | * |
60 | * ![](lockbutton-sorry.png) |
61 | * |
62 | * If the user has the permission and cannot drop it, the button is hidden. |
63 | * |
64 | * The text (and tooltips) that are shown in the various cases can be adjusted |
65 | * with the [property@Gtk.LockButton:text-lock], |
66 | * [property@Gtk.LockButton:text-unlock], |
67 | * [property@Gtk.LockButton:tooltip-lock], |
68 | * [property@Gtk.LockButton:tooltip-unlock] and |
69 | * [property@Gtk.LockButton:tooltip-not-authorized] properties. |
70 | */ |
71 | |
72 | struct _GtkLockButton |
73 | { |
74 | GtkButton parent_instance; |
75 | |
76 | GPermission *permission; |
77 | GCancellable *cancellable; |
78 | |
79 | char *tooltip_lock; |
80 | char *tooltip_unlock; |
81 | char *tooltip_not_authorized; |
82 | GIcon *icon_lock; |
83 | GIcon *icon_unlock; |
84 | |
85 | GtkWidget *box; |
86 | GtkWidget *image; |
87 | GtkWidget *stack; |
88 | GtkWidget *label_lock; |
89 | GtkWidget *label_unlock; |
90 | }; |
91 | |
92 | typedef struct _GtkLockButtonClass GtkLockButtonClass; |
93 | struct _GtkLockButtonClass |
94 | { |
95 | GtkButtonClass parent_class; |
96 | }; |
97 | |
98 | enum |
99 | { |
100 | PROP_0, |
101 | PROP_PERMISSION, |
102 | PROP_TEXT_LOCK, |
103 | PROP_TEXT_UNLOCK, |
104 | PROP_TOOLTIP_LOCK, |
105 | PROP_TOOLTIP_UNLOCK, |
106 | PROP_TOOLTIP_NOT_AUTHORIZED |
107 | }; |
108 | |
109 | static void update_state (GtkLockButton *button); |
110 | static void gtk_lock_button_clicked (GtkButton *button); |
111 | |
112 | static void on_permission_changed (GPermission *permission, |
113 | GParamSpec *pspec, |
114 | gpointer user_data); |
115 | |
116 | G_DEFINE_TYPE (GtkLockButton, gtk_lock_button, GTK_TYPE_BUTTON) |
117 | |
118 | static void |
119 | gtk_lock_button_finalize (GObject *object) |
120 | { |
121 | GtkLockButton *button = GTK_LOCK_BUTTON (object); |
122 | |
123 | g_free (mem: button->tooltip_lock); |
124 | g_free (mem: button->tooltip_unlock); |
125 | g_free (mem: button->tooltip_not_authorized); |
126 | |
127 | g_object_unref (object: button->icon_lock); |
128 | g_object_unref (object: button->icon_unlock); |
129 | |
130 | if (button->cancellable != NULL) |
131 | { |
132 | g_cancellable_cancel (cancellable: button->cancellable); |
133 | g_object_unref (object: button->cancellable); |
134 | } |
135 | |
136 | if (button->permission) |
137 | { |
138 | g_signal_handlers_disconnect_by_func (button->permission, |
139 | on_permission_changed, |
140 | button); |
141 | g_object_unref (object: button->permission); |
142 | } |
143 | |
144 | G_OBJECT_CLASS (gtk_lock_button_parent_class)->finalize (object); |
145 | } |
146 | |
147 | static void |
148 | gtk_lock_button_get_property (GObject *object, |
149 | guint property_id, |
150 | GValue *value, |
151 | GParamSpec *pspec) |
152 | { |
153 | GtkLockButton *button = GTK_LOCK_BUTTON (object); |
154 | |
155 | switch (property_id) |
156 | { |
157 | case PROP_PERMISSION: |
158 | g_value_set_object (value, v_object: button->permission); |
159 | break; |
160 | |
161 | case PROP_TEXT_LOCK: |
162 | g_value_set_string (value, v_string: gtk_label_get_text (GTK_LABEL (button->label_lock))); |
163 | break; |
164 | |
165 | case PROP_TEXT_UNLOCK: |
166 | g_value_set_string (value, v_string: gtk_label_get_text (GTK_LABEL (button->label_unlock))); |
167 | break; |
168 | |
169 | case PROP_TOOLTIP_LOCK: |
170 | g_value_set_string (value, v_string: button->tooltip_lock); |
171 | break; |
172 | |
173 | case PROP_TOOLTIP_UNLOCK: |
174 | g_value_set_string (value, v_string: button->tooltip_unlock); |
175 | break; |
176 | |
177 | case PROP_TOOLTIP_NOT_AUTHORIZED: |
178 | g_value_set_string (value, v_string: button->tooltip_not_authorized); |
179 | break; |
180 | |
181 | default: |
182 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); |
183 | break; |
184 | } |
185 | } |
186 | |
187 | static void |
188 | gtk_lock_button_set_property (GObject *object, |
189 | guint property_id, |
190 | const GValue *value, |
191 | GParamSpec *pspec) |
192 | { |
193 | GtkLockButton *button = GTK_LOCK_BUTTON (object); |
194 | |
195 | switch (property_id) |
196 | { |
197 | case PROP_PERMISSION: |
198 | gtk_lock_button_set_permission (button, permission: g_value_get_object (value)); |
199 | break; |
200 | |
201 | case PROP_TEXT_LOCK: |
202 | gtk_label_set_text (GTK_LABEL (button->label_lock), str: g_value_get_string (value)); |
203 | break; |
204 | |
205 | case PROP_TEXT_UNLOCK: |
206 | gtk_label_set_text (GTK_LABEL (button->label_unlock), str: g_value_get_string (value)); |
207 | break; |
208 | |
209 | case PROP_TOOLTIP_LOCK: |
210 | g_free (mem: button->tooltip_lock); |
211 | button->tooltip_lock = g_value_dup_string (value); |
212 | break; |
213 | |
214 | case PROP_TOOLTIP_UNLOCK: |
215 | g_free (mem: button->tooltip_unlock); |
216 | button->tooltip_unlock = g_value_dup_string (value); |
217 | break; |
218 | |
219 | case PROP_TOOLTIP_NOT_AUTHORIZED: |
220 | g_free (mem: button->tooltip_not_authorized); |
221 | button->tooltip_not_authorized = g_value_dup_string (value); |
222 | break; |
223 | |
224 | default: |
225 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); |
226 | break; |
227 | } |
228 | |
229 | update_state (button); |
230 | } |
231 | |
232 | static void |
233 | gtk_lock_button_init (GtkLockButton *button) |
234 | { |
235 | const char *names[3]; |
236 | |
237 | gtk_widget_init_template (GTK_WIDGET (button)); |
238 | |
239 | names[0] = "changes-allow-symbolic" ; |
240 | names[1] = "changes-allow" ; |
241 | names[2] = NULL; |
242 | button->icon_unlock = g_themed_icon_new_from_names (iconnames: (char **) names, len: -1); |
243 | |
244 | names[0] = "changes-prevent-symbolic" ; |
245 | names[1] = "changes-prevent" ; |
246 | names[2] = NULL; |
247 | button->icon_lock = g_themed_icon_new_from_names (iconnames: (char **) names, len: -1); |
248 | |
249 | update_state (button); |
250 | |
251 | gtk_widget_add_css_class (GTK_WIDGET (button), I_("lock" )); |
252 | } |
253 | |
254 | static void |
255 | gtk_lock_button_class_init (GtkLockButtonClass *klass) |
256 | { |
257 | GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
258 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); |
259 | GtkButtonClass *button_class = GTK_BUTTON_CLASS (klass); |
260 | |
261 | gobject_class->finalize = gtk_lock_button_finalize; |
262 | gobject_class->get_property = gtk_lock_button_get_property; |
263 | gobject_class->set_property = gtk_lock_button_set_property; |
264 | |
265 | button_class->clicked = gtk_lock_button_clicked; |
266 | |
267 | /** |
268 | * GtkLockButton:permission: (attributes org.gtk.Property.get=gtk_lock_button_get_permission org.gtk.Property.set=gtk_lock_button_set_permission) |
269 | * |
270 | * The `GPermission object controlling this button. |
271 | */ |
272 | g_object_class_install_property (oclass: gobject_class, property_id: PROP_PERMISSION, |
273 | pspec: g_param_spec_object (name: "permission" , |
274 | P_("Permission" ), |
275 | P_("The GPermission object controlling this button" ), |
276 | G_TYPE_PERMISSION, |
277 | flags: G_PARAM_READWRITE | |
278 | G_PARAM_STATIC_STRINGS)); |
279 | |
280 | /** |
281 | * GtkLockButton:text-lock: |
282 | * |
283 | * The text to display when prompting the user to lock. |
284 | */ |
285 | g_object_class_install_property (oclass: gobject_class, property_id: PROP_TEXT_LOCK, |
286 | pspec: g_param_spec_string (name: "text-lock" , |
287 | P_("Lock Text" ), |
288 | P_("The text to display when prompting the user to lock" ), |
289 | _("Lock" ), |
290 | flags: G_PARAM_READWRITE | |
291 | G_PARAM_CONSTRUCT | |
292 | G_PARAM_STATIC_STRINGS)); |
293 | |
294 | /** |
295 | * GtkLockButton:text-unlock: |
296 | * |
297 | * The text to display when prompting the user to unlock. |
298 | */ |
299 | g_object_class_install_property (oclass: gobject_class, property_id: PROP_TEXT_UNLOCK, |
300 | pspec: g_param_spec_string (name: "text-unlock" , |
301 | P_("Unlock Text" ), |
302 | P_("The text to display when prompting the user to unlock" ), |
303 | _("Unlock" ), |
304 | flags: G_PARAM_READWRITE | |
305 | G_PARAM_CONSTRUCT | |
306 | G_PARAM_STATIC_STRINGS)); |
307 | |
308 | /** |
309 | * GtkLockButton:tooltip-lock: |
310 | * |
311 | * The tooltip to display when prompting the user to lock. |
312 | */ |
313 | g_object_class_install_property (oclass: gobject_class, property_id: PROP_TOOLTIP_LOCK, |
314 | pspec: g_param_spec_string (name: "tooltip-lock" , |
315 | P_("Lock Tooltip" ), |
316 | P_("The tooltip to display when prompting the user to lock" ), |
317 | _("Dialog is unlocked.\nClick to prevent further changes" ), |
318 | flags: G_PARAM_READWRITE | |
319 | G_PARAM_CONSTRUCT | |
320 | G_PARAM_STATIC_STRINGS)); |
321 | |
322 | /** |
323 | * GtkLockButton:tooltip-unlock: |
324 | * |
325 | * The tooltip to display when prompting the user to unlock. |
326 | */ |
327 | g_object_class_install_property (oclass: gobject_class, property_id: PROP_TOOLTIP_UNLOCK, |
328 | pspec: g_param_spec_string (name: "tooltip-unlock" , |
329 | P_("Unlock Tooltip" ), |
330 | P_("The tooltip to display when prompting the user to unlock" ), |
331 | _("Dialog is locked.\nClick to make changes" ), |
332 | flags: G_PARAM_READWRITE | |
333 | G_PARAM_CONSTRUCT | |
334 | G_PARAM_STATIC_STRINGS)); |
335 | |
336 | /** |
337 | * GtkLockButton:tooltip-not-authorized: |
338 | * |
339 | * The tooltip to display when the user cannot obtain authorization. |
340 | */ |
341 | g_object_class_install_property (oclass: gobject_class, property_id: PROP_TOOLTIP_NOT_AUTHORIZED, |
342 | pspec: g_param_spec_string (name: "tooltip-not-authorized" , |
343 | P_("Not Authorized Tooltip" ), |
344 | P_("The tooltip to display when prompting the user cannot obtain authorization" ), |
345 | _("System policy prevents changes.\nContact your system administrator" ), |
346 | flags: G_PARAM_READWRITE | |
347 | G_PARAM_CONSTRUCT | |
348 | G_PARAM_STATIC_STRINGS)); |
349 | |
350 | /* Bind class to template |
351 | */ |
352 | gtk_widget_class_set_template_from_resource (widget_class, resource_name: "/org/gtk/libgtk/ui/gtklockbutton.ui" ); |
353 | gtk_widget_class_bind_template_child (widget_class, GtkLockButton, box); |
354 | gtk_widget_class_bind_template_child (widget_class, GtkLockButton, image); |
355 | gtk_widget_class_bind_template_child (widget_class, GtkLockButton, label_lock); |
356 | gtk_widget_class_bind_template_child (widget_class, GtkLockButton, label_unlock); |
357 | gtk_widget_class_bind_template_child (widget_class, GtkLockButton, stack); |
358 | |
359 | gtk_widget_class_set_css_name (widget_class, I_("button" )); |
360 | } |
361 | |
362 | static void |
363 | update_state (GtkLockButton *button) |
364 | { |
365 | gboolean allowed; |
366 | gboolean can_acquire; |
367 | gboolean can_release; |
368 | gboolean sensitive; |
369 | gboolean visible; |
370 | GIcon *icon; |
371 | const char *tooltip; |
372 | |
373 | if (button->permission) |
374 | { |
375 | allowed = g_permission_get_allowed (permission: button->permission); |
376 | can_acquire = g_permission_get_can_acquire (permission: button->permission); |
377 | can_release = g_permission_get_can_release (permission: button->permission); |
378 | } |
379 | else |
380 | { |
381 | allowed = TRUE; |
382 | can_acquire = FALSE; |
383 | can_release = FALSE; |
384 | } |
385 | |
386 | if (allowed && can_release) |
387 | { |
388 | visible = TRUE; |
389 | sensitive = TRUE; |
390 | icon = button->icon_lock; |
391 | tooltip = button->tooltip_lock; |
392 | } |
393 | else if (allowed && !can_release) |
394 | { |
395 | visible = FALSE; |
396 | sensitive = TRUE; |
397 | icon = button->icon_lock; |
398 | tooltip = button->tooltip_lock; |
399 | } |
400 | else if (!allowed && can_acquire) |
401 | { |
402 | visible = TRUE; |
403 | sensitive = TRUE; |
404 | icon = button->icon_unlock; |
405 | tooltip = button->tooltip_unlock; |
406 | } |
407 | else if (!allowed && !can_acquire) |
408 | { |
409 | visible = TRUE; |
410 | sensitive = FALSE; |
411 | icon = button->icon_unlock; |
412 | tooltip = button->tooltip_not_authorized; |
413 | } |
414 | else |
415 | { |
416 | g_assert_not_reached (); |
417 | } |
418 | |
419 | gtk_image_set_from_gicon (GTK_IMAGE (button->image), icon); |
420 | gtk_stack_set_visible_child (GTK_STACK (button->stack), |
421 | child: allowed ? button->label_lock : button->label_unlock); |
422 | gtk_widget_set_tooltip_markup (GTK_WIDGET (button), markup: tooltip); |
423 | gtk_widget_set_sensitive (GTK_WIDGET (button), sensitive); |
424 | gtk_widget_set_visible (GTK_WIDGET (button), visible); |
425 | } |
426 | |
427 | static void |
428 | on_permission_changed (GPermission *permission, |
429 | GParamSpec *pspec, |
430 | gpointer user_data) |
431 | { |
432 | GtkLockButton *button = GTK_LOCK_BUTTON (user_data); |
433 | |
434 | update_state (button); |
435 | } |
436 | |
437 | static void |
438 | acquire_cb (GObject *source, |
439 | GAsyncResult *result, |
440 | gpointer user_data) |
441 | { |
442 | GtkLockButton *button = GTK_LOCK_BUTTON (user_data); |
443 | GError *error; |
444 | |
445 | error = NULL; |
446 | if (!g_permission_acquire_finish (permission: button->permission, result, error: &error)) |
447 | { |
448 | g_warning ("Error acquiring permission: %s" , error->message); |
449 | g_error_free (error); |
450 | } |
451 | |
452 | g_object_unref (object: button->cancellable); |
453 | button->cancellable = NULL; |
454 | |
455 | update_state (button); |
456 | } |
457 | |
458 | static void |
459 | release_cb (GObject *source, |
460 | GAsyncResult *result, |
461 | gpointer user_data) |
462 | { |
463 | GtkLockButton *button = GTK_LOCK_BUTTON (user_data); |
464 | GError *error; |
465 | |
466 | error = NULL; |
467 | if (!g_permission_release_finish (permission: button->permission, result, error: &error)) |
468 | { |
469 | g_warning ("Error releasing permission: %s" , error->message); |
470 | g_error_free (error); |
471 | } |
472 | |
473 | g_object_unref (object: button->cancellable); |
474 | button->cancellable = NULL; |
475 | |
476 | update_state (button); |
477 | } |
478 | |
479 | static void |
480 | gtk_lock_button_clicked (GtkButton *widget) |
481 | { |
482 | GtkLockButton *button = GTK_LOCK_BUTTON (widget); |
483 | |
484 | /* if we already have a pending interactive check or permission is not set, |
485 | * then do nothing |
486 | */ |
487 | if (button->cancellable != NULL || button->permission == NULL) |
488 | return; |
489 | |
490 | if (g_permission_get_allowed (permission: button->permission)) |
491 | { |
492 | if (g_permission_get_can_release (permission: button->permission)) |
493 | { |
494 | button->cancellable = g_cancellable_new (); |
495 | |
496 | g_permission_release_async (permission: button->permission, |
497 | cancellable: button->cancellable, |
498 | callback: release_cb, |
499 | user_data: button); |
500 | } |
501 | } |
502 | else |
503 | { |
504 | if (g_permission_get_can_acquire (permission: button->permission)) |
505 | { |
506 | button->cancellable = g_cancellable_new (); |
507 | |
508 | g_permission_acquire_async (permission: button->permission, |
509 | cancellable: button->cancellable, |
510 | callback: acquire_cb, |
511 | user_data: button); |
512 | } |
513 | } |
514 | } |
515 | |
516 | /** |
517 | * gtk_lock_button_new: |
518 | * @permission: (nullable): a `GPermission` |
519 | * |
520 | * Creates a new lock button which reflects the @permission. |
521 | * |
522 | * Returns: a new `GtkLockButton` |
523 | */ |
524 | GtkWidget * |
525 | gtk_lock_button_new (GPermission *permission) |
526 | { |
527 | return GTK_WIDGET (g_object_new (GTK_TYPE_LOCK_BUTTON, |
528 | "permission" , permission, |
529 | NULL)); |
530 | } |
531 | |
532 | /** |
533 | * gtk_lock_button_get_permission: (attributes org.gtk.Method.get_property=permission) |
534 | * @button: a `GtkLockButton` |
535 | * |
536 | * Obtains the `GPermission` object that controls @button. |
537 | * |
538 | * Returns: (transfer none) (nullable): the `GPermission` of @button |
539 | */ |
540 | GPermission * |
541 | gtk_lock_button_get_permission (GtkLockButton *button) |
542 | { |
543 | g_return_val_if_fail (GTK_IS_LOCK_BUTTON (button), NULL); |
544 | |
545 | return button->permission; |
546 | } |
547 | |
548 | /** |
549 | * gtk_lock_button_set_permission: (attributes org.gtk.Method.set_property=permission) |
550 | * @button: a `GtkLockButton` |
551 | * @permission: (nullable): a `GPermission` object |
552 | * |
553 | * Sets the `GPermission` object that controls @button. |
554 | */ |
555 | void |
556 | gtk_lock_button_set_permission (GtkLockButton *button, |
557 | GPermission *permission) |
558 | { |
559 | g_return_if_fail (GTK_IS_LOCK_BUTTON (button)); |
560 | g_return_if_fail (permission == NULL || G_IS_PERMISSION (permission)); |
561 | |
562 | if (button->permission != permission) |
563 | { |
564 | if (button->permission) |
565 | { |
566 | g_signal_handlers_disconnect_by_func (button->permission, |
567 | on_permission_changed, |
568 | button); |
569 | g_object_unref (object: button->permission); |
570 | } |
571 | |
572 | button->permission = permission; |
573 | |
574 | if (button->permission) |
575 | { |
576 | g_object_ref (button->permission); |
577 | g_signal_connect (button->permission, "notify" , |
578 | G_CALLBACK (on_permission_changed), button); |
579 | } |
580 | |
581 | update_state (button); |
582 | |
583 | g_object_notify (G_OBJECT (button), property_name: "permission" ); |
584 | } |
585 | } |
586 | |
587 | const char * |
588 | _gtk_lock_button_get_current_text (GtkLockButton *button) |
589 | { |
590 | GtkWidget *label; |
591 | |
592 | g_return_val_if_fail (GTK_IS_LOCK_BUTTON (button), NULL); |
593 | |
594 | label = gtk_stack_get_visible_child (GTK_STACK (button->stack)); |
595 | |
596 | return gtk_label_get_text (GTK_LABEL (label)); |
597 | } |
598 | |
599 | |