[gtk/peek-password: 6/11] password entry: Add a way to see the content



commit 1b504082e58787c659ee5b4d2b895919bb906028
Author: Matthias Clasen <mclasen redhat com>
Date:   Wed Mar 13 16:27:07 2019 -0400

    password entry: Add a way to see the content
    
    Add a ::show-peek-icon property and show a clickable
    icon when it is set. Clicking it toggles the visibility
    of the content. The same functionality is also accessible
    via a context menu item.
    
    This is a common feature of password entries.

 gtk/gtkpasswordentry.c                             | 131 ++++++++++++++++++++-
 gtk/gtkpasswordentry.h                             |   6 +
 .../password-invisible-symbolic.symbolic.png       | Bin 0 -> 168 bytes
 .../status/password-visible-symbolic.symbolic.png  | Bin 0 -> 177 bytes
 .../status/password-invisible-symbolic.svg         |   7 ++
 .../scalable/status/password-visible-symbolic.svg  |   7 ++
 tests/testentryicons.c                             |   1 +
 7 files changed, 149 insertions(+), 3 deletions(-)
---
diff --git a/gtk/gtkpasswordentry.c b/gtk/gtkpasswordentry.c
index 847056eecf..ada47e39fe 100644
--- a/gtk/gtkpasswordentry.c
+++ b/gtk/gtkpasswordentry.c
@@ -26,9 +26,12 @@
 #include "gtkbindings.h"
 #include "gtktextprivate.h"
 #include "gtkeditable.h"
+#include "gtkgesturemultipress.h"
 #include "gtkbox.h"
 #include "gtkimage.h"
+#include "gtkcheckmenuitem.h"
 #include "gtkintl.h"
+#include "gtkprivate.h"
 #include "gtkmarshalers.h"
 #include "gtkstylecontext.h"
 #include "gtkeventcontrollerkey.h"
@@ -54,8 +57,16 @@ typedef struct {
   GtkWidget *entry;
   GtkWidget *icon;
   GdkKeymap *keymap;
+  GtkWidget *peek_icon;
 } GtkPasswordEntryPrivate;
 
+enum {
+  PROP_SHOW_PEEK_ICON = 1,
+  NUM_PROPERTIES 
+};
+
+static GParamSpec *props[NUM_PROPERTIES] = { NULL, };
+
 static void gtk_password_entry_editable_init (GtkEditableInterface *iface);
 
 G_DEFINE_TYPE_WITH_CODE (GtkPasswordEntry, gtk_password_entry, GTK_TYPE_WIDGET,
@@ -87,6 +98,44 @@ focus_changed (GtkWidget *widget)
     keymap_state_changed (priv->keymap, widget);
 }
 
+static void
+gtk_password_entry_toggle_peek (GtkPasswordEntry *entry)
+{
+  GtkPasswordEntryPrivate *priv = gtk_password_entry_get_instance_private (entry);
+
+  if (gtk_text_get_visibility (GTK_TEXT (priv->entry)))
+    {
+      gtk_text_set_visibility (GTK_TEXT (priv->entry), FALSE);
+      gtk_image_set_from_icon_name (GTK_IMAGE (priv->peek_icon), "password-invisible");
+    }
+  else
+    {
+      gtk_text_set_visibility (GTK_TEXT (priv->entry), TRUE);
+      gtk_image_set_from_icon_name (GTK_IMAGE (priv->peek_icon), "password-visible");
+    }
+}
+
+static void
+populate_popup (GtkText          *text,
+                GtkWidget        *popup,
+                GtkPasswordEntry *entry)
+{
+  GtkPasswordEntryPrivate *priv = gtk_password_entry_get_instance_private (entry);
+
+  if (priv->peek_icon != NULL)
+    {
+      GtkWidget *item;
+
+      item = gtk_check_menu_item_new_with_mnemonic (_("_Show text"));
+      gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item),
+                                      gtk_text_get_visibility (text));
+      g_signal_connect_swapped (item, "activate",
+                                G_CALLBACK (gtk_password_entry_toggle_peek), entry);
+      gtk_widget_show (item);
+      gtk_menu_shell_append (GTK_MENU_SHELL (popup), item);
+    }
+}
+
 static void
 gtk_password_entry_init (GtkPasswordEntry *entry)
 {
@@ -106,6 +155,7 @@ gtk_password_entry_init (GtkPasswordEntry *entry)
   gtk_container_add (GTK_CONTAINER (priv->box), priv->entry);
   gtk_editable_init_delegate (GTK_EDITABLE (entry));
   g_signal_connect_swapped (priv->entry, "notify::has-focus", G_CALLBACK (focus_changed), entry);
+  g_signal_connect (priv->entry, "populate-popup", G_CALLBACK (populate_popup), entry);
 
   priv->icon = gtk_image_new_from_icon_name ("dialog-warning-symbolic");
   gtk_widget_set_tooltip_text (priv->icon, _("Caps Lock is on"));
@@ -141,6 +191,7 @@ gtk_password_entry_dispose (GObject *object)
 
   g_clear_pointer (&priv->entry, gtk_widget_unparent);
   g_clear_pointer (&priv->icon, gtk_widget_unparent);
+  g_clear_pointer (&priv->peek_icon, gtk_widget_unparent);
   g_clear_pointer (&priv->box, gtk_widget_unparent);
 
   G_OBJECT_CLASS (gtk_password_entry_parent_class)->dispose (object);
@@ -158,10 +209,21 @@ gtk_password_entry_set_property (GObject      *object,
                                  const GValue *value,
                                  GParamSpec   *pspec)
 {
+  GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (object);
+
   if (gtk_editable_delegate_set_property (object, prop_id, value, pspec))
     return;
 
-  G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  switch (prop_id)
+    {
+    case PROP_SHOW_PEEK_ICON:
+      gtk_password_entry_set_show_peek_icon (entry, g_value_get_boolean (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
 }
 
 static void
@@ -170,10 +232,21 @@ gtk_password_entry_get_property (GObject    *object,
                                  GValue     *value,
                                  GParamSpec *pspec)
 {
+  GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (object);
+
   if (gtk_editable_delegate_get_property (object, prop_id, value, pspec))
     return;
 
-  G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  switch (prop_id)
+    {
+    case PROP_SHOW_PEEK_ICON:
+      g_value_set_boolean (value, gtk_password_entry_get_show_peek_icon (entry));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
 }
 
 static void
@@ -257,7 +330,15 @@ gtk_password_entry_class_init (GtkPasswordEntryClass *klass)
   widget_class->grab_focus = gtk_password_entry_grab_focus;
   widget_class->mnemonic_activate = gtk_password_entry_mnemonic_activate;
  
-  gtk_editable_install_properties (object_class, 1);
+  props[PROP_SHOW_PEEK_ICON] =
+      g_param_spec_boolean ("show-peek-icon",
+                            P_("Show Peek Icon"),
+                            P_("Whether to show an icon for revealing the content"),
+                            FALSE,
+                            GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
+
+  g_object_class_install_properties (object_class, NUM_PROPERTIES, props);
+  gtk_editable_install_properties (object_class, NUM_PROPERTIES);
 
   gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_ENTRY_ACCESSIBLE);
   gtk_widget_class_set_css_name (widget_class, I_("entry"));
@@ -290,3 +371,47 @@ gtk_password_entry_new (void)
 {
   return GTK_WIDGET (g_object_new (GTK_TYPE_PASSWORD_ENTRY, NULL));
 }
+
+void
+gtk_password_entry_set_show_peek_icon (GtkPasswordEntry *entry,
+                                       gboolean          show_peek_icon)
+{
+  GtkPasswordEntryPrivate *priv = gtk_password_entry_get_instance_private (entry);
+
+  g_return_if_fail (GTK_IS_PASSWORD_ENTRY (entry));
+
+  if (show_peek_icon == (priv->peek_icon != NULL))
+    return;
+
+  if (show_peek_icon)
+    {
+      GtkGesture *press;
+
+      priv->peek_icon = gtk_image_new_from_icon_name ("password-invisible");
+      gtk_style_context_add_class (gtk_widget_get_style_context (priv->peek_icon), "clickable");
+      gtk_widget_set_tooltip_text (priv->peek_icon, _("Show text"));
+      gtk_container_add (GTK_CONTAINER (priv->box), priv->peek_icon);
+
+      press = gtk_gesture_multi_press_new ();
+      g_signal_connect_swapped (press, "released",
+                                G_CALLBACK (gtk_password_entry_toggle_peek), entry);
+      gtk_widget_add_controller (priv->peek_icon, GTK_EVENT_CONTROLLER (press));
+    }
+  else
+    {
+      g_clear_pointer (&priv->peek_icon, gtk_widget_unparent);
+      gtk_text_set_visibility (GTK_TEXT (priv->entry), FALSE);
+    }
+
+  g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_SHOW_PEEK_ICON]);
+}
+
+gboolean
+gtk_password_entry_get_show_peek_icon (GtkPasswordEntry *entry)
+{
+  GtkPasswordEntryPrivate *priv = gtk_password_entry_get_instance_private (entry);
+
+  g_return_val_if_fail (GTK_IS_PASSWORD_ENTRY (entry), FALSE);
+
+  return priv->peek_icon != NULL;
+}
diff --git a/gtk/gtkpasswordentry.h b/gtk/gtkpasswordentry.h
index 66de3ee961..2527c58ca2 100644
--- a/gtk/gtkpasswordentry.h
+++ b/gtk/gtkpasswordentry.h
@@ -55,6 +55,12 @@ GType           gtk_password_entry_get_type (void) G_GNUC_CONST;
 GDK_AVAILABLE_IN_ALL
 GtkWidget *     gtk_password_entry_new      (void);
 
+GDK_AVAILABLE_IN_ALL
+void            gtk_password_entry_set_show_peek_icon (GtkPasswordEntry *entry,
+                                                       gboolean          show_peek_icon);
+GDK_AVAILABLE_IN_ALL
+gboolean        gtk_password_entry_get_show_peek_icon (GtkPasswordEntry *entry);
+
 G_END_DECLS
 
 #endif /* __GTK_PASSWORD_ENTRY_H__ */
diff --git a/gtk/icons/16x16/status/password-invisible-symbolic.symbolic.png 
b/gtk/icons/16x16/status/password-invisible-symbolic.symbolic.png
new file mode 100644
index 0000000000..500525e817
Binary files /dev/null and b/gtk/icons/16x16/status/password-invisible-symbolic.symbolic.png differ
diff --git a/gtk/icons/16x16/status/password-visible-symbolic.symbolic.png 
b/gtk/icons/16x16/status/password-visible-symbolic.symbolic.png
new file mode 100644
index 0000000000..b64050770d
Binary files /dev/null and b/gtk/icons/16x16/status/password-visible-symbolic.symbolic.png differ
diff --git a/gtk/icons/scalable/status/password-invisible-symbolic.svg 
b/gtk/icons/scalable/status/password-invisible-symbolic.svg
new file mode 100644
index 0000000000..8989e833df
--- /dev/null
+++ b/gtk/icons/scalable/status/password-invisible-symbolic.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg"; width="16" height="16">
+    <g color="#bebebe" fill="#474747">
+        <path d="M3 7h10c.554 0 1 .446 1 1v3c0 .554-.446 1-1 1H3c-.554 0-1-.446-1-1V8c0-.554.446-1 1-1z" 
style="marker:none" overflow="visible"/>
+        <path d="M7 1s-.709-.014-1.447.355C4.814 1.725 4 2.667 4 4v4h2V4c0-.667.186-.725.447-.855C6.71 3.014 
7 3 7 3h2s.291.014.553.145c.261.13.447.188.447.855v4h2V4c0-1.333-.814-2.275-1.553-2.645C9.71.986 9 1 9 1z" 
style="line-height:normal;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000;text-transform:none;text-orientation:mixed;shape-padding:0;isolation:auto;mix-blend-mode:normal;marker:none"
 font-weight="400" font-family="sans-serif" white-space="normal" overflow="visible"/>
+        <path d="M2 10h12v4H2z" style="marker:none" overflow="visible"/>
+    </g>
+</svg>
diff --git a/gtk/icons/scalable/status/password-visible-symbolic.svg 
b/gtk/icons/scalable/status/password-visible-symbolic.svg
new file mode 100644
index 0000000000..5857953ccf
--- /dev/null
+++ b/gtk/icons/scalable/status/password-visible-symbolic.svg
@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg"; width="16" height="16">
+    <g color="#bebebe" fill="#474747">
+        <path d="M3 9h10c.554 0 1 .446 1 1v3c0 .554-.446 1-1 1H3c-.554 0-1-.446-1-1v-3c0-.554.446-1 1-1z" 
style="marker:none" overflow="visible"/>
+        <path d="M7 0s-.709-.014-1.447.356C4.814.725 4 1.666 4 3v3h2V3c0-.667.186-.725.447-.855C6.71 2.014 7 
2 7 2h2s.291.014.553.145c.261.13.447.188.447.855v8h2V3c0-1.333-.814-2.275-1.553-2.644C9.71-.014 9 0 9 0z" 
style="line-height:normal;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000;text-transform:none;text-orientation:mixed;shape-padding:0;isolation:auto;mix-blend-mode:normal;marker:none"
 font-weight="400" font-family="sans-serif" white-space="normal" overflow="visible"/>
+        <path d="M2 12h12v4H2z" style="marker:none" overflow="visible"/>
+    </g>
+</svg>
diff --git a/tests/testentryicons.c b/tests/testentryicons.c
index 2dc2756ddb..07c9fb6ebc 100644
--- a/tests/testentryicons.c
+++ b/tests/testentryicons.c
@@ -242,6 +242,7 @@ main (int argc, char **argv)
   gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
 
   entry = gtk_password_entry_new ();
+  gtk_password_entry_set_show_peek_icon (GTK_PASSWORD_ENTRY (entry), TRUE);
   gtk_widget_set_hexpand (entry, TRUE);
   gtk_grid_attach (GTK_GRID (grid), entry, 1, 3, 1, 1);
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]