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