[libhandy/hdy_password_entry: 60/62] Add HdyPasswordEntry widget



commit f493c24aaad6df5307439b1ced67f26b168fc602
Author: Ujjwal Kumar <ujjwalkumar0501 gmail com>
Date:   Tue Mar 31 21:21:57 2020 +0530

    Add HdyPasswordEntry widget

 src/handy.gresources.xml |   2 +
 src/handy.h              |   1 +
 src/hdy-password-entry.c | 484 +++++++++++++++++++++++++++++++++++++++++++++++
 src/hdy-password-entry.h |  36 ++++
 src/meson.build          |   2 +
 5 files changed, 525 insertions(+)
---
diff --git a/src/handy.gresources.xml b/src/handy.gresources.xml
index f19ba862..33b17881 100644
--- a/src/handy.gresources.xml
+++ b/src/handy.gresources.xml
@@ -2,6 +2,8 @@
 <gresources>
   <gresource prefix="/sm/puri/handy">
     <file preprocess="xml-stripblanks">icons/hdy-expander-arrow-symbolic.svg</file>
+    <file preprocess="xml-stripblanks">icons/hdy-eye-not-looking-symbolic.svg</file>
+    <file preprocess="xml-stripblanks">icons/hdy-eye-open-symbolic.svg</file>
     <file compressed="true">themes/Adwaita.css</file>
     <file compressed="true">themes/Adwaita-dark.css</file>
     <file compressed="true">themes/fallback.css</file>
diff --git a/src/handy.h b/src/handy.h
index 9e6389f0..2bf36576 100644
--- a/src/handy.h
+++ b/src/handy.h
@@ -42,6 +42,7 @@ G_BEGIN_DECLS
 #include "hdy-leaflet.h"
 #include "hdy-list-box.h"
 #include "hdy-navigation-direction.h"
+#include "hdy-password-entry.h"
 #include "hdy-preferences-group.h"
 #include "hdy-preferences-page.h"
 #include "hdy-preferences-row.h"
diff --git a/src/hdy-password-entry.c b/src/hdy-password-entry.c
new file mode 100644
index 00000000..004ac35c
--- /dev/null
+++ b/src/hdy-password-entry.c
@@ -0,0 +1,484 @@
+/*
+ * Copyright (C) 2019 Purism SPC
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+
+#include "hdy-password-entry.h"
+
+#define DEFAULT_PEEK_DURATION 2000
+#define MIN_PEEK_DURATION 500
+
+/**
+ * SECTION:hdy-password-entry
+ * @short_description: A password entry widget.
+ * @title: HdyPasswordEntry
+ *
+ * The #HdyPasswordEntry widget is a password entry widget with toggle
+ * functionality to show entered characters for a short duration of time.
+ *
+ * # CSS nodes
+ *
+ * #HdyPasswordEntry has a single CSS node with name passwordentry.
+ *
+ * Since: 1.0
+ */
+
+typedef struct
+{
+  GtkWidget *password_entry;
+  GtkWidget *peek_icon;
+  GtkWidget *peek_icon_event_box;
+
+  GtkCssProvider *css_provider;
+
+  gboolean show_peek_icon;
+  guint peek_duration;
+  guint timeout_id;
+} HdyPasswordEntryPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (HdyPasswordEntry, hdy_password_entry, GTK_TYPE_OVERLAY);
+
+enum {
+  PROP_0,
+  PROP_PEEK_DURATION,
+  PROP_SHOW_PEEK_ICON,
+  LAST_PROP
+};
+
+enum {
+  SIGNAL_TIMED_OUT,
+  SIGNAL_LAST
+};
+
+static GParamSpec *props[LAST_PROP];
+static guint signals[SIGNAL_LAST];
+
+static void hide_text (GtkWidget*, gpointer);
+static void show_text (GtkWidget*, gpointer);
+
+static gboolean
+timeout_cb (gpointer user_data)
+{
+  hide_text (NULL, user_data);
+
+  g_signal_emit (HDY_PASSWORD_ENTRY (user_data), signals[SIGNAL_TIMED_OUT], 0);
+
+  return TRUE;
+}
+
+static void
+set_timeout (HdyPasswordEntry *entry)
+{
+  HdyPasswordEntryPrivate *priv = hdy_password_entry_get_instance_private (entry);
+
+  if (priv->timeout_id > 0)
+    g_source_remove (priv->timeout_id);
+  priv->timeout_id = g_timeout_add (priv->peek_duration,
+                                    timeout_cb,
+                                    entry);
+  g_source_set_name_by_id (priv->timeout_id, "[hdy] hdy_password_entry_peek_duration_cb");
+}
+
+static void
+peek_icon_button_press_event_cb (GtkWidget         *event_box,
+                                 GdkEventButton    *event,
+                                 HdyPasswordEntry  *entry)
+{
+  HdyPasswordEntryPrivate *priv = hdy_password_entry_get_instance_private (entry);
+
+  if ((event->type == GDK_BUTTON_PRESS &&
+        event->button == 1) ||
+       (event->type == GDK_TOUCH_BEGIN))
+  {
+    if (priv->timeout_id > 0)
+      hide_text (NULL, entry);
+    else
+      show_text (NULL, entry);
+  }
+}
+
+static void
+password_entry_size_allocated_cb (GtkWidget    *widget,
+                                  GdkRectangle *allocation,
+                                  gpointer      user_data)
+{
+  GtkStyleContext *style_context;
+  GtkStateFlags state_flags;
+  GtkBorder border, margin, padding;
+  guint spacing;
+
+  style_context = gtk_widget_get_style_context (widget);
+  state_flags = gtk_widget_get_state_flags (widget);
+  gtk_style_context_get_border (style_context, state_flags, &border);
+  gtk_style_context_get_margin (style_context, state_flags, &margin);
+  gtk_style_context_get_padding (style_context, state_flags, &padding);
+
+  spacing = margin.right + border.right;
+
+  if (padding.right == padding.left)
+    spacing += padding.right;
+  else
+    spacing += padding.right < padding.left? padding.right: padding.left;
+  gtk_widget_set_margin_end (GTK_WIDGET (user_data), spacing);
+}
+
+static void
+event_box_size_allocated_cb (GtkWidget    *widget,
+                             GdkRectangle *allocation,
+                             gpointer      user_data)
+{
+  HdyPasswordEntryPrivate *priv = hdy_password_entry_get_instance_private (HDY_PASSWORD_ENTRY (user_data));
+  g_autofree gchar *css;
+  GtkBorder padding;
+  guint extra_space;
+
+  gtk_style_context_get_padding (gtk_widget_get_style_context (priv->password_entry),
+                                 gtk_widget_get_state_flags (priv->password_entry),
+                                 &padding);
+  extra_space = allocation->width + (padding.right > padding.left ?
+                                     padding.left:
+                                     padding.right);
+
+  /* Don't add this padding when all the widgets are hidden (in which case
+   * the allocated width is 1), because it distorts the appearance of widget.
+   */
+  if (allocation->width > 6)
+    extra_space += 6;
+
+  css = g_strdup_printf (".password_entry:dir(ltr) { padding-right: %dpx; }"\
+                         ".password_entry:dir(rtl) { padding-left: %dpx; }",
+                         extra_space, extra_space);
+
+  gtk_css_provider_load_from_data (priv->css_provider, css, -1, NULL);
+}
+
+static void
+hide_text (GtkWidget *widget,
+           gpointer   user_data)
+{
+  HdyPasswordEntryPrivate *priv = hdy_password_entry_get_instance_private (HDY_PASSWORD_ENTRY (user_data));
+
+  if (priv->timeout_id > 0)
+    g_source_remove (priv->timeout_id);
+  gtk_entry_set_visibility (GTK_ENTRY (priv->password_entry), FALSE);
+  gtk_image_set_from_icon_name (GTK_IMAGE (priv->peek_icon),
+                                "hdy-eye-not-looking-symbolic",
+                                GTK_ICON_SIZE_MENU);
+  gtk_widget_set_tooltip_text (priv->peek_icon_event_box, _("Show text"));
+  priv->timeout_id = 0;
+}
+
+static void
+show_text (GtkWidget *widget,
+           gpointer user_data)
+{
+  HdyPasswordEntryPrivate *priv = hdy_password_entry_get_instance_private (HDY_PASSWORD_ENTRY (user_data));
+
+  gtk_entry_set_visibility (GTK_ENTRY (priv->password_entry), TRUE);
+  gtk_image_set_from_icon_name (GTK_IMAGE (priv->peek_icon),
+                                "hdy-eye-open-symbolic",
+                                GTK_ICON_SIZE_MENU);
+  gtk_widget_set_tooltip_text (priv->peek_icon_event_box, _("Hide text"));
+  set_timeout (HDY_PASSWORD_ENTRY (user_data));
+}
+
+static void
+populate_popup_cb (GtkEntry   *password_entry,
+                   GtkWidget  *widget,
+                   gpointer    user_data)
+{
+  GtkWidget *menuitem;
+
+  g_assert (GTK_IS_MENU (widget));
+
+  if (gtk_entry_get_visibility (password_entry))
+    {
+      menuitem = gtk_menu_item_new_with_mnemonic (_("_Hide text"));
+      g_signal_connect (menuitem, "activate",
+                        G_CALLBACK (hide_text), user_data);
+    }
+  else
+    {
+      menuitem = gtk_menu_item_new_with_mnemonic (_("_Show text"));
+      g_signal_connect (menuitem, "activate",
+                        G_CALLBACK (show_text), user_data);
+    }
+
+  gtk_widget_set_sensitive (menuitem, TRUE);
+
+  gtk_widget_show (menuitem);
+  gtk_menu_shell_append (GTK_MENU_SHELL (widget), menuitem);
+  gtk_widget_show (menuitem);
+}
+
+static void
+hdy_password_entry_dispose (GObject *object)
+{
+  HdyPasswordEntryPrivate *priv = hdy_password_entry_get_instance_private
+                                    (HDY_PASSWORD_ENTRY (object));
+
+  if (priv->timeout_id > 0)
+    g_source_remove (priv->timeout_id);
+
+  g_clear_object (&priv->css_provider);
+
+  G_OBJECT_CLASS (hdy_password_entry_parent_class)->dispose (object);
+}
+
+static void
+hdy_password_entry_get_property  (GObject    *object,
+                                  guint       prop_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec)
+{
+  HdyPasswordEntry *self = HDY_PASSWORD_ENTRY (object);
+
+  switch (prop_id) {
+  case PROP_PEEK_DURATION:
+    g_value_set_uint (value, hdy_password_entry_get_peek_duration (self));
+    break;
+
+  case PROP_SHOW_PEEK_ICON:
+    g_value_set_boolean (value, hdy_password_entry_get_show_peek_icon (self));
+    break;
+
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+hdy_password_entry_set_property (GObject      *object,
+                                 guint         prop_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+  HdyPasswordEntry *self = HDY_PASSWORD_ENTRY (object);
+
+  switch (prop_id) {
+  case PROP_PEEK_DURATION:
+    hdy_password_entry_set_peek_duration (self, g_value_get_uint (value));
+    break;
+
+  case PROP_SHOW_PEEK_ICON:
+    hdy_password_entry_set_show_peek_icon (self, g_value_get_boolean (value));
+    break;
+
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+hdy_password_entry_class_init (HdyPasswordEntryClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  /**
+   * HdyPasswordEntry::timed-out:
+   *
+   * This signal is emitted when a password peek timeout happens.
+   *
+   * Since: 1.0
+   */
+  signals[SIGNAL_TIMED_OUT] = g_signal_new ("timed-out",
+                                           G_TYPE_FROM_CLASS (klass),
+                                           G_SIGNAL_RUN_FIRST,
+                                           0,
+                                           NULL, NULL, NULL,
+                                           G_TYPE_NONE,
+                                           0);
+
+  object_class->dispose = hdy_password_entry_dispose;
+  object_class->get_property = hdy_password_entry_get_property;
+  object_class->set_property = hdy_password_entry_set_property;
+
+  /**
+   * HdyPasswordEntry::peek-duration:
+   *
+   * It has default value of 2000 Milliseconds and a Minimum
+   * duration of 500 Milliseconds.
+   *
+   * Changing this property sends a notify signal.
+   *
+   * Since: 1.0
+   */
+  props[PROP_PEEK_DURATION] = g_param_spec_uint
+                                  ("peek-duration",
+                                   _("Peek duration"),
+                                   _("Password peek duration in milliseconds"),
+                                   MIN_PEEK_DURATION,
+                                   G_MAXUINT,
+                                   DEFAULT_PEEK_DURATION,
+                                   G_PARAM_READWRITE);
+
+  /**
+   * HdyPasswordEntry::show-peek-icon:
+   *
+   * Whether to show the peek icon.
+   *
+   * Changing this property sends a notify signal.
+   *
+   * Since: 1.0
+   */
+  props[PROP_SHOW_PEEK_ICON] = g_param_spec_boolean
+                                 ("show-peek-icon",
+                                  _("Show peek icon"),
+                                  _("Whether to show the peek icon"),
+                                  TRUE,
+                                  G_PARAM_READWRITE);
+
+  g_object_class_install_properties (object_class, LAST_PROP, props);
+
+  gtk_widget_class_set_css_name (widget_class, "passwordentry");
+}
+
+static void
+hdy_password_entry_init (HdyPasswordEntry *entry)
+{
+  HdyPasswordEntryPrivate *priv = hdy_password_entry_get_instance_private (entry);
+
+  priv->peek_duration = DEFAULT_PEEK_DURATION;
+  priv->show_peek_icon = TRUE;
+  priv->password_entry = gtk_entry_new ();
+  priv->peek_icon = gtk_image_new_from_icon_name ("hdy-eye-not-looking-symbolic", GTK_ICON_SIZE_MENU);
+  priv->peek_icon_event_box = gtk_event_box_new ();
+
+  priv->css_provider = gtk_css_provider_new ();
+  gtk_style_context_add_provider (gtk_widget_get_style_context (priv->password_entry),
+                                  GTK_STYLE_PROVIDER (priv->css_provider),
+                                  GTK_STYLE_PROVIDER_PRIORITY_USER);
+
+  gtk_widget_set_halign (priv->peek_icon_event_box, GTK_ALIGN_END);
+  gtk_widget_set_tooltip_text (priv->peek_icon_event_box, _("Show text"));
+  g_signal_connect (G_OBJECT (priv->peek_icon_event_box), "button-press-event",
+                    G_CALLBACK (peek_icon_button_press_event_cb), entry);
+
+  gtk_widget_set_valign (priv->peek_icon, GTK_ALIGN_CENTER);
+
+  g_signal_connect (G_OBJECT (priv->peek_icon_event_box), "size-allocate",
+                    G_CALLBACK (event_box_size_allocated_cb), entry);
+  g_signal_connect (G_OBJECT (priv->password_entry), "size-allocate",
+                    G_CALLBACK (password_entry_size_allocated_cb),
+                    priv->peek_icon_event_box);
+  g_signal_connect (G_OBJECT (priv->password_entry), "populate-popup",
+                    G_CALLBACK (populate_popup_cb), entry);
+
+  gtk_style_context_add_class (gtk_widget_get_style_context (priv->password_entry), "password_entry");
+  g_object_set (G_OBJECT (priv->password_entry),
+                "input-purpose", GTK_INPUT_PURPOSE_PASSWORD,
+                "visibility", FALSE,
+                "placeholder-text", _("Password"),
+                "can-focus", TRUE,
+                NULL);
+
+  gtk_overlay_add_overlay (GTK_OVERLAY (entry), priv->peek_icon_event_box);
+  gtk_container_add (GTK_CONTAINER (entry), priv->password_entry);
+  gtk_container_add (GTK_CONTAINER (priv->peek_icon_event_box), priv->peek_icon);
+
+  gtk_widget_show (priv->password_entry);
+  gtk_widget_show (priv->peek_icon_event_box);
+  gtk_widget_show (priv->peek_icon);
+}
+
+/**
+ *hdy_password_entry_get_peek_duration:
+ * @entry: a HdyPasswordEntry
+ *
+ * Gets peek duration of password characters.
+ *
+ * Returns: Peek duration in milliseconds
+ *
+ * Since: 1.0
+ */
+
+guint
+hdy_password_entry_get_peek_duration (HdyPasswordEntry *entry)
+{
+  HdyPasswordEntryPrivate *priv = hdy_password_entry_get_instance_private (entry);
+
+  return priv->peek_duration;
+}
+
+/**
+ * hdy_password_entry_set_peek_duration:
+ * @entry: a HdyPasswordEntry
+ * @peek_duration: a guint
+ *
+ * Sets peek duration of password characters.
+ *
+ * Since: 1.0
+ */
+void
+hdy_password_entry_set_peek_duration (HdyPasswordEntry *entry,
+                                      guint peek_duration)
+{
+  HdyPasswordEntryPrivate *priv = hdy_password_entry_get_instance_private (entry);
+
+  priv->peek_duration = peek_duration;
+  g_object_notify_by_pspec (G_OBJECT (entry), props[PROP_PEEK_DURATION]);
+}
+
+/**
+ * hdy_password_entry_get_show_peek_icon:
+ * @entry: a HdyPasswordEntry
+ *
+ * Returns whether the entry is showing a clickable icon
+ * to reveal the contents of the entry in clear text.
+ *
+ * Since: 1.0
+ */
+gboolean
+hdy_password_entry_get_show_peek_icon (HdyPasswordEntry *entry)
+{
+  HdyPasswordEntryPrivate *priv = hdy_password_entry_get_instance_private (entry);
+
+  return priv->show_peek_icon;
+}
+
+/**
+ * hdy_password_entry_set_show_peek_icon:
+ * @entry: a HdyPasswordEntry
+ * @show_peek_icon: whether to show the peek icon
+ *
+ * Sets whether the entry should have a clickable icon
+ * to show the contents of the entry in clear text.
+ *
+ * Setting this to #FALSE also hides the text again.
+ *
+ * Since: 1.0
+ */
+void
+hdy_password_entry_set_show_peek_icon (HdyPasswordEntry *entry,
+                                       gboolean show_peek_icon)
+{
+  HdyPasswordEntryPrivate *priv = hdy_password_entry_get_instance_private (entry);
+
+  if (show_peek_icon)
+    gtk_widget_show (priv->peek_icon);
+  else
+    gtk_widget_hide (priv->peek_icon);
+
+  priv->show_peek_icon = show_peek_icon;
+}
+
+/**
+ * hdy_password_entry_new:
+ *
+ * Create a new #HdyPasswordEntry widget.
+ *
+ * Returns: The newly created #HdyPasswordEntry widget
+ *
+ * Since: 1.0
+ */
+GtkWidget *
+hdy_password_entry_new (void)
+{
+  return GTK_WIDGET (g_object_new (HDY_TYPE_PASSWORD_ENTRY, NULL));
+}
+
diff --git a/src/hdy-password-entry.h b/src/hdy-password-entry.h
new file mode 100644
index 00000000..a0fe9af4
--- /dev/null
+++ b/src/hdy-password-entry.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 Purism SPC
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#pragma once
+
+#if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION)
+#error "Only <handy.h> can be included directly."
+#endif
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define HDY_TYPE_PASSWORD_ENTRY (hdy_password_entry_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (HdyPasswordEntry, hdy_password_entry, HDY, PASSWORD_ENTRY, GtkOverlay)
+
+struct _HdyPasswordEntryClass
+{
+  GtkOverlayClass parent_class;
+};
+
+GtkWidget   *hdy_password_entry_new                   (void);
+guint        hdy_password_entry_get_peek_duration     (HdyPasswordEntry *entry);
+void         hdy_password_entry_set_peek_duration     (HdyPasswordEntry *entry,
+                                                       guint             peek_duration);
+
+gboolean     hdy_password_entry_get_show_peek_icon    (HdyPasswordEntry *entry);
+void         hdy_password_entry_set_show_peek_icon    (HdyPasswordEntry *entry,
+                                                       gboolean          show_peek_icon);
+
+
+G_END_DECLS
diff --git a/src/meson.build b/src/meson.build
index e11d95cb..fdf14d1f 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -82,6 +82,7 @@ src_headers = [
   'hdy-leaflet.h',
   'hdy-list-box.h',
   'hdy-navigation-direction.h',
+  'hdy-password-entry.h',
   'hdy-preferences-group.h',
   'hdy-preferences-page.h',
   'hdy-preferences-row.h',
@@ -132,6 +133,7 @@ src_sources = [
   'hdy-main.c',
   'hdy-navigation-direction.c',
   'hdy-nothing.c',
+  'hdy-password-entry.c',
   'hdy-preferences-group.c',
   'hdy-preferences-page.c',
   'hdy-preferences-row.c',


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