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 *; |
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 | , |
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 | |
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 (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 | |
135 | static void |
136 | focus_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 | |
144 | static void |
145 | gtk_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 | */ |
156 | void |
157 | gtk_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 | |
165 | static void |
166 | visibility_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 | |
185 | static void |
186 | activate_cb (GtkPasswordEntry *entry) |
187 | { |
188 | g_signal_emit (instance: entry, signal_id: signals[ACTIVATE], detail: 0); |
189 | } |
190 | |
191 | static void |
192 | catchall_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 | |
201 | static void |
202 | gtk_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 | |
235 | static void |
236 | gtk_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 | |
255 | static void |
256 | gtk_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 | |
274 | static void |
275 | gtk_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 | |
326 | static void |
327 | gtk_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 | |
361 | static void |
362 | gtk_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 | |
388 | static void |
389 | gtk_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 | |
431 | static gboolean |
432 | gtk_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 | |
442 | static void |
443 | gtk_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 | |
533 | static GtkEditable * |
534 | gtk_password_entry_get_delegate (GtkEditable *editable) |
535 | { |
536 | GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (editable); |
537 | |
538 | return GTK_EDITABLE (entry->entry); |
539 | } |
540 | |
541 | static void |
542 | gtk_password_entry_editable_init (GtkEditableInterface *iface) |
543 | { |
544 | iface->get_delegate = gtk_password_entry_get_delegate; |
545 | } |
546 | |
547 | static gboolean |
548 | gtk_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 | |
566 | static void |
567 | gtk_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 | */ |
582 | GtkText * |
583 | gtk_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 | */ |
597 | GtkWidget * |
598 | gtk_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 | */ |
613 | void |
614 | gtk_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 | */ |
667 | gboolean |
668 | gtk_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 | */ |
683 | void |
684 | (GtkPasswordEntry *entry, |
685 | GMenuModel *model) |
686 | { |
687 | GtkJoinedMenu *joined; |
688 | GMenu *; |
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 | */ |
734 | GMenuModel * |
735 | (GtkPasswordEntry *entry) |
736 | { |
737 | return entry->extra_menu; |
738 | } |
739 | |