]> Pileus Git - ~andy/gtk/blob - gtk/gtklockbutton.c
Allow NULL permissions
[~andy/gtk] / gtk / gtklockbutton.c
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, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 #include "config.h"
22
23 #include "gtklockbutton.h"
24
25 #include "gtkbutton.h"
26 #include "gtkbox.h"
27 #include "gtkeventbox.h"
28 #include "gtklabel.h"
29 #include "gtknotebook.h"
30 #include "gtkintl.h"
31
32 /**
33  * SECTION:gtklockbutton
34  * @title: GtkLockButton
35  * @short_description: A widget to unlock or lock privileged operations
36  *
37  * GtkLockButton is a widget that can be used in control panels or
38  * preference dialogs to allow users to obtain and revoke authorizations
39  * needed to operate the controls. The required authorization is represented
40  * by a #GPermission object. Concrete implementations of #GPermission may use
41  * PolicyKit or some other authorization framework.
42  *
43  * If the user lacks the authorization but authorization can be obtained
44  * through authentication, the widget looks like this
45  * <informalexample><inlinegraphic fileref="lockbutton-locked.png"></inlinegraphic></informalexample>
46  * and the user can click the button to obtain the authorization. Depending
47  * on the platform, this may pop up an authentication dialog or ask the user
48  * to authenticate in some other way. Once authorization is obtained, the
49  * widget changes to this
50  * <informalexample><inlinegraphic fileref="lockbutton-unlocked.png"></inlinegraphic></informalexample>
51  * and the authorization can be dropped by clicking the button. If the user
52  * is not able to obtain authorization at all, the widget looks like this
53  * <informalexample><inlinegraphic fileref="lockbutton-sorry.png"></inlinegraphic></informalexample>
54  * If the user is authorized and cannot drop the authorization, the button
55  * is hidden.
56  *
57  * The text (and tooltips) that are shown in the various cases can be adjusted
58  * with the #GtkLockButton:text-lock, #GtkLockButton:text-unlock,
59  * #GtkLockButton:text-not-authorized, #GtkLockButton:tooltip-lock,
60  * #GtkLockButton:tooltip-unlock and #GtkLockButton:tooltip-not-authorized
61  * properties.
62  */
63
64 struct _GtkLockButtonPrivate
65 {
66   GPermission *permission;
67
68   gchar *tooltip_lock;
69   gchar *tooltip_unlock;
70   gchar *tooltip_not_authorized;
71
72   GtkWidget *box;
73   GtkWidget *eventbox;
74   GtkWidget *image;
75   GtkWidget *button;
76   GtkWidget *notebook;
77
78   GtkWidget *label_lock;
79   GtkWidget *label_unlock;
80   GtkWidget *label_not_authorized;
81
82   GCancellable *cancellable;
83 };
84
85 enum
86 {
87   PROP_0,
88   PROP_PERMISSION,
89   PROP_TEXT_LOCK,
90   PROP_TEXT_UNLOCK,
91   PROP_TEXT_NOT_AUTHORIZED,
92   PROP_TOOLTIP_LOCK,
93   PROP_TOOLTIP_UNLOCK,
94   PROP_TOOLTIP_NOT_AUTHORIZED
95 };
96
97 static void update_state   (GtkLockButton *button);
98 static void update_tooltip (GtkLockButton *button);
99
100 static void on_permission_changed (GPermission *permission,
101                                    GParamSpec  *pspec,
102                                    gpointer     user_data);
103
104 static void on_clicked (GtkButton *button,
105                         gpointer   user_data);
106
107 static void on_button_press (GtkWidget      *widget,
108                              GdkEventButton *event,
109                              gpointer        user_data);
110
111 G_DEFINE_TYPE (GtkLockButton, gtk_lock_button, GTK_TYPE_BIN);
112
113 static void
114 gtk_lock_button_finalize (GObject *object)
115 {
116   GtkLockButton *button = GTK_LOCK_BUTTON (object);
117   GtkLockButtonPrivate *priv = button->priv;
118
119   g_free (priv->tooltip_lock);
120   g_free (priv->tooltip_unlock);
121   g_free (priv->tooltip_not_authorized);
122
123   if (priv->cancellable != NULL)
124     {
125       g_cancellable_cancel (priv->cancellable);
126       g_object_unref (priv->cancellable);
127     }
128
129   if (priv->permission)
130     {
131       g_signal_handlers_disconnect_by_func (priv->permission,
132                                             on_permission_changed,
133                                             button);
134       g_object_unref (priv->permission);
135     }
136
137   G_OBJECT_CLASS (gtk_lock_button_parent_class)->finalize (object);
138 }
139
140 static void
141 gtk_lock_button_get_property (GObject    *object,
142                               guint       property_id,
143                               GValue     *value,
144                               GParamSpec *pspec)
145 {
146   GtkLockButton *button = GTK_LOCK_BUTTON (object);
147   GtkLockButtonPrivate *priv = button->priv;
148
149   switch (property_id)
150     {
151     case PROP_PERMISSION:
152       g_value_set_object (value, priv->permission);
153       break;
154
155     case PROP_TEXT_LOCK:
156       g_value_set_string (value,
157                           gtk_label_get_text (GTK_LABEL (priv->label_lock)));
158       break;
159
160     case PROP_TEXT_UNLOCK:
161       g_value_set_string (value,
162                           gtk_label_get_text (GTK_LABEL (priv->label_unlock)));
163       break;
164
165     case PROP_TEXT_NOT_AUTHORIZED:
166       g_value_set_string (value,
167                           gtk_label_get_text (GTK_LABEL (priv->label_not_authorized)));
168       break;
169
170     case PROP_TOOLTIP_LOCK:
171       g_value_set_string (value, priv->tooltip_lock);
172       break;
173
174     case PROP_TOOLTIP_UNLOCK:
175       g_value_set_string (value, priv->tooltip_unlock);
176       break;
177
178     case PROP_TOOLTIP_NOT_AUTHORIZED:
179       g_value_set_string (value, priv->tooltip_not_authorized);
180       break;
181
182     default:
183       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
184       break;
185     }
186 }
187
188 static void
189 gtk_lock_button_set_property (GObject      *object,
190                               guint         property_id,
191                               const GValue *value,
192                               GParamSpec   *pspec)
193 {
194   GtkLockButton *button = GTK_LOCK_BUTTON (object);
195   GtkLockButtonPrivate *priv = button->priv;
196
197   switch (property_id)
198     {
199     case PROP_PERMISSION:
200       gtk_lock_button_set_permission (button, g_value_get_object (value));
201       break;
202
203     case PROP_TEXT_LOCK:
204       gtk_label_set_text (GTK_LABEL (priv->label_lock), g_value_get_string (value));
205       break;
206
207     case PROP_TEXT_UNLOCK:
208       gtk_label_set_text (GTK_LABEL (priv->label_unlock), g_value_get_string (value));
209       break;
210
211     case PROP_TEXT_NOT_AUTHORIZED:
212       gtk_label_set_text (GTK_LABEL (priv->label_not_authorized), g_value_get_string (value));
213       break;
214
215     case PROP_TOOLTIP_LOCK:
216       g_free (priv->tooltip_lock);
217       priv->tooltip_lock = g_value_dup_string (value);
218       update_tooltip (button);
219       break;
220
221     case PROP_TOOLTIP_UNLOCK:
222       g_free (priv->tooltip_unlock);
223       priv->tooltip_unlock = g_value_dup_string (value);
224       update_tooltip (button);
225       break;
226
227     case PROP_TOOLTIP_NOT_AUTHORIZED:
228       g_free (priv->tooltip_not_authorized);
229       priv->tooltip_not_authorized = g_value_dup_string (value);
230       update_tooltip (button);
231       break;
232
233     default:
234       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
235       break;
236     }
237 }
238
239 static void
240 gtk_lock_button_init (GtkLockButton *button)
241 {
242   GtkLockButtonPrivate *priv;
243
244   button->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (button,
245                                                      GTK_TYPE_LOCK_BUTTON,
246                                                      GtkLockButtonPrivate);
247
248   priv->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
249   gtk_container_add (GTK_CONTAINER (button), priv->box);
250
251   priv->eventbox = gtk_event_box_new ();
252   gtk_event_box_set_visible_window (GTK_EVENT_BOX (priv->eventbox), FALSE);
253   gtk_container_add (GTK_CONTAINER (priv->box), priv->eventbox);
254   gtk_widget_show (priv->eventbox);
255   priv->image = gtk_image_new ();
256   gtk_container_add (GTK_CONTAINER (priv->eventbox), priv->image);
257   gtk_widget_show (priv->image);
258
259   priv->notebook = gtk_notebook_new ();
260   gtk_notebook_set_show_tabs (GTK_NOTEBOOK (priv->notebook), FALSE);
261   gtk_notebook_set_show_border (GTK_NOTEBOOK (priv->notebook), FALSE);
262   gtk_widget_show (priv->notebook);
263
264   priv->button = gtk_button_new ();
265   gtk_container_add (GTK_CONTAINER (priv->button), priv->notebook);
266   gtk_widget_show (priv->button);
267
268   priv->label_lock = gtk_label_new ("");
269   gtk_notebook_append_page (GTK_NOTEBOOK (priv->notebook), priv->label_lock, NULL);
270   gtk_widget_show (priv->label_lock);
271
272   priv->label_unlock = gtk_label_new ("");
273   gtk_notebook_append_page (GTK_NOTEBOOK (priv->notebook), priv->label_unlock, NULL);
274   gtk_widget_show (priv->label_unlock);
275
276   priv->label_not_authorized = gtk_label_new ("");
277   gtk_notebook_append_page (GTK_NOTEBOOK (priv->notebook), priv->label_not_authorized, NULL);
278   gtk_widget_show (priv->label_not_authorized);
279
280   gtk_box_pack_start (GTK_BOX (priv->box), priv->button, FALSE, FALSE, 0);
281   gtk_widget_show (priv->button);
282
283   g_signal_connect (priv->eventbox, "button-press-event",
284                     G_CALLBACK (on_button_press), button);
285   g_signal_connect (priv->button, "clicked",
286                     G_CALLBACK (on_clicked), button);
287
288   gtk_widget_set_no_show_all (priv->box, TRUE);
289
290   update_state (button);
291 }
292
293 static void
294 gtk_lock_button_get_preferred_width (GtkWidget *widget,
295                                      gint      *minimum,
296                                      gint      *natural)
297 {
298   GtkLockButtonPrivate *priv = GTK_LOCK_BUTTON (widget)->priv;
299
300   gtk_widget_get_preferred_width (priv->box, minimum, natural);
301 }
302
303 static void
304 gtk_lock_button_get_preferred_height (GtkWidget *widget,
305                                       gint      *minimum,
306                                       gint      *natural)
307 {
308   GtkLockButtonPrivate *priv = GTK_LOCK_BUTTON (widget)->priv;
309
310   gtk_widget_get_preferred_height (priv->box, minimum, natural);
311 }
312
313 static void
314 gtk_lock_button_size_allocate (GtkWidget     *widget,
315                                GtkAllocation *allocation)
316 {
317   GtkLockButtonPrivate *priv = GTK_LOCK_BUTTON (widget)->priv;
318   GtkRequisition requisition;
319   GtkAllocation child_allocation;
320
321   gtk_widget_set_allocation (widget, allocation);
322   gtk_widget_get_preferred_size (priv->box, &requisition, NULL);
323   child_allocation.x = allocation->x;
324   child_allocation.y = allocation->y;
325   child_allocation.width = requisition.width;
326   child_allocation.height = requisition.height;
327   gtk_widget_size_allocate (priv->box, &child_allocation);
328 }
329
330 static void
331 gtk_lock_button_class_init (GtkLockButtonClass *klass)
332 {
333   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
334   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
335
336   gobject_class->finalize     = gtk_lock_button_finalize;
337   gobject_class->get_property = gtk_lock_button_get_property;
338   gobject_class->set_property = gtk_lock_button_set_property;
339
340   widget_class->get_preferred_width  = gtk_lock_button_get_preferred_width;
341   widget_class->get_preferred_height = gtk_lock_button_get_preferred_height;
342   widget_class->size_allocate        = gtk_lock_button_size_allocate;
343
344   g_type_class_add_private (klass, sizeof (GtkLockButtonPrivate));
345
346   g_object_class_install_property (gobject_class, PROP_PERMISSION,
347     g_param_spec_object ("permission",
348                          P_("Permission"),
349                          P_("The GPermission object controlling this button"),
350                          G_TYPE_PERMISSION,
351                          G_PARAM_READWRITE |
352                          G_PARAM_STATIC_STRINGS));
353
354   g_object_class_install_property (gobject_class, PROP_TEXT_LOCK,
355     g_param_spec_string ("text-lock",
356                          P_("Lock Text"),
357                          P_("The text to display when prompting the user to lock"),
358                          _("Lock"),
359                          G_PARAM_READWRITE |
360                          G_PARAM_CONSTRUCT |
361                          G_PARAM_STATIC_STRINGS));
362
363   g_object_class_install_property (gobject_class, PROP_TEXT_UNLOCK,
364     g_param_spec_string ("text-unlock",
365                          P_("Unlock Text"),
366                          P_("The text to display when prompting the user to unlock"),
367                          _("Unlock"),
368                          G_PARAM_READWRITE |
369                          G_PARAM_CONSTRUCT |
370                          G_PARAM_STATIC_STRINGS));
371
372   g_object_class_install_property (gobject_class, PROP_TEXT_NOT_AUTHORIZED,
373     g_param_spec_string ("text-not-authorized",
374                          P_("Not Authorized Text"),
375                          P_("The text to display when prompting the user cannot obtain authorization"),
376                          _("Locked"),
377                          G_PARAM_READWRITE |
378                          G_PARAM_CONSTRUCT |
379                          G_PARAM_STATIC_STRINGS));
380
381   g_object_class_install_property (gobject_class, PROP_TOOLTIP_LOCK,
382     g_param_spec_string ("tooltip-lock",
383                          P_("Lock Tooltip"),
384                          P_("The tooltip to display when prompting the user to lock"),
385                          _("Dialog is unlocked.\nClick to prevent further changes"),
386                          G_PARAM_READWRITE |
387                          G_PARAM_CONSTRUCT |
388                          G_PARAM_STATIC_STRINGS));
389
390   g_object_class_install_property (gobject_class, PROP_TOOLTIP_UNLOCK,
391     g_param_spec_string ("tooltip-unlock",
392                          P_("Unlock Tooltip"),
393                          P_("The tooltip to display when prompting the user to unlock"),
394                          _("Dialog is locked.\nClick to make changes"),
395                          G_PARAM_READWRITE |
396                          G_PARAM_CONSTRUCT |
397                          G_PARAM_STATIC_STRINGS));
398
399   g_object_class_install_property (gobject_class, PROP_TOOLTIP_NOT_AUTHORIZED,
400     g_param_spec_string ("tooltip-not-authorized",
401                          P_("Not Authorized Tooltip"),
402                          P_("The tooltip to display when prompting the user cannot obtain authorization"),
403                          _("System policy prevents changes.\nContact your system administrator"),
404                          G_PARAM_READWRITE |
405                          G_PARAM_CONSTRUCT |
406                          G_PARAM_STATIC_STRINGS));
407 }
408
409 static void
410 update_tooltip (GtkLockButton *button)
411 {
412   GtkLockButtonPrivate *priv = button->priv;
413   const gchar *tooltip;
414
415   switch (gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook)))
416     {
417       case 0:
418         tooltip = priv->tooltip_lock;
419         break;
420       case 1:
421         tooltip = priv->tooltip_unlock;
422         break;
423       case 2:
424         tooltip = priv->tooltip_not_authorized;
425         break;
426       default:
427         tooltip = "";
428         break;
429     }
430
431   gtk_widget_set_tooltip_markup (priv->box, tooltip);
432 }
433
434 static void
435 update_state (GtkLockButton *button)
436 {
437   GtkLockButtonPrivate *priv = button->priv;
438   gboolean allowed;
439   gboolean can_acquire;
440   gboolean can_release;
441   gint page;
442   gboolean sensitive;
443   gboolean visible;
444   GIcon *icon;
445
446   if (priv->permission)
447     {
448       allowed = g_permission_get_allowed (priv->permission);
449       can_acquire = g_permission_get_can_acquire (priv->permission);
450       can_release = g_permission_get_can_release (priv->permission);
451     }
452   else
453     {
454       allowed = FALSE;
455       can_acquire = FALSE;
456       can_release = FALSE;
457     }
458
459   visible = TRUE;
460   sensitive = TRUE;
461
462   if (allowed)
463     {
464       if (can_release)
465         {
466           page = 0;
467           sensitive = TRUE;
468         }
469       else
470         {
471           page = 0;
472           visible = FALSE;
473         }
474     }
475   else
476     {
477       if (can_acquire)
478         {
479           page = 1;
480           sensitive = TRUE;
481         }
482       else
483         {
484           page = 2;
485           sensitive = FALSE;
486         }
487     }
488
489   if (allowed)
490     {
491       gchar *names[3];
492
493       names[0] = "changes-allow-symbolic";
494       names[1] = "changes-allow";
495       names[2] = NULL;
496       icon = g_themed_icon_new_from_names (names, -1);
497     }
498   else
499     {
500       gchar *names[3];
501
502       names[0] = "changes-prevent-symbolic";
503       names[1] = "changes-prevent";
504       names[2] = NULL;
505       icon = g_themed_icon_new_from_names (names, -1);
506     }
507
508   gtk_image_set_from_gicon (GTK_IMAGE (priv->image), icon, GTK_ICON_SIZE_BUTTON);
509   g_object_unref (icon);
510
511   gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), page);
512   gtk_widget_set_sensitive (priv->box, sensitive);
513   gtk_widget_set_visible (priv->box, visible);
514
515   update_tooltip (button);
516 }
517
518 static void
519 on_permission_changed (GPermission *permission,
520                        GParamSpec  *pspec,
521                        gpointer     user_data)
522 {
523   GtkLockButton *button = GTK_LOCK_BUTTON (user_data);
524
525   update_state (button);
526 }
527
528 static void
529 acquire_cb (GObject      *source,
530             GAsyncResult *result,
531             gpointer      user_data)
532 {
533   GtkLockButton *button = GTK_LOCK_BUTTON (user_data);
534   GtkLockButtonPrivate *priv = button->priv;
535   GError *error;
536
537   error = NULL;
538   if (!g_permission_acquire_finish (priv->permission, result, &error))
539     {
540       g_warning ("Error acquiring permission: %s", error->message);
541       g_error_free (error);
542     }
543
544   g_object_unref (priv->cancellable);
545   priv->cancellable = NULL;
546
547   update_state (button);
548 }
549
550 static void
551 release_cb (GObject      *source,
552             GAsyncResult *result,
553             gpointer      user_data)
554 {
555   GtkLockButton *button = GTK_LOCK_BUTTON (user_data);
556   GtkLockButtonPrivate *priv = button->priv;
557   GError *error;
558
559   error = NULL;
560   if (!g_permission_release_finish (priv->permission, result, &error))
561     {
562       g_warning ("Error releasing permission: %s", error->message);
563       g_error_free (error);
564     }
565
566   g_object_unref (priv->cancellable);
567   priv->cancellable = NULL;
568
569   update_state (button);
570 }
571
572 static void
573 handle_click (GtkLockButton *button)
574 {
575   GtkLockButtonPrivate *priv = button->priv;
576
577   /* if we already have a pending interactive check, then do nothing */
578   if (priv->cancellable != NULL)
579     return;
580
581   if (g_permission_get_allowed (priv->permission))
582     {
583       if (g_permission_get_can_release (priv->permission))
584         {
585           priv->cancellable = g_cancellable_new ();
586
587           g_permission_release_async (priv->permission,
588                                       priv->cancellable,
589                                       release_cb,
590                                       button);
591         }
592     }
593   else
594     {
595       if (g_permission_get_can_acquire (priv->permission))
596         {
597           priv->cancellable = g_cancellable_new ();
598
599           g_permission_acquire_async (priv->permission,
600                                       priv->cancellable,
601                                       acquire_cb,
602                                       button);
603         }
604     }
605 }
606
607 static void
608 on_clicked (GtkButton *button,
609             gpointer   user_data)
610
611 {
612   handle_click (GTK_LOCK_BUTTON (user_data));
613 }
614
615 static void
616 on_button_press (GtkWidget      *widget,
617                  GdkEventButton *event,
618                  gpointer        user_data)
619 {
620   handle_click (GTK_LOCK_BUTTON (user_data));
621 }
622
623
624 /**
625  * gtk_lock_button_new:
626  * @permission: (allow-none): a #GPermission
627  *
628  * Creates a new lock button which reflects the @permission.
629  *
630  * Returns: a new #GtkLockButton
631  *
632  * Since: 3.2
633  */
634 GtkWidget *
635 gtk_lock_button_new (GPermission *permission)
636 {
637   return GTK_WIDGET (g_object_new (GTK_TYPE_LOCK_BUTTON,
638                                    "permission", permission,
639                                    NULL));
640 }
641
642 /**
643  * gtk_lock_button_get_permission:
644  * @button: a #GtkLockButton
645  *
646  * Obtains the #GPermission object that controls @button.
647  *
648  * Returns: the #GPermission of @button
649  *
650  * Since: 3.2
651  */
652 GPermission *
653 gtk_lock_button_get_permission (GtkLockButton *button)
654 {
655   g_return_val_if_fail (GTK_IS_LOCK_BUTTON (button), NULL);
656
657   return button->priv->permission;
658 }
659
660 /**
661  * gtk_lock_button_set_permission:
662  * @button: a #GtkLockButton
663  * @permission: (allow-none): a #GPermission object, or %NULL
664  *
665  * Sets the #GPermission object that controls @button.
666  *
667  * Since: 3.2
668  */
669 void
670 gtk_lock_button_set_permission (GtkLockButton *button,
671                                 GPermission   *permission)
672 {
673   GtkLockButtonPrivate *priv;
674
675   g_return_if_fail (GTK_IS_LOCK_BUTTON (button));
676   g_return_if_fail (permission == NULL || G_IS_PERMISSION (permission));
677
678   priv = button->priv;
679
680   if (priv->permission != permission)
681     {
682       if (priv->permission)
683         {
684           g_signal_handlers_disconnect_by_func (priv->permission,
685                                                 on_permission_changed,
686                                                 button);
687           g_object_unref (priv->permission);
688         }
689
690       priv->permission = permission;
691
692       if (priv->permission)
693         {
694           g_object_ref (priv->permission);
695           g_signal_connect (priv->permission, "notify",
696                             G_CALLBACK (on_permission_changed), button);
697         }
698
699       update_state (button);
700
701       g_object_notify (G_OBJECT (button), "permission");
702     }
703 }