1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 2019 Red Hat, Inc.
3  *
4  * Authors:
5  * - Matthias Clasen <mclasen@redhat.com>
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library 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 GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "config.h"
22 
23 #include "gtkpasswordentryprivate.h"
24 
25 #include "gtkaccessibleprivate.h"
26 #include "gtktextprivate.h"
27 #include "gtkeditable.h"
28 #include "gtkeventcontrollerkey.h"
29 #include "gtkgestureclick.h"
30 #include "gtkbox.h"
31 #include "gtkimage.h"
32 #include "gtkintl.h"
33 #include "gtkmarshalers.h"
34 #include "gtkpasswordentrybuffer.h"
35 #include "gtkprivate.h"
36 #include "gtkwidgetprivate.h"
37 #include "gtkcsspositionvalueprivate.h"
38 #include "gtkcssnodeprivate.h"
39 #include "gtkjoinedmenuprivate.h"
40 
41 
42 /**
43  * GtkPasswordEntry:
44  *
45  * `GtkPasswordEntry` is an entry that has been tailored for entering secrets.
46  *
47  * ![An example GtkPasswordEntry](password-entry.png)
48  *
49  * It does not show its contents in clear text, does not allow to copy it
50  * to the clipboard, and it shows a warning when Caps Lock is engaged. If
51  * the underlying platform allows it, `GtkPasswordEntry` will also place
52  * the text in a non-pageable memory area, to avoid it being written out
53  * to disk by the operating system.
54  *
55  * Optionally, it can offer a way to reveal the contents in clear text.
56  *
57  * `GtkPasswordEntry` provides only minimal API and should be used with
58  * the [iface@Gtk.Editable] API.
59  *
60  * # CSS Nodes
61  *
62  * ```
63  * entry.password
64  * ╰── text
65  *     ├── image.caps-lock-indicator
66  *     ┊
67  * ```
68  *
69  * `GtkPasswordEntry` has a single CSS node with name entry that carries
70  * a .passwordstyle class. The text Css node below it has a child with
71  * name image and style class .caps-lock-indicator for the Caps Lock
72  * icon, and possibly other children.
73  *
74  * # Accessibility
75  *
76  * `GtkPasswordEntry` uses the %GTK_ACCESSIBLE_ROLE_TEXT_BOX role.
77  */
78 
79 struct _GtkPasswordEntry
80 {
81   GtkWidget parent_instance;
82 
83   GtkWidget *entry;
84   GtkWidget *icon;
85   GtkWidget *peek_icon;
86   GdkDevice *keyboard;
87   GMenuModel *extra_menu;
88 };
89 
90 struct _GtkPasswordEntryClass
91 {
92   GtkWidgetClass parent_class;
93 };
94 
95 enum {
96   ACTIVATE,
97   LAST_SIGNAL
98 };
99 
100 static guint signals[LAST_SIGNAL] = { 0, };
101 
102 enum {
103   PROP_PLACEHOLDER_TEXT = 1,
104   PROP_ACTIVATES_DEFAULT,
105   PROP_SHOW_PEEK_ICON,
106   PROP_EXTRA_MENU,
107   NUM_PROPERTIES
108 };
109 
110 static GParamSpec *props[NUM_PROPERTIES] = { NULL, };
111 
112 static void gtk_password_entry_editable_init (GtkEditableInterface *iface);
113 static void gtk_password_entry_accessible_init (GtkAccessibleInterface *iface);
114 
G_DEFINE_TYPE_WITH_CODE(GtkPasswordEntry,gtk_password_entry,GTK_TYPE_WIDGET,G_IMPLEMENT_INTERFACE (GTK_TYPE_ACCESSIBLE,gtk_password_entry_accessible_init)G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,gtk_password_entry_editable_init))115 G_DEFINE_TYPE_WITH_CODE (GtkPasswordEntry, gtk_password_entry, GTK_TYPE_WIDGET,
116                          G_IMPLEMENT_INTERFACE (GTK_TYPE_ACCESSIBLE, gtk_password_entry_accessible_init)
117                          G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, gtk_password_entry_editable_init))
118 
119 static void
120 caps_lock_state_changed (GdkDevice  *device,
121                          GParamSpec *pspec,
122                         GtkWidget   *widget)
123 {
124   GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (widget);
125 
126   if (gtk_editable_get_editable (GTK_EDITABLE (entry)) &&
127       gtk_widget_has_focus (entry->entry) &&
128       !gtk_text_get_visibility (GTK_TEXT (entry->entry)) &&
129       gdk_device_get_caps_lock_state (device))
130     gtk_widget_show (entry->icon);
131   else
132     gtk_widget_hide (entry->icon);
133 }
134 
135 static void
focus_changed(GtkWidget * widget)136 focus_changed (GtkWidget *widget)
137 {
138   GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (widget);
139 
140   if (entry->keyboard)
141     caps_lock_state_changed (entry->keyboard, NULL, widget);
142 }
143 
144 /*< private >
145  * gtk_password_entry_toggle_peek:
146  * @entry: a `GtkPasswordEntry`
147  *
148  * Toggles the text visibility.
149  */
150 void
gtk_password_entry_toggle_peek(GtkPasswordEntry * entry)151 gtk_password_entry_toggle_peek (GtkPasswordEntry *entry)
152 {
153   gboolean visibility;
154 
155   visibility = gtk_text_get_visibility (GTK_TEXT (entry->entry));
156   gtk_text_set_visibility (GTK_TEXT (entry->entry), !visibility);
157 }
158 
159 static void
visibility_toggled(GObject * object,GParamSpec * pspec,GtkPasswordEntry * entry)160 visibility_toggled (GObject          *object,
161                     GParamSpec       *pspec,
162                     GtkPasswordEntry *entry)
163 {
164   if (gtk_text_get_visibility (GTK_TEXT (entry->entry)))
165     {
166       gtk_image_set_from_icon_name (GTK_IMAGE (entry->peek_icon), "eye-open-negative-filled-symbolic");
167       gtk_widget_set_tooltip_text (entry->peek_icon, _("Hide Text"));
168     }
169   else
170     {
171       gtk_image_set_from_icon_name (GTK_IMAGE (entry->peek_icon), "eye-not-looking-symbolic");
172       gtk_widget_set_tooltip_text (entry->peek_icon, _("Show Text"));
173     }
174 
175   if (entry->keyboard)
176     caps_lock_state_changed (entry->keyboard, NULL, GTK_WIDGET (entry));
177 }
178 
179 static void
activate_cb(GtkPasswordEntry * entry)180 activate_cb (GtkPasswordEntry *entry)
181 {
182   g_signal_emit (entry, signals[ACTIVATE], 0);
183 }
184 
185 static void
gtk_password_entry_init(GtkPasswordEntry * entry)186 gtk_password_entry_init (GtkPasswordEntry *entry)
187 {
188   GtkEntryBuffer *buffer = gtk_password_entry_buffer_new ();
189 
190   entry->entry = gtk_text_new ();
191   gtk_text_set_buffer (GTK_TEXT (entry->entry), buffer);
192   gtk_text_set_visibility (GTK_TEXT (entry->entry), FALSE);
193   gtk_widget_set_parent (entry->entry, GTK_WIDGET (entry));
194   gtk_editable_init_delegate (GTK_EDITABLE (entry));
195   g_signal_connect_swapped (entry->entry, "notify::has-focus", G_CALLBACK (focus_changed), entry);
196   g_signal_connect_swapped (entry->entry, "activate", G_CALLBACK (activate_cb), entry);
197 
198   entry->icon = gtk_image_new_from_icon_name ("caps-lock-symbolic");
199   gtk_widget_set_tooltip_text (entry->icon, _("Caps Lock is on"));
200   gtk_widget_add_css_class (entry->icon, "caps-lock-indicator");
201   gtk_widget_set_cursor (entry->icon, gtk_widget_get_cursor (entry->entry));
202   gtk_widget_set_parent (entry->icon, GTK_WIDGET (entry));
203 
204   gtk_widget_add_css_class (GTK_WIDGET (entry), I_("password"));
205 
206   gtk_password_entry_set_extra_menu (entry, NULL);
207 
208   /* Transfer ownership to the GtkText widget */
209   g_object_unref (buffer);
210 }
211 
212 static void
gtk_password_entry_realize(GtkWidget * widget)213 gtk_password_entry_realize (GtkWidget *widget)
214 {
215   GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (widget);
216   GdkSeat *seat;
217 
218   GTK_WIDGET_CLASS (gtk_password_entry_parent_class)->realize (widget);
219 
220   seat = gdk_display_get_default_seat (gtk_widget_get_display (widget));
221   if (seat)
222     entry->keyboard = gdk_seat_get_keyboard (seat);
223 
224   if (entry->keyboard)
225     {
226       g_signal_connect (entry->keyboard, "notify::caps-lock-state",
227                         G_CALLBACK (caps_lock_state_changed), entry);
228       caps_lock_state_changed (entry->keyboard, NULL, widget);
229     }
230 }
231 
232 static void
gtk_password_entry_dispose(GObject * object)233 gtk_password_entry_dispose (GObject *object)
234 {
235   GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (object);
236 
237   if (entry->keyboard)
238     g_signal_handlers_disconnect_by_func (entry->keyboard, caps_lock_state_changed, entry);
239 
240   if (entry->entry)
241     gtk_editable_finish_delegate (GTK_EDITABLE (entry));
242 
243   g_clear_pointer (&entry->entry, gtk_widget_unparent);
244   g_clear_pointer (&entry->icon, gtk_widget_unparent);
245   g_clear_pointer (&entry->peek_icon, gtk_widget_unparent);
246   g_clear_object (&entry->extra_menu);
247 
248   G_OBJECT_CLASS (gtk_password_entry_parent_class)->dispose (object);
249 }
250 
251 static void
gtk_password_entry_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)252 gtk_password_entry_set_property (GObject      *object,
253                                  guint         prop_id,
254                                  const GValue *value,
255                                  GParamSpec   *pspec)
256 {
257   GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (object);
258   const char *text;
259 
260   if (gtk_editable_delegate_set_property (object, prop_id, value, pspec))
261     {
262       if (prop_id == NUM_PROPERTIES + GTK_EDITABLE_PROP_EDITABLE)
263         {
264           gtk_accessible_update_property (GTK_ACCESSIBLE (entry),
265                                           GTK_ACCESSIBLE_PROPERTY_READ_ONLY, !g_value_get_boolean (value),
266                                           -1);
267         }
268       return;
269     }
270 
271   switch (prop_id)
272     {
273     case PROP_PLACEHOLDER_TEXT:
274       text = g_value_get_string (value);
275       gtk_text_set_placeholder_text (GTK_TEXT (entry->entry), text);
276       gtk_accessible_update_property (GTK_ACCESSIBLE (entry),
277                                       GTK_ACCESSIBLE_PROPERTY_PLACEHOLDER, text,
278                                       -1);
279       break;
280 
281     case PROP_ACTIVATES_DEFAULT:
282       if (gtk_text_get_activates_default (GTK_TEXT (entry->entry)) != g_value_get_boolean (value))
283         {
284           gtk_text_set_activates_default (GTK_TEXT (entry->entry), g_value_get_boolean (value));
285           g_object_notify_by_pspec (object, pspec);
286         }
287       break;
288 
289     case PROP_SHOW_PEEK_ICON:
290       gtk_password_entry_set_show_peek_icon (entry, g_value_get_boolean (value));
291       break;
292 
293     case PROP_EXTRA_MENU:
294       gtk_password_entry_set_extra_menu (entry, g_value_get_object (value));
295       break;
296 
297     default:
298       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
299       break;
300     }
301 }
302 
303 static void
gtk_password_entry_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)304 gtk_password_entry_get_property (GObject    *object,
305                                  guint       prop_id,
306                                  GValue     *value,
307                                  GParamSpec *pspec)
308 {
309   GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (object);
310 
311   if (gtk_editable_delegate_get_property (object, prop_id, value, pspec))
312     return;
313 
314   switch (prop_id)
315     {
316     case PROP_PLACEHOLDER_TEXT:
317       g_value_set_string (value, gtk_text_get_placeholder_text (GTK_TEXT (entry->entry)));
318       break;
319 
320     case PROP_ACTIVATES_DEFAULT:
321       g_value_set_boolean (value, gtk_text_get_activates_default (GTK_TEXT (entry->entry)));
322       break;
323 
324     case PROP_SHOW_PEEK_ICON:
325       g_value_set_boolean (value, gtk_password_entry_get_show_peek_icon (entry));
326       break;
327 
328     case PROP_EXTRA_MENU:
329       g_value_set_object (value, entry->extra_menu);
330       break;
331 
332     default:
333       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
334       break;
335     }
336 }
337 
338 static void
gtk_password_entry_measure(GtkWidget * widget,GtkOrientation orientation,int for_size,int * minimum,int * natural,int * minimum_baseline,int * natural_baseline)339 gtk_password_entry_measure (GtkWidget      *widget,
340                             GtkOrientation  orientation,
341                             int             for_size,
342                             int            *minimum,
343                             int            *natural,
344                             int            *minimum_baseline,
345                             int            *natural_baseline)
346 {
347   GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (widget);
348   int icon_min = 0, icon_nat = 0;
349 
350   gtk_widget_measure (entry->entry, orientation, for_size,
351                       minimum, natural,
352                       minimum_baseline, natural_baseline);
353 
354   if (entry->icon && gtk_widget_get_visible (entry->icon))
355     gtk_widget_measure (entry->icon, orientation, for_size,
356                         &icon_min, &icon_nat,
357                         NULL, NULL);
358 
359   if (entry->peek_icon && gtk_widget_get_visible (entry->peek_icon))
360     gtk_widget_measure (entry->peek_icon, orientation, for_size,
361                         &icon_min, &icon_nat,
362                         NULL, NULL);
363 }
364 
365 static void
gtk_password_entry_size_allocate(GtkWidget * widget,int width,int height,int baseline)366 gtk_password_entry_size_allocate (GtkWidget *widget,
367                                   int        width,
368                                   int        height,
369                                   int        baseline)
370 {
371   GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (widget);
372   GtkCssStyle *style = gtk_css_node_get_style (gtk_widget_get_css_node (widget));
373   int icon_min = 0, icon_nat = 0;
374   int peek_min = 0, peek_nat = 0;
375   int text_width;
376   int spacing;
377 
378   spacing = _gtk_css_position_value_get_x (style->size->border_spacing, 100);
379 
380   if (entry->icon && gtk_widget_get_visible (entry->icon))
381     gtk_widget_measure (entry->icon, GTK_ORIENTATION_HORIZONTAL, -1,
382                         &icon_min, &icon_nat,
383                         NULL, NULL);
384 
385   if (entry->peek_icon && gtk_widget_get_visible (entry->peek_icon))
386     gtk_widget_measure (entry->peek_icon, GTK_ORIENTATION_HORIZONTAL, -1,
387                         &peek_min, &peek_nat,
388                         NULL, NULL);
389 
390   text_width = width - (icon_nat + (icon_nat > 0 ? spacing : 0))
391                      - (peek_nat + (peek_nat > 0 ? spacing : 0));
392 
393   gtk_widget_size_allocate (entry->entry,
394                             &(GtkAllocation) { 0, 0, text_width, height },
395                             baseline);
396 
397   if (entry->icon && gtk_widget_get_visible (entry->icon))
398     gtk_widget_size_allocate (entry->icon,
399                               &(GtkAllocation) { text_width + spacing, 0, icon_nat, height },
400                               baseline);
401 
402   if (entry->peek_icon && gtk_widget_get_visible (entry->peek_icon))
403     gtk_widget_size_allocate (entry->peek_icon,
404                               &(GtkAllocation) { text_width + spacing + icon_nat + (icon_nat > 0 ? spacing : 0), 0, peek_nat, height },
405                               baseline);
406 }
407 
408 static gboolean
gtk_password_entry_mnemonic_activate(GtkWidget * widget,gboolean group_cycling)409 gtk_password_entry_mnemonic_activate (GtkWidget *widget,
410                                       gboolean   group_cycling)
411 {
412   GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (widget);
413 
414   gtk_widget_grab_focus (entry->entry);
415 
416   return TRUE;
417 }
418 
419 static void
gtk_password_entry_class_init(GtkPasswordEntryClass * klass)420 gtk_password_entry_class_init (GtkPasswordEntryClass *klass)
421 {
422   GObjectClass *object_class = G_OBJECT_CLASS (klass);
423   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
424 
425   object_class->dispose = gtk_password_entry_dispose;
426   object_class->get_property = gtk_password_entry_get_property;
427   object_class->set_property = gtk_password_entry_set_property;
428 
429   widget_class->realize = gtk_password_entry_realize;
430   widget_class->measure = gtk_password_entry_measure;
431   widget_class->size_allocate = gtk_password_entry_size_allocate;
432   widget_class->mnemonic_activate = gtk_password_entry_mnemonic_activate;
433   widget_class->grab_focus = gtk_widget_grab_focus_child;
434   widget_class->focus = gtk_widget_focus_child;
435 
436   /**
437    * GtkPasswordEntry:placeholder-text:
438    *
439    * The text that will be displayed in the `GtkPasswordEntry`
440    * when it is empty and unfocused.
441    */
442   props[PROP_PLACEHOLDER_TEXT] =
443       g_param_spec_string ("placeholder-text",
444                            P_("Placeholder text"),
445                            P_("Show text in the entry when it’s empty and unfocused"),
446                            NULL,
447                            GTK_PARAM_READWRITE);
448 
449   /**
450    * GtkPasswordEntry:activates-default:
451    *
452    * Whether to activate the default widget when Enter is pressed.
453    */
454   props[PROP_ACTIVATES_DEFAULT] =
455       g_param_spec_boolean ("activates-default",
456                             P_("Activates default"),
457                             P_("Whether to activate the default widget (such as the default button in a dialog) when Enter is pressed"),
458                             FALSE,
459                             GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
460 
461   /**
462    * GtkPasswordEntry:show-peek-icon: (attributes org.gtk.Property.get=gtk_password_entry_get_show_peek_icon org.gtk.Property.set=gtk_password_entry_set_show_peek_icon)
463    *
464    * Whether to show an icon for revealing the content.
465    */
466   props[PROP_SHOW_PEEK_ICON] =
467       g_param_spec_boolean ("show-peek-icon",
468                             P_("Show Peek Icon"),
469                             P_("Whether to show an icon for revealing the content"),
470                             FALSE,
471                             GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
472 
473   /**
474    * GtkPasswordEntry:extra-menu: (attributes org.gtk.Property.get=gtk_password_entry_get_extra_menu org.gtk.Property.set=gtk_password_entry_set_extra_menu)
475    *
476    * A menu model whose contents will be appended to
477    * the context menu.
478    */
479   props[PROP_EXTRA_MENU] =
480       g_param_spec_object ("extra-menu",
481                            P_("Extra menu"),
482                            P_("Model menu to append to the context menu"),
483                            G_TYPE_MENU_MODEL,
484                            GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
485 
486   g_object_class_install_properties (object_class, NUM_PROPERTIES, props);
487   gtk_editable_install_properties (object_class, NUM_PROPERTIES);
488 
489   /**
490    * GtkPasswordEntry::activate:
491    * @self: The widget on which the signal is emitted
492    *
493    * Emitted when the entry is activated.
494    *
495    * The keybindings for this signal are all forms of the Enter key.
496    */
497   signals[ACTIVATE] =
498     g_signal_new (I_("activate"),
499                   G_OBJECT_CLASS_TYPE (object_class),
500                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
501                   0,
502                   NULL, NULL,
503                   NULL,
504                   G_TYPE_NONE, 0);
505 
506   gtk_widget_class_set_css_name (widget_class, I_("entry"));
507   gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_TEXT_BOX);
508 }
509 
510 static GtkEditable *
gtk_password_entry_get_delegate(GtkEditable * editable)511 gtk_password_entry_get_delegate (GtkEditable *editable)
512 {
513   GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (editable);
514 
515   return GTK_EDITABLE (entry->entry);
516 }
517 
518 static void
gtk_password_entry_editable_init(GtkEditableInterface * iface)519 gtk_password_entry_editable_init (GtkEditableInterface *iface)
520 {
521   iface->get_delegate = gtk_password_entry_get_delegate;
522 }
523 
524 static gboolean
gtk_password_entry_accessible_get_platform_state(GtkAccessible * self,GtkAccessiblePlatformState state)525 gtk_password_entry_accessible_get_platform_state (GtkAccessible              *self,
526                                                   GtkAccessiblePlatformState  state)
527 {
528   GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (self);
529 
530   switch (state)
531     {
532     case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE:
533       return gtk_widget_get_focusable (GTK_WIDGET (entry->entry));
534     case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED:
535       return gtk_widget_has_focus (GTK_WIDGET (entry->entry));
536     case GTK_ACCESSIBLE_PLATFORM_STATE_ACTIVE:
537       return FALSE;
538     default:
539       g_assert_not_reached ();
540     }
541 }
542 
543 static void
gtk_password_entry_accessible_init(GtkAccessibleInterface * iface)544 gtk_password_entry_accessible_init (GtkAccessibleInterface *iface)
545 {
546   GtkAccessibleInterface *parent_iface = g_type_interface_peek_parent (iface);
547   iface->get_at_context = parent_iface->get_at_context;
548   iface->get_platform_state = gtk_password_entry_accessible_get_platform_state;
549 }
550 
551 /*< private >
552  * gtk_password_entry_get_text_widget
553  * @entry: a `GtkPasswordEntry`
554  *
555  * Retrieves the `GtkText` delegate of the `GtkPasswordEntry`.
556  *
557  * Returns: (transfer none): the `GtkText` delegate widget
558  */
559 GtkText *
gtk_password_entry_get_text_widget(GtkPasswordEntry * entry)560 gtk_password_entry_get_text_widget (GtkPasswordEntry *entry)
561 {
562   g_return_val_if_fail (GTK_IS_PASSWORD_ENTRY (entry), NULL);
563 
564   return GTK_TEXT (entry->entry);
565 }
566 
567 /**
568  * gtk_password_entry_new:
569  *
570  * Creates a `GtkPasswordEntry`.
571  *
572  * Returns: a new `GtkPasswordEntry`
573  */
574 GtkWidget *
gtk_password_entry_new(void)575 gtk_password_entry_new (void)
576 {
577   return GTK_WIDGET (g_object_new (GTK_TYPE_PASSWORD_ENTRY, NULL));
578 }
579 
580 /**
581  * gtk_password_entry_set_show_peek_icon: (attributes org.gtk.Method.set_property=show-peek-icon)
582  * @entry: a `GtkPasswordEntry`
583  * @show_peek_icon: whether to show the peek icon
584  *
585  * Sets whether the entry should have a clickable icon
586  * to reveal the contents.
587  *
588  * Setting this to %FALSE also hides the text again.
589  */
590 void
gtk_password_entry_set_show_peek_icon(GtkPasswordEntry * entry,gboolean show_peek_icon)591 gtk_password_entry_set_show_peek_icon (GtkPasswordEntry *entry,
592                                        gboolean          show_peek_icon)
593 {
594   g_return_if_fail (GTK_IS_PASSWORD_ENTRY (entry));
595 
596   show_peek_icon = !!show_peek_icon;
597 
598   if (show_peek_icon == (entry->peek_icon != NULL))
599     return;
600 
601   if (show_peek_icon)
602     {
603       GtkGesture *press;
604 
605       entry->peek_icon = gtk_image_new_from_icon_name ("eye-not-looking-symbolic");
606       gtk_widget_set_tooltip_text (entry->peek_icon, _("Show Text"));
607       gtk_widget_set_parent (entry->peek_icon, GTK_WIDGET (entry));
608 
609       press = gtk_gesture_click_new ();
610       g_signal_connect_swapped (press, "released",
611                                 G_CALLBACK (gtk_password_entry_toggle_peek), entry);
612       gtk_widget_add_controller (entry->peek_icon, GTK_EVENT_CONTROLLER (press));
613 
614       g_signal_connect (entry->entry, "notify::visibility",
615                         G_CALLBACK (visibility_toggled), entry);
616       visibility_toggled (G_OBJECT (entry->entry), NULL, entry);
617     }
618   else
619     {
620       g_clear_pointer (&entry->peek_icon, gtk_widget_unparent);
621       gtk_text_set_visibility (GTK_TEXT (entry->entry), FALSE);
622       g_signal_handlers_disconnect_by_func (entry->entry,
623                                             visibility_toggled,
624                                             entry);
625     }
626 
627   if (entry->keyboard)
628     caps_lock_state_changed (entry->keyboard, NULL, GTK_WIDGET (entry));
629 
630   g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_SHOW_PEEK_ICON]);
631 }
632 
633 /**
634  * gtk_password_entry_get_show_peek_icon: (attributes org.gtk.Method.get_property=show-peek-icon)
635  * @entry: a `GtkPasswordEntry`
636  *
637  * Returns whether the entry is showing an icon to
638  * reveal the contents.
639  *
640  * Returns: %TRUE if an icon is shown
641  */
642 gboolean
gtk_password_entry_get_show_peek_icon(GtkPasswordEntry * entry)643 gtk_password_entry_get_show_peek_icon (GtkPasswordEntry *entry)
644 {
645   g_return_val_if_fail (GTK_IS_PASSWORD_ENTRY (entry), FALSE);
646 
647   return entry->peek_icon != NULL;
648 }
649 
650 /**
651  * gtk_password_entry_set_extra_menu: (attributes org.gtk.Method.set_property=extra-menu)
652  * @entry: a `GtkPasswordEntry`
653  * @model: (nullable): a `GMenuModel`
654  *
655  * Sets a menu model to add when constructing
656  * the context menu for @entry.
657  */
658 void
gtk_password_entry_set_extra_menu(GtkPasswordEntry * entry,GMenuModel * model)659 gtk_password_entry_set_extra_menu (GtkPasswordEntry *entry,
660                                    GMenuModel       *model)
661 {
662   GtkJoinedMenu *joined;
663   GMenu *menu;
664   GMenu *section;
665   GMenuItem *item;
666 
667   g_return_if_fail (GTK_IS_PASSWORD_ENTRY (entry));
668 
669   /* bypass this check for the initial call from init */
670   if (entry->extra_menu)
671     {
672       if (!g_set_object (&entry->extra_menu, model))
673         return;
674     }
675 
676   joined = gtk_joined_menu_new ();
677   menu = g_menu_new ();
678 
679   section = g_menu_new ();
680   item = g_menu_item_new (_("_Show Text"), "misc.toggle-visibility");
681   g_menu_item_set_attribute (item, "touch-icon", "s", "eye-not-looking-symbolic");
682   g_menu_append_item (section, item);
683   g_object_unref (item);
684 
685   g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
686   g_object_unref (section);
687 
688   gtk_joined_menu_append_menu (joined, G_MENU_MODEL (menu));
689   g_object_unref (menu);
690 
691   if (model)
692     gtk_joined_menu_append_menu (joined, model);
693 
694   gtk_text_set_extra_menu (GTK_TEXT (entry->entry), G_MENU_MODEL (joined));
695 
696   g_object_unref (joined);
697 
698   g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_EXTRA_MENU]);
699 }
700 
701 /**
702  * gtk_password_entry_get_extra_menu: (attributes org.gtk.Method.get_property=extra-menu)
703  * @entry: a `GtkPasswordEntry`
704  *
705  * Gets the menu model set with gtk_password_entry_set_extra_menu().
706  *
707  * Returns: (transfer none): (nullable): the menu model
708  */
709 GMenuModel *
gtk_password_entry_get_extra_menu(GtkPasswordEntry * entry)710 gtk_password_entry_get_extra_menu (GtkPasswordEntry *entry)
711 {
712   return entry->extra_menu;
713 }
714