From 62d50feedde8df0e54db21b8419ea6cfbed7b7f3 Mon Sep 17 00:00:00 2001 From: Alexander Mikhaylenko Date: Fri, 22 Apr 2022 19:11:54 +0400 Subject: [PATCH 1/7] action-row: Refine spacing Make it 6px, matching toolbars. Leave 12px between prefix box, icon and title. --- src/stylesheet/widgets/_lists.scss | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/stylesheet/widgets/_lists.scss b/src/stylesheet/widgets/_lists.scss index 5b43c202..5aa04454 100644 --- a/src/stylesheet/widgets/_lists.scss +++ b/src/stylesheet/widgets/_lists.scss @@ -85,7 +85,7 @@ row { > box.header { margin-left: 12px; margin-right: 12px; - border-spacing: 12px; + border-spacing: 6px; min-height: 50px; > .icon:disabled { @@ -100,7 +100,13 @@ row { > .prefixes, > .suffixes { - border-spacing: 12px; + border-spacing: 6px; + } + + > .icon, + > .prefixes { + &:dir(ltr) { margin-right: 6px; } + &:dir(rtl) { margin-left: 6px; } } } } -- GitLab From cf267e47d58219375cf931dd64a54cc9015abc43 Mon Sep 17 00:00:00 2001 From: Alexander Mikhaylenko Date: Fri, 22 Apr 2022 19:16:21 +0400 Subject: [PATCH 2/7] doc: Regenerate combo row images Its spacing has changed. --- doc/images/combo-row-dark.png | Bin 2332 -> 2347 bytes doc/images/combo-row.png | Bin 2924 -> 2955 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/images/combo-row-dark.png b/doc/images/combo-row-dark.png index 2d3960542fe4161e4e6e3dfa977cc564e5358a82..056001934d18cf456d507e889d6bc594b093cad6 100644 GIT binary patch delta 2336 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 delta 2917 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 P00000NkvXXu0mjffL`3l -- GitLab From 6238f9cc65055982bbae617c05fb42fda81decd3 Mon Sep 17 00:00:00 2001 From: Alexander Mikhaylenko Date: Thu, 21 Apr 2022 23:32:22 +0400 Subject: [PATCH 3/7] gizmo: Add setters for setting allocate etc functions --- src/adw-gizmo-private.h | 13 +++++++++++ src/adw-gizmo.c | 50 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/src/adw-gizmo-private.h b/src/adw-gizmo-private.h index ce667c15..f9dd638a 100644 --- a/src/adw-gizmo-private.h +++ b/src/adw-gizmo-private.h @@ -58,4 +58,17 @@ GtkWidget *adw_gizmo_new_with_role (const char *css_name, AdwGizmoFocusFunc focus_func, AdwGizmoGrabFocusFunc grab_focus_func) G_GNUC_WARN_UNUSED_RESULT; +void adw_gizmo_set_measure_func (AdwGizmo *self, + AdwGizmoMeasureFunc measure_func); +void adw_gizmo_set_allocate_func (AdwGizmo *self, + AdwGizmoAllocateFunc allocate_func); +void adw_gizmo_set_snapshot_func (AdwGizmo *self, + AdwGizmoSnapshotFunc snapshot_func); +void adw_gizmo_set_contains_func (AdwGizmo *self, + AdwGizmoContainsFunc contains_func); +void adw_gizmo_set_focus_func (AdwGizmo *self, + AdwGizmoFocusFunc focus_func); +void adw_gizmo_set_grab_focus_func (AdwGizmo *self, + AdwGizmoGrabFocusFunc grab_focus_func); + G_END_DECLS diff --git a/src/adw-gizmo.c b/src/adw-gizmo.c index cdebb73e..df1695e0 100644 --- a/src/adw-gizmo.c +++ b/src/adw-gizmo.c @@ -189,3 +189,53 @@ adw_gizmo_new_with_role (const char *css_name, return GTK_WIDGET (gizmo); } + +void +adw_gizmo_set_measure_func (AdwGizmo *self, + AdwGizmoMeasureFunc measure_func) +{ + self->measure_func = measure_func; + + gtk_widget_queue_resize (GTK_WIDGET (self)); +} + +void +adw_gizmo_set_allocate_func (AdwGizmo *self, + AdwGizmoAllocateFunc allocate_func) +{ + self->allocate_func = allocate_func; + + gtk_widget_queue_allocate (GTK_WIDGET (self)); +} + +void +adw_gizmo_set_snapshot_func (AdwGizmo *self, + AdwGizmoSnapshotFunc snapshot_func) +{ + self->snapshot_func = snapshot_func; + + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +void +adw_gizmo_set_contains_func (AdwGizmo *self, + AdwGizmoContainsFunc contains_func) +{ + self->contains_func = contains_func; + + gtk_widget_queue_resize (GTK_WIDGET (self)); +} + +void +adw_gizmo_set_focus_func (AdwGizmo *self, + AdwGizmoFocusFunc focus_func) +{ + self->focus_func = focus_func; +} + +void +adw_gizmo_set_grab_focus_func (AdwGizmo *self, + AdwGizmoGrabFocusFunc grab_focus_func) +{ + self->grab_focus_func = grab_focus_func; +} -- GitLab From c4c4bdaac70796d431995c78d69d14ba9da9a0ac Mon Sep 17 00:00:00 2001 From: Maximiliano Sandoval R Date: Tue, 20 Jul 2021 15:33:52 +0200 Subject: [PATCH 4/7] Add AdwEntryRow --- doc/boxed-lists.md | 10 + doc/images/entry-row-dark.png | Bin 0 -> 2137 bytes doc/images/entry-row.png | Bin 0 -> 2535 bytes doc/libadwaita.toml.in | 2 + doc/tools/data/entry-row.ui | 23 + doc/visual-index.md | 7 + po/POTFILES.in | 1 + src/adw-entry-row-private.h | 24 + src/adw-entry-row.c | 735 ++++++++++++++++++ src/adw-entry-row.h | 53 ++ src/adw-entry-row.ui | 126 +++ src/adwaita.gresources.xml | 2 + src/adwaita.h | 1 + .../actions/adw-entry-apply-symbolic.svg | 45 ++ src/meson.build | 2 + src/stylesheet/widgets/_lists.scss | 27 + tests/meson.build | 1 + tests/test-entry-row.c | 81 ++ 18 files changed, 1140 insertions(+) create mode 100644 doc/images/entry-row-dark.png create mode 100644 doc/images/entry-row.png create mode 100644 doc/tools/data/entry-row.ui create mode 100644 src/adw-entry-row-private.h create mode 100644 src/adw-entry-row.c create mode 100644 src/adw-entry-row.h create mode 100644 src/adw-entry-row.ui create mode 100644 src/icons/scalable/actions/adw-entry-apply-symbolic.svg create mode 100644 tests/test-entry-row.c diff --git a/doc/boxed-lists.md b/doc/boxed-lists.md index 640c8309..d2c45049 100644 --- a/doc/boxed-lists.md +++ b/doc/boxed-lists.md @@ -78,6 +78,16 @@ other rows. combo-row +## Entry Rows + +[class@EntryRow] is a row with an embedded entry. It can have prefix and suffix +widgets, and an apply button. + + + + entry-row + + ## Preferences Group [class@PreferencesGroup] provides a boxed list along with a title and a diff --git a/doc/images/entry-row-dark.png b/doc/images/entry-row-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..ae7c99e491ff210e199614b4b00d45e52ae0783f GIT binary patch literal 2137 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 literal 0 HcmV?d00001 diff --git a/doc/libadwaita.toml.in b/doc/libadwaita.toml.in index 14b7db2f..a80efa7c 100644 --- a/doc/libadwaita.toml.in +++ b/doc/libadwaita.toml.in @@ -122,6 +122,8 @@ content_images = [ "images/devel-window-dark.png", "images/dim-label.png", "images/dim-label-dark.png", + "images/entry-row.png", + "images/entry-row-dark.png", "images/expander-row.png", "images/expander-row-dark.png", "images/flap-narrow.png", diff --git a/doc/tools/data/entry-row.ui b/doc/tools/data/entry-row.ui new file mode 100644 index 00000000..df3e9571 --- /dev/null +++ b/doc/tools/data/entry-row.ui @@ -0,0 +1,23 @@ + + + + + + 6 + 6 + 6 + 6 + none + 400 + + + + Title + Text + False + + + + diff --git a/doc/visual-index.md b/doc/visual-index.md index db200c2b..30d6b3ed 100644 --- a/doc/visual-index.md +++ b/doc/visual-index.md @@ -49,6 +49,13 @@ Slug: visual-index expander-row ](class.ExpanderRow.html) +### Entry Row + +[ + + entry-row +](class.EntryRow.html) + ## Preferences ### Preferences Group diff --git a/po/POTFILES.in b/po/POTFILES.in index 4910cfb8..29355757 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -1,5 +1,6 @@ # List of source files containing translatable strings. # Please keep this file sorted alphabetically. +src/adw-entry-row.ui src/adw-inspector-page.c src/adw-inspector-page.ui src/adw-preferences-window.c diff --git a/src/adw-entry-row-private.h b/src/adw-entry-row-private.h new file mode 100644 index 00000000..dd827a0e --- /dev/null +++ b/src/adw-entry-row-private.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2022 Purism SPC + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#if !defined(_ADWAITA_INSIDE) && !defined(ADWAITA_COMPILATION) +#error "Only can be included directly." +#endif + +#include "adw-entry-row.h" + +G_BEGIN_DECLS + +void adw_entry_row_set_indicator_icon_name (AdwEntryRow *self, + const char *icon_name); +void adw_entry_row_set_indicator_tooltip (AdwEntryRow *self, + const char *tooltip); +void adw_entry_row_set_show_indicator (AdwEntryRow *self, + gboolean show_indicator); + +G_END_DECLS diff --git a/src/adw-entry-row.c b/src/adw-entry-row.c new file mode 100644 index 00000000..502d6e98 --- /dev/null +++ b/src/adw-entry-row.c @@ -0,0 +1,735 @@ +/* + * Copyright (C) 2021 Maximiliano Sandoval + * Copyright (C) 2022 Purism SPC + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" +#include "adw-entry-row-private.h" + +#include "adw-animation-private.h" +#include "adw-animation-util.h" +#include "adw-gizmo-private.h" +#include "adw-macros-private.h" +#include "adw-timed-animation.h" +#include "adw-widget-utils-private.h" + +#define EMPTY_ANIMATION_DURATION 150 +#define TITLE_SPACING 3 + +/** + * AdwEntryRow: + * + * A [class@Gtk.ListBoxRow] with an embedded text entry. + * + * + * + * entry-row + * + * + * `AdwEntryRow` has a title that doubles as placeholder text. It shows an icon + * indicating that it's editable and can receive additional widgets before or + * after the editable part. + * + * If [property@EntryRow:show-apply-button] is set to `TRUE`, `AdwEntryRow` can + * show an apply button when editing its contents. This can be useful if + * changing its contents can result in an expensive operation, such as network + * activity. + * + * `AdwEntryRow` provides only minimal API and should be used with the + * [iface@Gtk.Editable] API. + * + * See also [class@PasswordEntryRow]. + * + * ## AdwEntryRow as GtkBuildable + * + * The `AdwEntryRow` implementation of the [iface@Gtk.Buildable] interface + * supports adding a child at its end by specifying “suffix” or omitting the + * “type” attribute of a element. + * + * It also supports adding a child as a prefix widget by specifying “prefix” as + * the “type” attribute of a element. + * + * ## CSS nodes + * + * `AdwEntryRow` has a single CSS node with name `row` and the `.entry` style + * class. + * + * Since: 1.2 + */ + +typedef struct +{ + GtkWidget *header; + GtkWidget *text; + GtkWidget *title; + GtkWidget *empty_title; + GtkWidget *editable_area; + GtkWidget *edit_icon; + GtkWidget *apply_button; + GtkWidget *indicator; + GtkBox *suffixes; + GtkBox *prefixes; + + gboolean empty; + double empty_progress; + AdwAnimation *empty_animation; + + gboolean editing; + gboolean show_apply_button; + gboolean text_changed; + gboolean show_indicator; +} AdwEntryRowPrivate; + +static void adw_entry_row_editable_init (GtkEditableInterface *iface); +static void adw_entry_row_buildable_init (GtkBuildableIface *iface); + +G_DEFINE_TYPE_WITH_CODE (AdwEntryRow, adw_entry_row, ADW_TYPE_PREFERENCES_ROW, + G_ADD_PRIVATE (AdwEntryRow) + G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, adw_entry_row_buildable_init) + G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, adw_entry_row_editable_init)) + +static GtkBuildableIface *parent_buildable_iface; + +enum { + PROP_0, + PROP_SHOW_APPLY_BUTTON, + PROP_LAST_PROP, +}; + +static GParamSpec *props[PROP_LAST_PROP]; + +enum { + SIGNAL_APPLY, + SIGNAL_LAST_SIGNAL, +}; + +static guint signals[SIGNAL_LAST_SIGNAL]; + +static void +empty_animation_value_cb (double value, + AdwEntryRow *self) +{ + AdwEntryRowPrivate *priv = adw_entry_row_get_instance_private (self); + + priv->empty_progress = value; + + gtk_widget_queue_allocate (priv->editable_area); + + gtk_widget_set_opacity (priv->text, value); + gtk_widget_set_opacity (priv->title, value); + gtk_widget_set_opacity (priv->empty_title, 1 - value); +} + +static gboolean +is_text_focused (AdwEntryRow *self) +{ + AdwEntryRowPrivate *priv = adw_entry_row_get_instance_private (self); + GtkStateFlags flags = gtk_widget_get_state_flags (priv->text); + + return !!(flags & GTK_STATE_FLAG_FOCUS_WITHIN); +} + +static void +update_empty (AdwEntryRow *self) +{ + AdwEntryRowPrivate *priv = adw_entry_row_get_instance_private (self); + GtkEntryBuffer *buffer = gtk_text_get_buffer (GTK_TEXT (priv->text)); + gboolean focused = is_text_focused (self); + gboolean editable = gtk_editable_get_editable (GTK_EDITABLE (priv->text)); + gboolean empty = gtk_entry_buffer_get_length (buffer) == 0; + + gtk_widget_set_visible (priv->edit_icon, !priv->text_changed && (!priv->editing || !editable)); + gtk_widget_set_sensitive (priv->edit_icon, editable); + gtk_widget_set_visible (priv->indicator, priv->editing && priv->show_indicator); + gtk_widget_set_visible (priv->apply_button, priv->text_changed); + + priv->empty = empty && !(focused && editable) && !priv->text_changed; + + gtk_widget_queue_allocate (priv->editable_area); + + adw_timed_animation_set_value_from (ADW_TIMED_ANIMATION (priv->empty_animation), + priv->empty_progress); + adw_timed_animation_set_value_to (ADW_TIMED_ANIMATION (priv->empty_animation), + priv->empty ? 0 : 1); + adw_animation_play (priv->empty_animation); +} + +static void +text_changed_cb (AdwEntryRow *self) +{ + AdwEntryRowPrivate *priv = adw_entry_row_get_instance_private (self); + + if (priv->show_apply_button && priv->editing) + priv->text_changed = TRUE; + + update_empty (self); +} + +static void +text_state_flags_changed_cb (AdwEntryRow *self) +{ + AdwEntryRowPrivate *priv = adw_entry_row_get_instance_private (self); + + priv->editing = is_text_focused (self); + + if (priv->editing) + gtk_widget_add_css_class (GTK_WIDGET (self), "focused"); + else + gtk_widget_remove_css_class (GTK_WIDGET (self), "focused"); + + update_empty (self); +} + +static gboolean +text_keynav_failed_cb (AdwEntryRow *self, + GtkDirectionType direction) +{ + if (direction == GTK_DIR_LEFT || direction == GTK_DIR_RIGHT) + return gtk_widget_child_focus (GTK_WIDGET (self), direction); + + return GDK_EVENT_PROPAGATE; +} + +static void +pressed_cb (GtkGesture *gesture, + int n_press, + double x, + double y, + AdwEntryRow *self) +{ + AdwEntryRowPrivate *priv = adw_entry_row_get_instance_private (self); + GtkWidget *picked; + + picked = gtk_widget_pick (GTK_WIDGET (self), x, y, GTK_PICK_DEFAULT); + + if (picked != GTK_WIDGET (self) && + picked != priv->header && + picked != priv->indicator && + picked != GTK_WIDGET (priv->prefixes) && + picked != GTK_WIDGET (priv->suffixes)) { + gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED); + + return; + } + + gtk_widget_grab_focus (GTK_WIDGET (priv->text)); + + gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_CLAIMED); +} + +static void +apply_button_clicked_cb (AdwEntryRow *self) +{ + AdwEntryRowPrivate *priv = adw_entry_row_get_instance_private (self); + + if (gtk_widget_has_focus (priv->apply_button)) + gtk_widget_grab_focus (GTK_WIDGET (self)); + + priv->text_changed = FALSE; + update_empty (self); + + g_signal_emit (self, signals[SIGNAL_APPLY], 0); +} + +static void +text_activated_cb (AdwEntryRow *self) +{ + AdwEntryRowPrivate *priv = adw_entry_row_get_instance_private (self); + + if (gtk_widget_get_visible (priv->apply_button)) + apply_button_clicked_cb (self); +} + +static void +measure_editable_area (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + AdwEntryRow *self = g_object_get_data (G_OBJECT (widget), "row"); + AdwEntryRowPrivate *priv = adw_entry_row_get_instance_private (self); + int text_min = 0, text_nat = 0; + int title_min = 0, title_nat = 0; + int empty_min = 0, empty_nat = 0; + + gtk_widget_measure (priv->text, orientation, for_size, + &text_min, &text_nat, NULL, NULL); + gtk_widget_measure (priv->title, orientation, for_size, + &title_min, &title_nat, NULL, NULL); + gtk_widget_measure (priv->empty_title, orientation, for_size, + &empty_min, &empty_nat, NULL, NULL); + + if (minimum) + *minimum = MAX (text_min + TITLE_SPACING + title_min, empty_min); + + if (natural) + *natural = MAX (text_nat + TITLE_SPACING + title_nat, empty_nat); + + if (minimum_baseline) + *minimum_baseline = -1; + + if (natural_baseline) + *natural_baseline = -1; +} + +static void +allocate_editable_area (GtkWidget *widget, + int width, + int height, + int baseline) +{ + AdwEntryRow *self = g_object_get_data (G_OBJECT (widget), "row"); + AdwEntryRowPrivate *priv = adw_entry_row_get_instance_private (self); + gboolean is_rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL; + GskTransform *transform; + int empty_height = 0, title_height = 0, text_height = 0, text_baseline = -1; + float empty_scale, title_scale, title_offset; + + gtk_widget_measure (priv->title, GTK_ORIENTATION_VERTICAL, width, + NULL, &title_height, NULL, NULL); + gtk_widget_measure (priv->empty_title, GTK_ORIENTATION_VERTICAL, width, + NULL, &empty_height, NULL, NULL); + gtk_widget_measure (priv->text, GTK_ORIENTATION_VERTICAL, width, + NULL, &text_height, NULL, &text_baseline); + + empty_scale = (float) adw_lerp (1.0, (double) title_height / empty_height, priv->empty_progress); + title_scale = (float) adw_lerp ((double) empty_height / title_height, 1.0, priv->empty_progress); + title_offset = (float) adw_lerp ((double) (height - empty_height) / 2.0, + (double) (height - title_height - text_height - TITLE_SPACING) / 2.0, + priv->empty_progress); + + transform = gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (0, title_offset)); + if (is_rtl) + transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (width, 0)); + transform = gsk_transform_scale (transform, empty_scale, empty_scale); + if (is_rtl) + transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (-width, 0)); + gtk_widget_allocate (priv->empty_title, width, empty_height, -1, transform); + + transform = gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (0, title_offset)); + if (is_rtl) + transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (width, 0)); + transform = gsk_transform_scale (transform, title_scale, title_scale); + if (is_rtl) + transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (-width, 0)); + gtk_widget_allocate (priv->title, width, title_height, -1, transform); + + text_baseline += (int) ((double) (height + title_height - text_height + TITLE_SPACING) / 2.0); + gtk_widget_allocate (priv->text, width, height, text_baseline, NULL); +} + +static gboolean +adw_entry_row_grab_focus (GtkWidget *widget) +{ + AdwEntryRow *self = ADW_ENTRY_ROW (widget); + AdwEntryRowPrivate *priv = adw_entry_row_get_instance_private (self); + + return gtk_widget_grab_focus (priv->text); +} + +static void +adw_entry_row_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + AdwEntryRow *self = ADW_ENTRY_ROW (object); + + if (gtk_editable_delegate_get_property (object, prop_id, value, pspec)) + return; + + switch (prop_id) { + case PROP_SHOW_APPLY_BUTTON: + g_value_set_boolean (value, adw_entry_row_get_show_apply_button (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +adw_entry_row_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + AdwEntryRow *self = ADW_ENTRY_ROW (object); + + if (gtk_editable_delegate_set_property (object, prop_id, value, pspec)) + { + switch (prop_id) { + case PROP_LAST_PROP + GTK_EDITABLE_PROP_EDITABLE: + update_empty (self); + break; + default:; + } + return; + } + + switch (prop_id) { + case PROP_SHOW_APPLY_BUTTON: + adw_entry_row_set_show_apply_button (self, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +adw_entry_row_dispose (GObject *object) +{ + AdwEntryRow *self = ADW_ENTRY_ROW (object); + AdwEntryRowPrivate *priv = adw_entry_row_get_instance_private (self); + + g_clear_object (&priv->empty_animation); + + if (priv->text) + gtk_editable_finish_delegate (GTK_EDITABLE (self)); + + G_OBJECT_CLASS (adw_entry_row_parent_class)->dispose (object); +} + +static void +adw_entry_row_class_init (AdwEntryRowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = adw_entry_row_get_property; + object_class->set_property = adw_entry_row_set_property; + object_class->dispose = adw_entry_row_dispose; + + widget_class->focus = adw_widget_focus_child; + widget_class->grab_focus = adw_entry_row_grab_focus; + + /** + * AdwEntryRow:show-apply-button: (attributes org.gtk.Property.get=adw_entry_row_get_show_apply_button org.gtk.Property.set=adw_entry_row_set_show_apply_button) + * + * Whether to show the apply button. + * + * When set to `TRUE`, typing text in the entry will reveal an apply button. + * Clicking it or pressing the Enter key will hide the button and + * emit the [signal@EntryRow::apply] signal. + * + * This is useful if changing the entry contents can trigger an expensive + * operation, e.g. network activity, to avoid triggering it after typing every + * character. + * + * Since: 1.2 + */ + props[PROP_SHOW_APPLY_BUTTON] = + g_param_spec_boolean ("show-apply-button", + "Show Apply Button", + "Whether to show the apply button", + FALSE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); + + gtk_editable_install_properties (object_class, PROP_LAST_PROP); + + /** + * AdwEntryRow::apply: + * + * Emitted when the apply button is pressed. + * + * See [property@EntryRow:show-apply-button]. + * + * Since: 1.2 + */ + signals[SIGNAL_APPLY] = + g_signal_new ("apply", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, + 0); + + gtk_widget_class_set_template_from_resource (widget_class, + "/org/gnome/Adwaita/ui/adw-entry-row.ui"); + gtk_widget_class_bind_template_child_private (widget_class, AdwEntryRow, header); + gtk_widget_class_bind_template_child_private (widget_class, AdwEntryRow, prefixes); + gtk_widget_class_bind_template_child_private (widget_class, AdwEntryRow, suffixes); + gtk_widget_class_bind_template_child_private (widget_class, AdwEntryRow, editable_area); + gtk_widget_class_bind_template_child_private (widget_class, AdwEntryRow, text); + gtk_widget_class_bind_template_child_private (widget_class, AdwEntryRow, empty_title); + gtk_widget_class_bind_template_child_private (widget_class, AdwEntryRow, title); + gtk_widget_class_bind_template_child_private (widget_class, AdwEntryRow, edit_icon); + gtk_widget_class_bind_template_child_private (widget_class, AdwEntryRow, apply_button); + gtk_widget_class_bind_template_child_private (widget_class, AdwEntryRow, indicator); + + gtk_widget_class_bind_template_callback (widget_class, pressed_cb); + gtk_widget_class_bind_template_callback (widget_class, text_state_flags_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, text_keynav_failed_cb); + gtk_widget_class_bind_template_callback (widget_class, text_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, update_empty); + gtk_widget_class_bind_template_callback (widget_class, text_activated_cb); + gtk_widget_class_bind_template_callback (widget_class, apply_button_clicked_cb); + + g_type_ensure (ADW_TYPE_GIZMO); +} + +static void +adw_entry_row_init (AdwEntryRow *self) +{ + AdwEntryRowPrivate *priv = adw_entry_row_get_instance_private (self); + AdwAnimationTarget *target; + + gtk_widget_init_template (GTK_WIDGET (self)); + gtk_editable_init_delegate (GTK_EDITABLE (self)); + + adw_gizmo_set_measure_func (ADW_GIZMO (priv->editable_area), (AdwGizmoMeasureFunc) measure_editable_area); + adw_gizmo_set_allocate_func (ADW_GIZMO (priv->editable_area), (AdwGizmoAllocateFunc) allocate_editable_area); + adw_gizmo_set_focus_func (ADW_GIZMO (priv->editable_area), (AdwGizmoFocusFunc) adw_widget_focus_child); + + g_object_set_data (G_OBJECT (priv->editable_area), "row", self); + + priv->empty_progress = 0.0; + + target = adw_callback_animation_target_new ((AdwAnimationTargetFunc) + empty_animation_value_cb, + self, NULL); + + priv->empty_animation = + adw_timed_animation_new (GTK_WIDGET (self), 0, 0, + EMPTY_ANIMATION_DURATION, target); + + update_empty (self); +} + +static void +adw_entry_row_buildable_add_child (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const char *type) +{ + AdwEntryRow *self = ADW_ENTRY_ROW (buildable); + AdwEntryRowPrivate *priv = adw_entry_row_get_instance_private (self); + + if (!priv->header) + parent_buildable_iface->add_child (buildable, builder, child, type); + else if (g_strcmp0 (type, "prefix") == 0) + adw_entry_row_add_prefix (self, GTK_WIDGET (child)); + else if (g_strcmp0 (type, "suffix") == 0) + adw_entry_row_add_suffix (self, GTK_WIDGET (child)); + else if (!type && GTK_IS_WIDGET (child)) + adw_entry_row_add_suffix (self, GTK_WIDGET (child)); + else + parent_buildable_iface->add_child (buildable, builder, child, type); +} + +static void +adw_entry_row_buildable_init (GtkBuildableIface *iface) +{ + parent_buildable_iface = g_type_interface_peek_parent (iface); + iface->add_child = adw_entry_row_buildable_add_child; +} + +static GtkEditable * +adw_entry_row_get_delegate (GtkEditable *editable) +{ + AdwEntryRow *self = ADW_ENTRY_ROW (editable); + AdwEntryRowPrivate *priv = adw_entry_row_get_instance_private (self); + + return GTK_EDITABLE (priv->text); +} + +void +adw_entry_row_editable_init (GtkEditableInterface *iface) +{ + iface->get_delegate = adw_entry_row_get_delegate; +} + +/** + * adw_entry_row_new: + * + * Creates a new `AdwEntryRow`. + * + * Returns: the newly created `AdwEntryRow` + * + * Since: 1.2 + */ +GtkWidget * +adw_entry_row_new (void) +{ + return g_object_new (ADW_TYPE_ENTRY_ROW, NULL); +} + +/** + * adw_entry_row_add_prefix: + * @self: an entry row + * @widget: a widget + * + * Adds a prefix widget to @self. + * + * Since: 1.2 + */ +void +adw_entry_row_add_prefix (AdwEntryRow *self, + GtkWidget *widget) +{ + AdwEntryRowPrivate *priv; + + g_return_if_fail (ADW_IS_ENTRY_ROW (self)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + + priv = adw_entry_row_get_instance_private (self); + + gtk_box_prepend (priv->prefixes, widget); + gtk_widget_show (GTK_WIDGET (priv->prefixes)); +} + +/** + * adw_entry_row_add_suffix: + * @self: an entry row + * @widget: a widget + * + * Adds a suffix widget to @self. + * + * Since: 1.2 + */ +void +adw_entry_row_add_suffix (AdwEntryRow *self, + GtkWidget *widget) +{ + AdwEntryRowPrivate *priv; + + g_return_if_fail (ADW_IS_ENTRY_ROW (self)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + + priv = adw_entry_row_get_instance_private (self); + + gtk_box_append (priv->suffixes, widget); + gtk_widget_show (GTK_WIDGET (priv->suffixes)); +} + +/** + * adw_entry_row_remove: + * @self: an entry row + * @widget: the child to be removed + * + * Removes a child from @self. + * + * Since: 1.2 + */ +void +adw_entry_row_remove (AdwEntryRow *self, + GtkWidget *child) +{ + AdwEntryRowPrivate *priv; + GtkWidget *parent; + + g_return_if_fail (ADW_IS_ENTRY_ROW (self)); + g_return_if_fail (GTK_IS_WIDGET (child)); + + priv = adw_entry_row_get_instance_private (self); + + parent = gtk_widget_get_parent (child); + + if (parent == GTK_WIDGET (priv->prefixes)) + gtk_box_remove (priv->prefixes, child); + else if (parent == GTK_WIDGET (priv->suffixes)) + gtk_box_remove (priv->suffixes, child); + else + ADW_CRITICAL_CANNOT_REMOVE_CHILD (self, child); +} + +/** + * adw_entry_row_get_show_apply_button: (attributes org.gtk.Method.get_property=show-apply-button) + * @self: an entry row + * + * Gets whether @self can show the apply button. + * + * Returns: whether to show the apply button + * + * Since: 1.2 + */ +gboolean +adw_entry_row_get_show_apply_button (AdwEntryRow *self) +{ + AdwEntryRowPrivate *priv; + + g_return_val_if_fail (ADW_IS_ENTRY_ROW (self), FALSE); + + priv = adw_entry_row_get_instance_private (self); + + return priv->show_apply_button; +} + +/** + * adw_entry_row_set_show_apply_button: (attributes org.gtk.Method.set_property=show-apply-button) + * @self: an entry row + * @show_apply_button: whether to show the apply button + * + * Sets whether @self can show the apply button. + * + * Since: 1.2 + */ +void +adw_entry_row_set_show_apply_button (AdwEntryRow *self, + gboolean show_apply_button) +{ + AdwEntryRowPrivate *priv; + + g_return_if_fail (ADW_IS_ENTRY_ROW (self)); + + priv = adw_entry_row_get_instance_private (self); + + show_apply_button = !!show_apply_button; + + if (priv->show_apply_button == show_apply_button) + return; + + priv->show_apply_button = show_apply_button; + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SHOW_APPLY_BUTTON]); +} + +void +adw_entry_row_set_indicator_icon_name (AdwEntryRow *self, + const char *icon_name) +{ + AdwEntryRowPrivate *priv; + + g_return_if_fail (ADW_IS_ENTRY_ROW (self)); + + priv = adw_entry_row_get_instance_private (self); + + gtk_image_set_from_icon_name (GTK_IMAGE (priv->indicator), icon_name); +} + +void +adw_entry_row_set_indicator_tooltip (AdwEntryRow *self, + const char *tooltip) +{ + AdwEntryRowPrivate *priv; + + g_return_if_fail (ADW_IS_ENTRY_ROW (self)); + + priv = adw_entry_row_get_instance_private (self); + + gtk_widget_set_tooltip_text (priv->indicator, tooltip); +} + +void +adw_entry_row_set_show_indicator (AdwEntryRow *self, + gboolean show_indicator) +{ + AdwEntryRowPrivate *priv; + + g_return_if_fail (ADW_IS_ENTRY_ROW (self)); + + priv = adw_entry_row_get_instance_private (self); + + show_indicator = !!show_indicator; + + priv->show_indicator = show_indicator; + + update_empty (self); +} diff --git a/src/adw-entry-row.h b/src/adw-entry-row.h new file mode 100644 index 00000000..b4e68ddd --- /dev/null +++ b/src/adw-entry-row.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2021 Maximiliano Sandoval + * Copyright (C) 2022 Purism SPC + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#if !defined(_ADWAITA_INSIDE) && !defined(ADWAITA_COMPILATION) +#error "Only can be included directly." +#endif + +#include "adw-version.h" + +#include "adw-preferences-row.h" + +G_BEGIN_DECLS + +#define ADW_TYPE_ENTRY_ROW (adw_entry_row_get_type()) + +ADW_AVAILABLE_IN_1_2 +G_DECLARE_DERIVABLE_TYPE (AdwEntryRow, adw_entry_row, ADW, ENTRY_ROW, AdwPreferencesRow) + +/** + * AdwEntryRowClass + * @parent_class: The parent class + */ +struct _AdwEntryRowClass +{ + AdwPreferencesRowClass parent_class; +}; + +ADW_AVAILABLE_IN_1_2 +GtkWidget *adw_entry_row_new (void) G_GNUC_WARN_UNUSED_RESULT; + +ADW_AVAILABLE_IN_1_2 +void adw_entry_row_add_prefix (AdwEntryRow *self, + GtkWidget *widget); +ADW_AVAILABLE_IN_1_2 +void adw_entry_row_add_suffix (AdwEntryRow *self, + GtkWidget *widget); +ADW_AVAILABLE_IN_1_2 +void adw_entry_row_remove (AdwEntryRow *self, + GtkWidget *widget); + +ADW_AVAILABLE_IN_1_2 +gboolean adw_entry_row_get_show_apply_button (AdwEntryRow *self); +ADW_AVAILABLE_IN_1_2 +void adw_entry_row_set_show_apply_button (AdwEntryRow *self, + gboolean show_apply_button); + +G_END_DECLS diff --git a/src/adw-entry-row.ui b/src/adw-entry-row.ui new file mode 100644 index 00000000..979200be --- /dev/null +++ b/src/adw-entry-row.ui @@ -0,0 +1,126 @@ + + + + + diff --git a/src/adwaita.gresources.xml b/src/adwaita.gresources.xml index 21524a9e..7f4a4236 100644 --- a/src/adwaita.gresources.xml +++ b/src/adwaita.gresources.xml @@ -3,6 +3,7 @@ glsl/fade.glsl glsl/mask.glsl + icons/scalable/actions/adw-entry-apply-symbolic.svg icons/scalable/actions/adw-expander-arrow-symbolic.svg icons/scalable/status/avatar-default-symbolic.svg icons/scalable/status/adw-tab-icon-missing-symbolic.svg @@ -10,6 +11,7 @@ adw-action-row.ui adw-combo-row.ui + adw-entry-row.ui adw-expander-row.ui adw-inspector-page.ui adw-preferences-group.ui diff --git a/src/adwaita.h b/src/adwaita.h index 3371b1c9..2cb03ee1 100644 --- a/src/adwaita.h +++ b/src/adwaita.h @@ -39,6 +39,7 @@ G_BEGIN_DECLS #include "adw-combo-row.h" #include "adw-deprecation-macros.h" #include "adw-easing.h" +#include "adw-entry-row.h" #include "adw-enum-list-model.h" #include "adw-expander-row.h" #include "adw-flap.h" diff --git a/src/icons/scalable/actions/adw-entry-apply-symbolic.svg b/src/icons/scalable/actions/adw-entry-apply-symbolic.svg new file mode 100644 index 00000000..df63d41e --- /dev/null +++ b/src/icons/scalable/actions/adw-entry-apply-symbolic.svg @@ -0,0 +1,45 @@ + + + + + + + + diff --git a/src/meson.build b/src/meson.build index 19d9231a..886d3edd 100644 --- a/src/meson.build +++ b/src/meson.build @@ -98,6 +98,7 @@ src_headers = [ 'adw-combo-row.h', 'adw-deprecation-macros.h', 'adw-easing.h', + 'adw-entry-row.h', 'adw-enum-list-model.h', 'adw-expander-row.h', 'adw-flap.h', @@ -158,6 +159,7 @@ src_sources = [ 'adw-clamp-scrollable.c', 'adw-combo-row.c', 'adw-easing.c', + 'adw-entry-row.c', 'adw-enum-list-model.c', 'adw-expander-row.c', 'adw-flap.c', diff --git a/src/stylesheet/widgets/_lists.scss b/src/stylesheet/widgets/_lists.scss index 5aa04454..0bf07135 100644 --- a/src/stylesheet/widgets/_lists.scss +++ b/src/stylesheet/widgets/_lists.scss @@ -111,6 +111,33 @@ row { } } +/*************** + * AdwEntryRow * + ***************/ + +row.entry { + @include focus-ring($focus-state: '.focused', $offset: -1px); + + &:not(:selected).activatable.focused:hover, + &:not(:selected).activatable.focused:active { + background-color: transparent; + } + + .edit-icon, .indicator { + min-width: 24px; + min-height: 24px; + padding: 5px; + } + + .edit-icon:disabled { + opacity: $strong_disabled_opacity; + } + + .indicator { + opacity: $dimmer_opacity; + } +} + /*************** * AdwComboRow * ***************/ diff --git a/tests/meson.build b/tests/meson.build index 6c2a2066..e3bdf95b 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -35,6 +35,7 @@ test_names = [ 'test-carousel-indicator-lines', 'test-combo-row', 'test-easing', + 'test-entry-row', 'test-expander-row', 'test-flap', 'test-header-bar', diff --git a/tests/test-entry-row.c b/tests/test-entry-row.c new file mode 100644 index 00000000..24e1c3c1 --- /dev/null +++ b/tests/test-entry-row.c @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2022 Purism SPC + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * Author: Alexander Mikhaylenko + */ + +#include + +int notified; + +static void +notify_cb (GtkWidget *widget, gpointer data) +{ + notified++; +} + +static void +test_adw_entry_row_add_remove (void) +{ + AdwEntryRow *row = g_object_ref_sink (ADW_ENTRY_ROW (adw_entry_row_new ())); + GtkWidget *prefix, *suffix; + + g_assert_nonnull (row); + + prefix = gtk_check_button_new (); + g_assert_nonnull (prefix); + + suffix = gtk_check_button_new (); + g_assert_nonnull (suffix); + + adw_entry_row_add_prefix (row, prefix); + adw_entry_row_add_suffix (row, suffix); + + adw_entry_row_remove (row, prefix); + adw_entry_row_remove (row, suffix); + + g_assert_finalize_object (row); +} + +static void +test_adw_entry_row_show_apply_button (void) +{ + AdwEntryRow *row = g_object_ref_sink (ADW_ENTRY_ROW (adw_entry_row_new ())); + gboolean show_apply_button; + + g_assert_nonnull (row); + + notified = 0; + g_signal_connect (row, "notify::show-apply-button", G_CALLBACK (notify_cb), NULL); + + g_object_get (row, "show-apply-button", &show_apply_button, NULL); + g_assert_false (show_apply_button); + + adw_entry_row_set_show_apply_button (row, FALSE); + g_assert_cmpint (notified, ==, 0); + + adw_entry_row_set_show_apply_button (row, TRUE); + g_assert_true (adw_entry_row_get_show_apply_button (row)); + g_assert_cmpint (notified, ==, 1); + + g_object_set (row, "show-apply-button", FALSE, NULL); + g_assert_false (adw_entry_row_get_show_apply_button (row)); + g_assert_cmpint (notified, ==, 2); + + g_assert_finalize_object (row); +} + +int +main (int argc, + char *argv[]) +{ + gtk_test_init (&argc, &argv, NULL); + adw_init (); + + g_test_add_func("/Adwaita/EntryRow/add_remove", test_adw_entry_row_add_remove); + g_test_add_func("/Adwaita/EntryRow/show_apply_button", test_adw_entry_row_show_apply_button); + + return g_test_run(); +} -- GitLab From 85a4923c31041a6876cb49ad15ac85682cb314dd Mon Sep 17 00:00:00 2001 From: Maximiliano Sandoval R Date: Sun, 25 Jul 2021 17:07:51 +0200 Subject: [PATCH 5/7] Add AdwPasswordEntryRow --- doc/boxed-lists.md | 11 ++ doc/images/password-entry-row-dark.png | Bin 0 -> 2203 bytes doc/images/password-entry-row.png | Bin 0 -> 2596 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 | 202 +++++++++++++++++++++++++ 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, 309 insertions(+) create mode 100644 doc/images/password-entry-row-dark.png create mode 100644 doc/images/password-entry-row.png create mode 100644 doc/tools/data/password-entry-row.ui create mode 100644 src/adw-password-entry-row.c create mode 100644 src/adw-password-entry-row.h create mode 100644 tests/test-password-entry-row.c 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. entry-row +## 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 +Caps Lock indicator. + + + + password-entry-row + + ## 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 0000000000000000000000000000000000000000..607ea433642203e7614ff4c8dc7c8edabce0ed02 GIT binary patch literal 2203 zcmV;M2xRw(P)?bFnz9R%5Hu+(YSVD3IP^lodf=ZZiGgw{3eZfb(N>8fH~|$)As3WF zl?%}BG!!YEKy@`XPHiC^S`8@&mNpfc>UI(x9fu{`(yq224!`G#e|h}av7Pv_ANTW; zEXR+(c78i7pZ@+nAR;0nA|lc$oECjnL`34tZRA7shDKPuf=5K;$q*JaHHS~FVc`UR zc0#U+h)Cyh13$YV*Q(koC@KX_5=eQ^i5k8~L`34v1&Tlc$a~Lq1y8l0u;24TB?Y91 zhK5q(!^3W0Utfdm7ZH&-SX*0TVPU~pTwE;9&CR(>OG|m+LrnPiVdM)9 zol1e@SkDe7aC(M@hPuw2Ig>bk{P@oN7l?>REX~i)Gd(?BTv}Rs3_QdHj@{cS3!Q3< z0Q{gy0zD^Bp6t4C;lj?hT0}%*YjSduix)5Yp;Mp~JP-(-WZ<*GVYDRBJ2f@cb?)4` zCN)-5fRza!Gj0gTeohlV-^$G3kh5>;SweZbe}$b zI@kje5fKUHWkxsUqi4Y81p-j&-E{BYzrS~RdAa#56cLe*ap1rKZr{GW4m={z%L(h@ zB!JYhW5+aN@80bI@3Hv^oU$H{|D0%1*u2<^Un(LZJHyKk`;5(v9!@GyTUOA3Nnn@v z9eu#6cl|#=7I*>p9k8~I-ytF*?JG1(f`L<3&;bAZn;ziDz^?<}eHnP)d;MkLpTIlY z{GB2q(q784q|!c|W^sX=l$ObT5)(`!BGTca4jd@W(+mS22fhV_9Y6gH@I3G%U>q|% zCn6#-6*i<6l?C7c@K@lizS{Kmgx^BK@tr8K5hp%HcvfI-WOTZDLpAI;P`N#VRW*!{i9`HBd511_}k#HO* zh1uNyn9^;h6*wXy4f+NqY{C{aeHQb_J__By6!09CN3C4oW#9~Ej=ZTV1x~An)I>z$ z$PSxV1MjT@4=W7x;_! zUIF+4@Gzpb`~owC1}0d(;H@9~OvZN;k@@jlZM3LFuU+KmCf$Ary`nBbWNzKRK( z=P`feqc8|eSg(HxybF8*cmp$+^d;}wv{g5OneU050!KuoHe;B>iBp)c`7I`FrYVKZ zpCfF~IGg{5|J{3@1)c^z2K>gmcFn3wc%O}&0!Kt-YwZ>`P87!SQ+T`G>&U-s7})?OCg?;C(i7S{5xLQrXuqhX|)I3k3W*n^uR7?^l@R z&`?@5@NS^yqnJaI{SB!v0Y7bKqqGl4M5KOiVCF+!#jHkar?7$2vS`0|vw~@#KWFnS zX77z3HrIe}10OcCQ3@Opk^0&DXD?=AzDI=WR98=(h{RZu(xL%} z(!zo6si~>$oVD{m%@eN&Q$$2sN`WIHB9RCS92ao!-@jkWJrzVmL^gQYu`zFB3kHr~P`q;Gih4gHB2pJG zJN!jwn?lD971w$$UcY`lf~JXxNGI{K!(W|-^}K17nuA%TmIQhR1_pZX+_|GAsUjj$ zv%P!wcI9%pb>Japg<1hGbi#(zT;Opomn+Q9&L$&msfdWg*6i$TGMCF0Fw3FK=5g%a zjsHks_TqF83=H(Bmm?w~weWg34}nLR(6Kjuy1_Xd|4{_;xm+%v$z;+Iw^>9)VkVQx zq;t7k9>^028o$OLG7I)}HYR(PmzPry9y~}KJ$iIIN8N~sNQXLg>QriOZqCKb+vG9x zHc(+m&HmXwQ!RxVUh5qj8*?sQy43W0VT*`Jd}K12bT*rHfpyF0m1>D~TI7Za51I3(n=smlOBy-E%P~Li-1F z+H*G*_u<%rLu`HzC}}Y#kI^&GFRo9U>yq8EOfa(6E8p!=hU7h=|CO dr{4$q002ovPDHLkV1oM+4Y2?K literal 0 HcmV?d00001 diff --git a/doc/images/password-entry-row.png b/doc/images/password-entry-row.png new file mode 100644 index 0000000000000000000000000000000000000000..bfff08799dec959bb208fb26d9161fa03cb8a50f GIT binary patch literal 2596 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 + + + + + 6 + 6 + 6 + 6 + none + 400 + + + + Title + A long password + False + + + + 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 entry-row ](class.EntryRow.html) +### Password Entry Row + +[ + + password-entry-row +](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..ee97b2d1 --- /dev/null +++ b/src/adw-password-entry-row.c @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2021 Maximiliano Sandoval + * Copyright (C) 2022 Purism SPC + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" +#include + +#include "adw-password-entry-row.h" + +#include "adw-entry-row-private.h" +#include "adw-macros-private.h" + +/** + * AdwPasswordEntryRow: + * + * A [class@EntryRow] tailored for entering secrets. + * + * + * + * password-entry-row + * + * + * It does not show its contents in clear text, does not allow to copy it to the + * clipboard, and 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. + * + * It offer a way to reveal the contents in clear text. + * + * ## CSS Nodes + * + * `AdwPasswordEntryRow` has a single CSS node with name `row` that carries + * `.entry` and `.password` style classes. + * + * 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 + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#if !defined(_ADWAITA_INSIDE) && !defined(ADWAITA_COMPILATION) +#error "Only can be included directly." +#endif + +#include "adw-version.h" + +#include + +#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 + */ + +#include + +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(); +} -- GitLab From 6acd15c2d863469bec47cfee5ca61882442034b2 Mon Sep 17 00:00:00 2001 From: Maximiliano Sandoval R Date: Tue, 20 Jul 2021 15:33:52 +0200 Subject: [PATCH 6/7] demo: Add examples of entry rows --- demo/adw-demo-window.ui | 4 ++- demo/pages/lists/adw-demo-page-lists.c | 26 ++++++++++++++++ demo/pages/lists/adw-demo-page-lists.ui | 41 +++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/demo/adw-demo-window.ui b/demo/adw-demo-window.ui index f2b4339c..4ce61d67 100644 --- a/demo/adw-demo-window.ui +++ b/demo/adw-demo-window.ui @@ -138,7 +138,9 @@ Lists - + + + diff --git a/demo/pages/lists/adw-demo-page-lists.c b/demo/pages/lists/adw-demo-page-lists.c index 594fa2ca..b3a782f0 100644 --- a/demo/pages/lists/adw-demo-page-lists.c +++ b/demo/pages/lists/adw-demo-page-lists.c @@ -7,14 +7,40 @@ struct _AdwDemoPageLists AdwBin parent_instance; }; +enum { + SIGNAL_ADD_TOAST, + SIGNAL_LAST_SIGNAL, +}; + +static guint signals[SIGNAL_LAST_SIGNAL]; + G_DEFINE_TYPE (AdwDemoPageLists, adw_demo_page_lists, ADW_TYPE_BIN) +static void +entry_apply_cb (AdwDemoPageLists *self) +{ + AdwToast *toast = adw_toast_new ("Changes applied"); + + g_signal_emit (self, signals[SIGNAL_ADD_TOAST], 0, toast); +} + static void adw_demo_page_lists_class_init (AdwDemoPageListsClass *klass) { GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + signals[SIGNAL_ADD_TOAST] = + g_signal_new ("add-toast", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 1, + ADW_TYPE_TOAST); + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Adwaita1/Demo/ui/pages/lists/adw-demo-page-lists.ui"); + + gtk_widget_class_bind_template_callback (widget_class, entry_apply_cb); } static void diff --git a/demo/pages/lists/adw-demo-page-lists.ui b/demo/pages/lists/adw-demo-page-lists.ui index 3286dbe7..03780e56 100644 --- a/demo/pages/lists/adw-demo-page-lists.ui +++ b/demo/pages/lists/adw-demo-page-lists.ui @@ -66,6 +66,47 @@ + + + Entry Rows + + + Entry Row + True + + + + + + + + + Entry With Confirmation + True + + + + + + Entry With Suffix + + + center + edit-copy-symbolic + + + + + + + + Password Entry + + + + Combo Rows -- GitLab From 4594b1fe389105974e4cb1dc6df21b02ba3ad073 Mon Sep 17 00:00:00 2001 From: Maximiliano Sandoval R Date: Sat, 24 Jul 2021 23:10:59 +0200 Subject: [PATCH 7/7] demo: Use an entry row for avatar text --- demo/pages/avatar/adw-demo-page-avatar.c | 2 +- demo/pages/avatar/adw-demo-page-avatar.ui | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/demo/pages/avatar/adw-demo-page-avatar.c b/demo/pages/avatar/adw-demo-page-avatar.c index fb935e06..e5f0d42a 100644 --- a/demo/pages/avatar/adw-demo-page-avatar.c +++ b/demo/pages/avatar/adw-demo-page-avatar.c @@ -7,7 +7,7 @@ struct _AdwDemoPageAvatar AdwBin parent_instance; AdwAvatar *avatar; - GtkEntry *text; + AdwEntryRow *text; GtkLabel *file_chooser_label; GtkListBox *contacts; }; diff --git a/demo/pages/avatar/adw-demo-page-avatar.ui b/demo/pages/avatar/adw-demo-page-avatar.ui index b3acea24..0bd9ff94 100644 --- a/demo/pages/avatar/adw-demo-page-avatar.ui +++ b/demo/pages/avatar/adw-demo-page-avatar.ui @@ -66,13 +66,8 @@ - + Text - - - center - - -- GitLab