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
79struct _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
90struct _GtkPasswordEntryClass
91{
92 GtkWidgetClass parent_class;
93};
94
95enum {
96 ACTIVATE,
97 LAST_SIGNAL
98};
99
100static guint signals[LAST_SIGNAL] = { 0, };
101
102enum {
103 PROP_PLACEHOLDER_TEXT = 1,
104 PROP_ACTIVATES_DEFAULT,
105 PROP_SHOW_PEEK_ICON,
106 PROP_EXTRA_MENU,
107 NUM_PROPERTIES
108};
109
110static GParamSpec *props[NUM_PROPERTIES] = { NULL, };
111
112static void gtk_password_entry_editable_init (GtkEditableInterface *iface);
113static void gtk_password_entry_accessible_init (GtkAccessibleInterface *iface);
114
115G_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
119static void
120caps_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 (widget: entry->entry) &&
128 !gtk_text_get_visibility (GTK_TEXT (entry->entry)) &&
129 gdk_device_get_caps_lock_state (device))
130 gtk_widget_show (widget: entry->icon);
131 else
132 gtk_widget_hide (widget: entry->icon);
133}
134
135static void
136focus_changed (GtkWidget *widget)
137{
138 GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (widget);
139
140 if (entry->keyboard)
141 caps_lock_state_changed (device: entry->keyboard, NULL, widget);
142}
143
144static void
145gtk_password_entry_icon_press (GtkGesture *gesture)
146{
147 gtk_gesture_set_state (gesture, state: GTK_EVENT_SEQUENCE_CLAIMED);
148}
149
150/*< private >
151 * gtk_password_entry_toggle_peek:
152 * @entry: a `GtkPasswordEntry`
153 *
154 * Toggles the text visibility.
155 */
156void
157gtk_password_entry_toggle_peek (GtkPasswordEntry *entry)
158{
159 gboolean visibility;
160
161 visibility = gtk_text_get_visibility (GTK_TEXT (entry->entry));
162 gtk_text_set_visibility (GTK_TEXT (entry->entry), visible: !visibility);
163}
164
165static void
166visibility_toggled (GObject *object,
167 GParamSpec *pspec,
168 GtkPasswordEntry *entry)
169{
170 if (gtk_text_get_visibility (GTK_TEXT (entry->entry)))
171 {
172 gtk_image_set_from_icon_name (GTK_IMAGE (entry->peek_icon), icon_name: "eye-open-negative-filled-symbolic");
173 gtk_widget_set_tooltip_text (widget: entry->peek_icon, _("Hide Text"));
174 }
175 else
176 {
177 gtk_image_set_from_icon_name (GTK_IMAGE (entry->peek_icon), icon_name: "eye-not-looking-symbolic");
178 gtk_widget_set_tooltip_text (widget: entry->peek_icon, _("Show Text"));
179 }
180
181 if (entry->keyboard)
182 caps_lock_state_changed (device: entry->keyboard, NULL, GTK_WIDGET (entry));
183}
184
185static void
186activate_cb (GtkPasswordEntry *entry)
187{
188 g_signal_emit (instance: entry, signal_id: signals[ACTIVATE], detail: 0);
189}
190
191static void
192catchall_click_press (GtkGestureClick *gesture,
193 int n_press,
194 double x,
195 double y,
196 gpointer user_data)
197{
198 gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_CLAIMED);
199}
200
201static void
202gtk_password_entry_init (GtkPasswordEntry *entry)
203{
204 GtkGesture *catchall;
205 GtkEntryBuffer *buffer = gtk_password_entry_buffer_new ();
206
207 entry->entry = gtk_text_new ();
208 gtk_text_set_buffer (GTK_TEXT (entry->entry), buffer);
209 gtk_text_set_visibility (GTK_TEXT (entry->entry), FALSE);
210 gtk_widget_set_parent (widget: entry->entry, GTK_WIDGET (entry));
211 gtk_editable_init_delegate (GTK_EDITABLE (entry));
212 g_signal_connect_swapped (entry->entry, "notify::has-focus", G_CALLBACK (focus_changed), entry);
213 g_signal_connect_swapped (entry->entry, "activate", G_CALLBACK (activate_cb), entry);
214
215 entry->icon = gtk_image_new_from_icon_name (icon_name: "caps-lock-symbolic");
216 gtk_widget_set_tooltip_text (widget: entry->icon, _("Caps Lock is on"));
217 gtk_widget_add_css_class (widget: entry->icon, css_class: "caps-lock-indicator");
218 gtk_widget_set_cursor (widget: entry->icon, cursor: gtk_widget_get_cursor (widget: entry->entry));
219 gtk_widget_set_parent (widget: entry->icon, GTK_WIDGET (entry));
220
221 catchall = gtk_gesture_click_new ();
222 g_signal_connect (catchall, "pressed",
223 G_CALLBACK (catchall_click_press), entry);
224 gtk_widget_add_controller (GTK_WIDGET (entry),
225 GTK_EVENT_CONTROLLER (catchall));
226
227 gtk_widget_add_css_class (GTK_WIDGET (entry), I_("password"));
228
229 gtk_password_entry_set_extra_menu (entry, NULL);
230
231 /* Transfer ownership to the GtkText widget */
232 g_object_unref (object: buffer);
233}
234
235static void
236gtk_password_entry_realize (GtkWidget *widget)
237{
238 GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (widget);
239 GdkSeat *seat;
240
241 GTK_WIDGET_CLASS (gtk_password_entry_parent_class)->realize (widget);
242
243 seat = gdk_display_get_default_seat (display: gtk_widget_get_display (widget));
244 if (seat)
245 entry->keyboard = gdk_seat_get_keyboard (seat);
246
247 if (entry->keyboard)
248 {
249 g_signal_connect (entry->keyboard, "notify::caps-lock-state",
250 G_CALLBACK (caps_lock_state_changed), entry);
251 caps_lock_state_changed (device: entry->keyboard, NULL, widget);
252 }
253}
254
255static void
256gtk_password_entry_dispose (GObject *object)
257{
258 GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (object);
259
260 if (entry->keyboard)
261 g_signal_handlers_disconnect_by_func (entry->keyboard, caps_lock_state_changed, entry);
262
263 if (entry->entry)
264 gtk_editable_finish_delegate (GTK_EDITABLE (entry));
265
266 g_clear_pointer (&entry->entry, gtk_widget_unparent);
267 g_clear_pointer (&entry->icon, gtk_widget_unparent);
268 g_clear_pointer (&entry->peek_icon, gtk_widget_unparent);
269 g_clear_object (&entry->extra_menu);
270
271 G_OBJECT_CLASS (gtk_password_entry_parent_class)->dispose (object);
272}
273
274static void
275gtk_password_entry_set_property (GObject *object,
276 guint prop_id,
277 const GValue *value,
278 GParamSpec *pspec)
279{
280 GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (object);
281 const char *text;
282
283 if (gtk_editable_delegate_set_property (object, prop_id, value, pspec))
284 {
285 if (prop_id == NUM_PROPERTIES + GTK_EDITABLE_PROP_EDITABLE)
286 {
287 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: entry),
288 first_property: GTK_ACCESSIBLE_PROPERTY_READ_ONLY, !g_value_get_boolean (value),
289 -1);
290 }
291 return;
292 }
293
294 switch (prop_id)
295 {
296 case PROP_PLACEHOLDER_TEXT:
297 text = g_value_get_string (value);
298 gtk_text_set_placeholder_text (GTK_TEXT (entry->entry), text);
299 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: entry),
300 first_property: GTK_ACCESSIBLE_PROPERTY_PLACEHOLDER, text,
301 -1);
302 break;
303
304 case PROP_ACTIVATES_DEFAULT:
305 if (gtk_text_get_activates_default (GTK_TEXT (entry->entry)) != g_value_get_boolean (value))
306 {
307 gtk_text_set_activates_default (GTK_TEXT (entry->entry), activates: g_value_get_boolean (value));
308 g_object_notify_by_pspec (object, pspec);
309 }
310 break;
311
312 case PROP_SHOW_PEEK_ICON:
313 gtk_password_entry_set_show_peek_icon (entry, show_peek_icon: g_value_get_boolean (value));
314 break;
315
316 case PROP_EXTRA_MENU:
317 gtk_password_entry_set_extra_menu (entry, model: g_value_get_object (value));
318 break;
319
320 default:
321 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
322 break;
323 }
324}
325
326static void
327gtk_password_entry_get_property (GObject *object,
328 guint prop_id,
329 GValue *value,
330 GParamSpec *pspec)
331{
332 GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (object);
333
334 if (gtk_editable_delegate_get_property (object, prop_id, value, pspec))
335 return;
336
337 switch (prop_id)
338 {
339 case PROP_PLACEHOLDER_TEXT:
340 g_value_set_string (value, v_string: gtk_text_get_placeholder_text (GTK_TEXT (entry->entry)));
341 break;
342
343 case PROP_ACTIVATES_DEFAULT:
344 g_value_set_boolean (value, v_boolean: gtk_text_get_activates_default (GTK_TEXT (entry->entry)));
345 break;
346
347 case PROP_SHOW_PEEK_ICON:
348 g_value_set_boolean (value, v_boolean: gtk_password_entry_get_show_peek_icon (entry));
349 break;
350
351 case PROP_EXTRA_MENU:
352 g_value_set_object (value, v_object: entry->extra_menu);
353 break;
354
355 default:
356 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
357 break;
358 }
359}
360
361static void
362gtk_password_entry_measure (GtkWidget *widget,
363 GtkOrientation orientation,
364 int for_size,
365 int *minimum,
366 int *natural,
367 int *minimum_baseline,
368 int *natural_baseline)
369{
370 GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (widget);
371 int icon_min = 0, icon_nat = 0;
372
373 gtk_widget_measure (widget: entry->entry, orientation, for_size,
374 minimum, natural,
375 minimum_baseline, natural_baseline);
376
377 if (entry->icon && gtk_widget_get_visible (widget: entry->icon))
378 gtk_widget_measure (widget: entry->icon, orientation, for_size,
379 minimum: &icon_min, natural: &icon_nat,
380 NULL, NULL);
381
382 if (entry->peek_icon && gtk_widget_get_visible (widget: entry->peek_icon))
383 gtk_widget_measure (widget: entry->peek_icon, orientation, for_size,
384 minimum: &icon_min, natural: &icon_nat,
385 NULL, NULL);
386}
387
388static void
389gtk_password_entry_size_allocate (GtkWidget *widget,
390 int width,
391 int height,
392 int baseline)
393{
394 GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (widget);
395 GtkCssStyle *style = gtk_css_node_get_style (cssnode: gtk_widget_get_css_node (widget));
396 int icon_min = 0, icon_nat = 0;
397 int peek_min = 0, peek_nat = 0;
398 int text_width;
399 int spacing;
400
401 spacing = _gtk_css_position_value_get_x (position: style->size->border_spacing, one_hundred_percent: 100);
402
403 if (entry->icon && gtk_widget_get_visible (widget: entry->icon))
404 gtk_widget_measure (widget: entry->icon, orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1,
405 minimum: &icon_min, natural: &icon_nat,
406 NULL, NULL);
407
408 if (entry->peek_icon && gtk_widget_get_visible (widget: entry->peek_icon))
409 gtk_widget_measure (widget: entry->peek_icon, orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1,
410 minimum: &peek_min, natural: &peek_nat,
411 NULL, NULL);
412
413 text_width = width - (icon_nat + (icon_nat > 0 ? spacing : 0))
414 - (peek_nat + (peek_nat > 0 ? spacing : 0));
415
416 gtk_widget_size_allocate (widget: entry->entry,
417 allocation: &(GtkAllocation) { 0, 0, text_width, height },
418 baseline);
419
420 if (entry->icon && gtk_widget_get_visible (widget: entry->icon))
421 gtk_widget_size_allocate (widget: entry->icon,
422 allocation: &(GtkAllocation) { text_width + spacing, 0, icon_nat, height },
423 baseline);
424
425 if (entry->peek_icon && gtk_widget_get_visible (widget: entry->peek_icon))
426 gtk_widget_size_allocate (widget: entry->peek_icon,
427 allocation: &(GtkAllocation) { text_width + spacing + icon_nat + (icon_nat > 0 ? spacing : 0), 0, peek_nat, height },
428 baseline);
429}
430
431static gboolean
432gtk_password_entry_mnemonic_activate (GtkWidget *widget,
433 gboolean group_cycling)
434{
435 GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (widget);
436
437 gtk_widget_grab_focus (widget: entry->entry);
438
439 return TRUE;
440}
441
442static void
443gtk_password_entry_class_init (GtkPasswordEntryClass *klass)
444{
445 GObjectClass *object_class = G_OBJECT_CLASS (klass);
446 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
447
448 object_class->dispose = gtk_password_entry_dispose;
449 object_class->get_property = gtk_password_entry_get_property;
450 object_class->set_property = gtk_password_entry_set_property;
451
452 widget_class->realize = gtk_password_entry_realize;
453 widget_class->measure = gtk_password_entry_measure;
454 widget_class->size_allocate = gtk_password_entry_size_allocate;
455 widget_class->mnemonic_activate = gtk_password_entry_mnemonic_activate;
456 widget_class->grab_focus = gtk_widget_grab_focus_child;
457 widget_class->focus = gtk_widget_focus_child;
458
459 /**
460 * GtkPasswordEntry:placeholder-text:
461 *
462 * The text that will be displayed in the `GtkPasswordEntry`
463 * when it is empty and unfocused.
464 */
465 props[PROP_PLACEHOLDER_TEXT] =
466 g_param_spec_string (name: "placeholder-text",
467 P_("Placeholder text"),
468 P_("Show text in the entry when it’s empty and unfocused"),
469 NULL,
470 GTK_PARAM_READWRITE);
471
472 /**
473 * GtkPasswordEntry:activates-default:
474 *
475 * Whether to activate the default widget when Enter is pressed.
476 */
477 props[PROP_ACTIVATES_DEFAULT] =
478 g_param_spec_boolean (name: "activates-default",
479 P_("Activates default"),
480 P_("Whether to activate the default widget (such as the default button in a dialog) when Enter is pressed"),
481 FALSE,
482 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
483
484 /**
485 * 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)
486 *
487 * Whether to show an icon for revealing the content.
488 */
489 props[PROP_SHOW_PEEK_ICON] =
490 g_param_spec_boolean (name: "show-peek-icon",
491 P_("Show Peek Icon"),
492 P_("Whether to show an icon for revealing the content"),
493 FALSE,
494 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
495
496 /**
497 * GtkPasswordEntry:extra-menu: (attributes org.gtk.Property.get=gtk_password_entry_get_extra_menu org.gtk.Property.set=gtk_password_entry_set_extra_menu)
498 *
499 * A menu model whose contents will be appended to
500 * the context menu.
501 */
502 props[PROP_EXTRA_MENU] =
503 g_param_spec_object (name: "extra-menu",
504 P_("Extra menu"),
505 P_("Model menu to append to the context menu"),
506 G_TYPE_MENU_MODEL,
507 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
508
509 g_object_class_install_properties (oclass: object_class, n_pspecs: NUM_PROPERTIES, pspecs: props);
510 gtk_editable_install_properties (object_class, first_prop: NUM_PROPERTIES);
511
512 /**
513 * GtkPasswordEntry::activate:
514 * @self: The widget on which the signal is emitted
515 *
516 * Emitted when the entry is activated.
517 *
518 * The keybindings for this signal are all forms of the Enter key.
519 */
520 signals[ACTIVATE] =
521 g_signal_new (I_("activate"),
522 G_OBJECT_CLASS_TYPE (object_class),
523 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
524 class_offset: 0,
525 NULL, NULL,
526 NULL,
527 G_TYPE_NONE, n_params: 0);
528
529 gtk_widget_class_set_css_name (widget_class, I_("entry"));
530 gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_TEXT_BOX);
531}
532
533static GtkEditable *
534gtk_password_entry_get_delegate (GtkEditable *editable)
535{
536 GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (editable);
537
538 return GTK_EDITABLE (entry->entry);
539}
540
541static void
542gtk_password_entry_editable_init (GtkEditableInterface *iface)
543{
544 iface->get_delegate = gtk_password_entry_get_delegate;
545}
546
547static gboolean
548gtk_password_entry_accessible_get_platform_state (GtkAccessible *self,
549 GtkAccessiblePlatformState state)
550{
551 GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (self);
552
553 switch (state)
554 {
555 case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSABLE:
556 return gtk_widget_get_focusable (GTK_WIDGET (entry->entry));
557 case GTK_ACCESSIBLE_PLATFORM_STATE_FOCUSED:
558 return gtk_widget_has_focus (GTK_WIDGET (entry->entry));
559 case GTK_ACCESSIBLE_PLATFORM_STATE_ACTIVE:
560 return FALSE;
561 default:
562 g_assert_not_reached ();
563 }
564}
565
566static void
567gtk_password_entry_accessible_init (GtkAccessibleInterface *iface)
568{
569 GtkAccessibleInterface *parent_iface = g_type_interface_peek_parent (g_iface: iface);
570 iface->get_at_context = parent_iface->get_at_context;
571 iface->get_platform_state = gtk_password_entry_accessible_get_platform_state;
572}
573
574/*< private >
575 * gtk_password_entry_get_text_widget
576 * @entry: a `GtkPasswordEntry`
577 *
578 * Retrieves the `GtkText` delegate of the `GtkPasswordEntry`.
579 *
580 * Returns: (transfer none): the `GtkText` delegate widget
581 */
582GtkText *
583gtk_password_entry_get_text_widget (GtkPasswordEntry *entry)
584{
585 g_return_val_if_fail (GTK_IS_PASSWORD_ENTRY (entry), NULL);
586
587 return GTK_TEXT (entry->entry);
588}
589
590/**
591 * gtk_password_entry_new:
592 *
593 * Creates a `GtkPasswordEntry`.
594 *
595 * Returns: a new `GtkPasswordEntry`
596 */
597GtkWidget *
598gtk_password_entry_new (void)
599{
600 return GTK_WIDGET (g_object_new (GTK_TYPE_PASSWORD_ENTRY, NULL));
601}
602
603/**
604 * gtk_password_entry_set_show_peek_icon: (attributes org.gtk.Method.set_property=show-peek-icon)
605 * @entry: a `GtkPasswordEntry`
606 * @show_peek_icon: whether to show the peek icon
607 *
608 * Sets whether the entry should have a clickable icon
609 * to reveal the contents.
610 *
611 * Setting this to %FALSE also hides the text again.
612 */
613void
614gtk_password_entry_set_show_peek_icon (GtkPasswordEntry *entry,
615 gboolean show_peek_icon)
616{
617 g_return_if_fail (GTK_IS_PASSWORD_ENTRY (entry));
618
619 show_peek_icon = !!show_peek_icon;
620
621 if (show_peek_icon == (entry->peek_icon != NULL))
622 return;
623
624 if (show_peek_icon)
625 {
626 GtkGesture *press;
627
628 entry->peek_icon = gtk_image_new_from_icon_name (icon_name: "eye-not-looking-symbolic");
629 gtk_widget_set_tooltip_text (widget: entry->peek_icon, _("Show Text"));
630 gtk_widget_set_parent (widget: entry->peek_icon, GTK_WIDGET (entry));
631
632 press = gtk_gesture_click_new ();
633 g_signal_connect (press, "pressed",
634 G_CALLBACK (gtk_password_entry_icon_press), entry);
635 g_signal_connect_swapped (press, "released",
636 G_CALLBACK (gtk_password_entry_toggle_peek), entry);
637 gtk_widget_add_controller (widget: entry->peek_icon, GTK_EVENT_CONTROLLER (press));
638
639 g_signal_connect (entry->entry, "notify::visibility",
640 G_CALLBACK (visibility_toggled), entry);
641 visibility_toggled (G_OBJECT (entry->entry), NULL, entry);
642 }
643 else
644 {
645 g_clear_pointer (&entry->peek_icon, gtk_widget_unparent);
646 gtk_text_set_visibility (GTK_TEXT (entry->entry), FALSE);
647 g_signal_handlers_disconnect_by_func (entry->entry,
648 visibility_toggled,
649 entry);
650 }
651
652 if (entry->keyboard)
653 caps_lock_state_changed (device: entry->keyboard, NULL, GTK_WIDGET (entry));
654
655 g_object_notify_by_pspec (G_OBJECT (entry), pspec: props[PROP_SHOW_PEEK_ICON]);
656}
657
658/**
659 * gtk_password_entry_get_show_peek_icon: (attributes org.gtk.Method.get_property=show-peek-icon)
660 * @entry: a `GtkPasswordEntry`
661 *
662 * Returns whether the entry is showing an icon to
663 * reveal the contents.
664 *
665 * Returns: %TRUE if an icon is shown
666 */
667gboolean
668gtk_password_entry_get_show_peek_icon (GtkPasswordEntry *entry)
669{
670 g_return_val_if_fail (GTK_IS_PASSWORD_ENTRY (entry), FALSE);
671
672 return entry->peek_icon != NULL;
673}
674
675/**
676 * gtk_password_entry_set_extra_menu: (attributes org.gtk.Method.set_property=extra-menu)
677 * @entry: a `GtkPasswordEntry`
678 * @model: (nullable): a `GMenuModel`
679 *
680 * Sets a menu model to add when constructing
681 * the context menu for @entry.
682 */
683void
684gtk_password_entry_set_extra_menu (GtkPasswordEntry *entry,
685 GMenuModel *model)
686{
687 GtkJoinedMenu *joined;
688 GMenu *menu;
689 GMenu *section;
690 GMenuItem *item;
691
692 g_return_if_fail (GTK_IS_PASSWORD_ENTRY (entry));
693
694 /* bypass this check for the initial call from init */
695 if (entry->extra_menu)
696 {
697 if (!g_set_object (&entry->extra_menu, model))
698 return;
699 }
700
701 joined = gtk_joined_menu_new ();
702 menu = g_menu_new ();
703
704 section = g_menu_new ();
705 item = g_menu_item_new (_("_Show Text"), detailed_action: "misc.toggle-visibility");
706 g_menu_item_set_attribute (menu_item: item, attribute: "touch-icon", format_string: "s", "eye-not-looking-symbolic");
707 g_menu_append_item (menu: section, item);
708 g_object_unref (object: item);
709
710 g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
711 g_object_unref (object: section);
712
713 gtk_joined_menu_append_menu (self: joined, G_MENU_MODEL (menu));
714 g_object_unref (object: menu);
715
716 if (model)
717 gtk_joined_menu_append_menu (self: joined, model);
718
719 gtk_text_set_extra_menu (GTK_TEXT (entry->entry), G_MENU_MODEL (joined));
720
721 g_object_unref (object: joined);
722
723 g_object_notify_by_pspec (G_OBJECT (entry), pspec: props[PROP_EXTRA_MENU]);
724}
725
726/**
727 * gtk_password_entry_get_extra_menu: (attributes org.gtk.Method.get_property=extra-menu)
728 * @entry: a `GtkPasswordEntry`
729 *
730 * Gets the menu model set with gtk_password_entry_set_extra_menu().
731 *
732 * Returns: (transfer none) (nullable): the menu model
733 */
734GMenuModel *
735gtk_password_entry_get_extra_menu (GtkPasswordEntry *entry)
736{
737 return entry->extra_menu;
738}
739

source code of gtk/gtk/gtkpasswordentry.c