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
72struct _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
92typedef struct _GtkLockButtonClass GtkLockButtonClass;
93struct _GtkLockButtonClass
94{
95 GtkButtonClass parent_class;
96};
97
98enum
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
109static void update_state (GtkLockButton *button);
110static void gtk_lock_button_clicked (GtkButton *button);
111
112static void on_permission_changed (GPermission *permission,
113 GParamSpec *pspec,
114 gpointer user_data);
115
116G_DEFINE_TYPE (GtkLockButton, gtk_lock_button, GTK_TYPE_BUTTON)
117
118static void
119gtk_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
147static void
148gtk_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
187static void
188gtk_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
232static void
233gtk_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
254static void
255gtk_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
362static void
363update_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
427static void
428on_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
437static void
438acquire_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
458static void
459release_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
479static void
480gtk_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 */
524GtkWidget *
525gtk_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 */
540GPermission *
541gtk_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 */
555void
556gtk_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
587const 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

source code of gtk/gtk/gtklockbutton.c