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