[libadwaita/entry-row: 2/4] Add AdwPasswordEntryRow




commit 0f47630215b66639e6b16807f8c9d692d3389590
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 -> 2204 bytes
 doc/images/password-entry-row.png      | Bin 0 -> 2595 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           | 206 +++++++++++++++++++++++++++++++++
 src/adw-password-entry-row.h           |  29 +++++
 src/adwaita.h                          |   1 +
 src/meson.build                        |   2 +
 tests/meson.build                      |   1 +
 tests/test-password-entry-row.c        |  30 +++++
 13 files changed, 313 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..baaaa85e
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..8af8b28c
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..6cb9ab38
--- /dev/null
+++ b/src/adw-password-entry-row.c
@@ -0,0 +1,206 @@
+/*
+ * 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),
+                              "view-conceal-symbolic");
+    gtk_widget_set_tooltip_text (self->show_text_toggle, _("Hide Text"));
+  } else {
+    gtk_button_set_icon_name (GTK_BUTTON (self->show_text_toggle),
+                              "view-reveal-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
+show_text_clicked_cb (AdwPasswordEntryRow *self)
+{
+  GtkEditable *delegate = gtk_editable_get_delegate (GTK_EDITABLE (self));
+  gboolean visible = gtk_text_get_visibility (GTK_TEXT (delegate));
+
+  gtk_text_set_visibility (GTK_TEXT (delegate), !visible);
+}
+
+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;
+  GMenu *menu;
+  GMenu *section;
+  GMenuItem *item;
+
+  self->show_text_toggle = gtk_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_signal_connect_swapped (self->show_text_toggle, "clicked",
+                            G_CALLBACK (show_text_clicked_cb), self);
+
+  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);
+
+  menu = g_menu_new ();
+  section = g_menu_new ();
+  item = g_menu_item_new (_("_Show Text"), "misc.toggle-visibility");
+  g_menu_item_set_attribute (item, "touch-icon", "s", "view-reveal-symbolic");
+  g_menu_append_item (section, item);
+
+  g_menu_append_section (menu, NULL, G_MENU_MODEL (section));
+
+  gtk_text_set_extra_menu (GTK_TEXT (delegate), G_MENU_MODEL (menu));
+
+  g_object_unref (item);
+  g_object_unref (section);
+  g_object_unref (menu);
+}
+
+/**
+ * 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',
diff --git a/tests/meson.build b/tests/meson.build
index e3bdf95b..03293446 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -40,6 +40,7 @@ test_names = [
   'test-flap',
   'test-header-bar',
   'test-leaflet',
+  'test-password-entry-row',
   'test-preferences-group',
   'test-preferences-page',
   'test-preferences-row',
diff --git a/tests/test-password-entry-row.c b/tests/test-password-entry-row.c
new file mode 100644
index 00000000..372babb0
--- /dev/null
+++ b/tests/test-password-entry-row.c
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 Purism SPC
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * Author: Alexander Mikhaylenko <alexander mikhaylenko puri sm>
+ */
+
+#include <adwaita.h>
+
+static void
+test_adw_password_entry_row_new (void)
+{
+  GtkWidget *row = g_object_ref_sink (adw_password_entry_row_new ());
+  g_assert_nonnull (row);
+
+  g_assert_finalize_object (row);
+}
+
+int
+main (int   argc,
+      char *argv[])
+{
+  gtk_test_init (&argc, &argv, NULL);
+  adw_init ();
+
+  g_test_add_func("/Adwaita/PasswordEntryRow/new", test_adw_password_entry_row_new);
+
+  return g_test_run();
+}


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