[libadwaita/entry-row: 3/5] Add AdwPasswordEntryRow




commit 1833f42581469850408555ffa53cb42557924648
Author: Maximiliano Sandoval R <msandova protonmail com>
Date:   Sun Jul 25 17:07:51 2021 +0200

    Add AdwPasswordEntryRow

 doc/boxed-lists.md                     |  11 ++
 doc/images/password-entry-row-dark.png | Bin 0 -> 2264 bytes
 doc/images/password-entry-row.png      | Bin 0 -> 2695 bytes
 doc/libadwaita.toml.in                 |   2 +
 doc/tools/data/password-entry-row.ui   |  23 +++++
 doc/visual-index.md                    |   7 ++
 po/POTFILES.in                         |   1 +
 src/adw-password-entry-row.c           | 182 +++++++++++++++++++++++++++++++++
 src/adw-password-entry-row.h           |  29 ++++++
 src/adwaita.h                          |   1 +
 src/meson.build                        |   2 +
 11 files changed, 258 insertions(+)
---
diff --git a/doc/boxed-lists.md b/doc/boxed-lists.md
index d2c45049..6c9b912e 100644
--- a/doc/boxed-lists.md
+++ b/doc/boxed-lists.md
@@ -88,6 +88,17 @@ widgets, and an apply button.
   <img src="entry-row.png" alt="entry-row">
 </picture>
 
+## Password Entry Rows
+
+[class@PasswordEntryRow] is a variant of [class@EntryRow] tailored for entering
+secrets. It conceals the text and provides a button to show it, along with a
+<kbd>Caps Lock</kbd> indicator.
+
+<picture>
+  <source srcset="password-entry-row-dark.png" media="(prefers-color-scheme: dark)">
+  <img src="password-entry-row.png" alt="password-entry-row">
+</picture>
+
 ## Preferences Group
 
 [class@PreferencesGroup] provides a boxed list along with a title and a
diff --git a/doc/images/password-entry-row-dark.png b/doc/images/password-entry-row-dark.png
new file mode 100644
index 00000000..a7f4b00e
Binary files /dev/null and b/doc/images/password-entry-row-dark.png differ
diff --git a/doc/images/password-entry-row.png b/doc/images/password-entry-row.png
new file mode 100644
index 00000000..6e73115d
Binary files /dev/null and b/doc/images/password-entry-row.png differ
diff --git a/doc/libadwaita.toml.in b/doc/libadwaita.toml.in
index a80efa7c..76ae6c2d 100644
--- a/doc/libadwaita.toml.in
+++ b/doc/libadwaita.toml.in
@@ -154,6 +154,8 @@ content_images = [
   "images/osd-progress-bar-dark.png",
   "images/osd-toolbar.png",
   "images/osd-toolbar-dark.png",
+  "images/password-entry-row.png",
+  "images/password-entry-row-dark.png",
   "images/popover-menu-list.png",
   "images/popover-menu-list-dark.png",
   "images/preferences-group.png",
diff --git a/doc/tools/data/password-entry-row.ui b/doc/tools/data/password-entry-row.ui
new file mode 100644
index 00000000..cbd82ac9
--- /dev/null
+++ b/doc/tools/data/password-entry-row.ui
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk" version="4.0"/>
+  <requires lib="libadwaita" version="1.0"/>
+  <object class="GtkListBox" id="widget">
+    <property name="margin-top">6</property>
+    <property name="margin-bottom">6</property>
+    <property name="margin-start">6</property>
+    <property name="margin-end">6</property>
+    <property name="selection-mode">none</property>
+    <property name="width-request">400</property>
+    <style>
+      <class name="boxed-list"/>
+    </style>
+    <child>
+      <object class="AdwPasswordEntryRow">
+        <property name="title">Title</property>
+        <property name="text">A long password</property>
+        <property name="can-focus">False</property>
+      </object>
+    </child>
+  </object>
+</interface>
diff --git a/doc/visual-index.md b/doc/visual-index.md
index 30d6b3ed..b2597403 100644
--- a/doc/visual-index.md
+++ b/doc/visual-index.md
@@ -56,6 +56,13 @@ Slug: visual-index
   <img src="entry-row.png" alt="entry-row">
 </picture>](class.EntryRow.html)
 
+### Password Entry Row
+
+[<picture>
+  <source srcset="password-entry-row-dark.png" media="(prefers-color-scheme: dark)">
+  <img src="password-entry-row.png" alt="password-entry-row">
+</picture>](class.PasswordEntryRow.html)
+
 ## Preferences
 
 ### Preferences Group
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 29355757..31db95b2 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -3,5 +3,6 @@
 src/adw-entry-row.ui
 src/adw-inspector-page.c
 src/adw-inspector-page.ui
+src/adw-password-entry-row.c
 src/adw-preferences-window.c
 src/adw-preferences-window.ui
diff --git a/src/adw-password-entry-row.c b/src/adw-password-entry-row.c
new file mode 100644
index 00000000..881dfe50
--- /dev/null
+++ b/src/adw-password-entry-row.c
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2021 Maximiliano Sandoval <msandova protonmail com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+
+#include "adw-password-entry-row.h"
+
+#include "adw-entry-row-private.h"
+#include "adw-macros-private.h"
+
+/**
+ * AdwPasswordEntryRow:
+ *
+ * A [class@Gtk.ListBoxRow] tailored for entering secrets inside a list.
+ *
+ * <picture>
+ *   <source srcset="password-entry-row-dark.png" media="(prefers-color-scheme: dark)">
+ *   <img src="password-entry-row.png" alt="password-entry-row">
+ * </picture>
+ *
+ * It does not show its contents in clear text, does not allow to copy it to the
+ * clipboard, and it shows a warning when Caps Lock is engaged. If the
+ * underlying platform allows it, `AdwPasswordEntryRow` will also place the text
+ * in a non-pageable memory area, to avoid it being written out to disk by the
+ * operating system.
+ *
+ * Optionally, it can offer a way to reveal the contents in clear text.
+ *
+ * `AdwPasswordEntryRow` provides only minimal API and should be used with the
+ * [iface@Gtk.Editable] API.
+ *
+ * # CSS Nodes
+ *
+ * `AdwPasswordEntryRow` has a single CSS node with name row that carries a
+ * `.password` style class. The text CSS node below it has a child with name
+ * `image` and style class `.caps-lock-indicator` for the Caps Lock icon, and
+ * possibly other children.
+ *
+ * Since: 1.2
+ */
+
+struct _AdwPasswordEntryRow
+{
+  AdwEntryRow parent_instance;
+
+  GtkWidget *show_text_toggle;
+
+  GdkDevice *keyboard;
+};
+
+G_DEFINE_FINAL_TYPE (AdwPasswordEntryRow, adw_password_entry_row, ADW_TYPE_ENTRY_ROW)
+
+static void
+update_caps_lock (AdwPasswordEntryRow *self)
+{
+  GtkEditable *delegate = gtk_editable_get_delegate (GTK_EDITABLE (self));
+
+  adw_entry_row_set_show_indicator (ADW_ENTRY_ROW (self),
+                                    !gtk_text_get_visibility (GTK_TEXT (delegate)) &&
+                                    gdk_device_get_caps_lock_state (self->keyboard));
+}
+
+static void
+notify_visibility_cb (AdwPasswordEntryRow *self)
+{
+  GtkEditable *delegate = gtk_editable_get_delegate (GTK_EDITABLE (self));
+
+  if (gtk_text_get_visibility (GTK_TEXT (delegate))) {
+    gtk_button_set_icon_name (GTK_BUTTON (self->show_text_toggle),
+                              "eye-open-negative-filled-symbolic");
+    gtk_widget_set_tooltip_text (self->show_text_toggle, _("Hide Text"));
+  } else {
+    gtk_button_set_icon_name (GTK_BUTTON (self->show_text_toggle),
+                              "eye-not-looking-symbolic");
+    gtk_widget_set_tooltip_text (self->show_text_toggle, _("Show Text"));
+  }
+
+  if (self->keyboard)
+    update_caps_lock (self);
+}
+
+static void
+notify_has_focus_cb (AdwPasswordEntryRow *self)
+{
+  if (self->keyboard)
+    update_caps_lock (self);
+}
+
+static void
+adw_password_entry_row_realize (GtkWidget *widget)
+{
+  AdwPasswordEntryRow *self = ADW_PASSWORD_ENTRY_ROW (widget);
+  GdkSeat *seat;
+
+  GTK_WIDGET_CLASS (adw_password_entry_row_parent_class)->realize (widget);
+
+  seat = gdk_display_get_default_seat (gtk_widget_get_display (widget));
+  if (seat)
+    self->keyboard = gdk_seat_get_keyboard (seat);
+
+  if (self->keyboard) {
+    g_signal_connect_swapped (self->keyboard, "notify::caps-lock-state",
+                              G_CALLBACK (update_caps_lock), self);
+    update_caps_lock (self);
+  }
+}
+
+static void
+adw_password_entry_row_dispose (GObject *object)
+{
+  AdwPasswordEntryRow *self = ADW_PASSWORD_ENTRY_ROW (object);
+
+  if (self->keyboard)
+    g_signal_handlers_disconnect_by_func (self->keyboard, update_caps_lock, self);
+
+  G_OBJECT_CLASS (adw_password_entry_row_parent_class)->dispose (object);
+}
+
+static void
+adw_password_entry_row_class_init (AdwPasswordEntryRowClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->dispose = adw_password_entry_row_dispose;
+
+  widget_class->realize = adw_password_entry_row_realize;
+}
+
+static void
+adw_password_entry_row_init (AdwPasswordEntryRow *self)
+{
+  GtkEditable *delegate;
+
+  self->show_text_toggle = gtk_toggle_button_new ();
+  gtk_widget_set_valign (self->show_text_toggle, GTK_ALIGN_CENTER);
+  gtk_widget_set_focus_on_click (self->show_text_toggle, FALSE);
+  gtk_widget_add_css_class (self->show_text_toggle, "flat");
+  adw_entry_row_add_suffix (ADW_ENTRY_ROW (self), self->show_text_toggle);
+
+  delegate = gtk_editable_get_delegate (GTK_EDITABLE (self));
+
+  g_assert (GTK_IS_TEXT (delegate));
+
+  gtk_text_set_visibility (GTK_TEXT (delegate), FALSE);
+  gtk_text_set_buffer (GTK_TEXT (delegate), gtk_password_entry_buffer_new ());
+
+  g_signal_connect_swapped (delegate, "notify::has-focus",
+                            G_CALLBACK (notify_has_focus_cb), self);
+  g_signal_connect_swapped (delegate, "notify::visibility",
+                            G_CALLBACK (notify_visibility_cb), self);
+  g_object_bind_property (self->show_text_toggle, "active",
+                          delegate, "visibility",
+                          G_BINDING_SYNC_CREATE);
+
+  adw_entry_row_set_indicator_icon_name (ADW_ENTRY_ROW (self), "caps-lock-symbolic");
+  adw_entry_row_set_indicator_tooltip (ADW_ENTRY_ROW (self), _("Caps Lock is on"));
+
+  gtk_widget_add_css_class (GTK_WIDGET (self), "password");
+
+  notify_visibility_cb (self);
+
+}
+
+/**
+ * adw_password_entry_row_new:
+ *
+ * Creates a new `AdwPasswordEntryRow`.
+ *
+ * Returns: the newly created `AdwPasswordEntryRow`
+ *
+ * Since: 1.2
+ */
+GtkWidget *
+adw_password_entry_row_new (void)
+{
+  return g_object_new (ADW_TYPE_PASSWORD_ENTRY_ROW, NULL);
+}
diff --git a/src/adw-password-entry-row.h b/src/adw-password-entry-row.h
new file mode 100644
index 00000000..4b9623c7
--- /dev/null
+++ b/src/adw-password-entry-row.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 Maximiliano Sandoval <msandova protonmail com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#pragma once
+
+#if !defined(_ADWAITA_INSIDE) && !defined(ADWAITA_COMPILATION)
+#error "Only <adwaita.h> can be included directly."
+#endif
+
+#include "adw-version.h"
+
+#include <gtk/gtk.h>
+
+#include "adw-entry-row.h"
+
+G_BEGIN_DECLS
+
+#define ADW_TYPE_PASSWORD_ENTRY_ROW (adw_password_entry_row_get_type())
+
+ADW_AVAILABLE_IN_1_2
+G_DECLARE_FINAL_TYPE (AdwPasswordEntryRow, adw_password_entry_row, ADW, PASSWORD_ENTRY_ROW, AdwEntryRow)
+
+ADW_AVAILABLE_IN_1_2
+GtkWidget *adw_password_entry_row_new (void) G_GNUC_WARN_UNUSED_RESULT;
+
+G_END_DECLS
diff --git a/src/adwaita.h b/src/adwaita.h
index 2cb03ee1..aa1ca1da 100644
--- a/src/adwaita.h
+++ b/src/adwaita.h
@@ -48,6 +48,7 @@ G_BEGIN_DECLS
 #include "adw-leaflet.h"
 #include "adw-main.h"
 #include "adw-navigation-direction.h"
+#include "adw-password-entry-row.h"
 #include "adw-preferences-group.h"
 #include "adw-preferences-page.h"
 #include "adw-preferences-row.h"
diff --git a/src/meson.build b/src/meson.build
index 886d3edd..0f6c5217 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -107,6 +107,7 @@ src_headers = [
   'adw-leaflet.h',
   'adw-main.h',
   'adw-navigation-direction.h',
+  'adw-password-entry-row.h',
   'adw-preferences-group.h',
   'adw-preferences-page.h',
   'adw-preferences-row.h',
@@ -168,6 +169,7 @@ src_sources = [
   'adw-leaflet.c',
   'adw-main.c',
   'adw-navigation-direction.c',
+  'adw-password-entry-row.c',
   'adw-preferences-group.c',
   'adw-preferences-page.c',
   'adw-preferences-row.c',


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