From 981ed22dff12f5c356faa67886a48d1b2ab18a84 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sun, 19 Dec 2021 19:22:31 +0100 Subject: [PATCH] label: Add gtk_label_set_natural_wrap_mode() Allows influencing natural size requests so that labels can request more width than necessary for a given height. Related: !4245 Related: #4535 --- gtk/gtkenums.h | 25 +++++ gtk/gtklabel.c | 106 ++++++++++++++++-- gtk/gtklabel.h | 5 + .../label-wrap-word-char-natural-size.ref.ui | 14 ++- .../label-wrap-word-char-natural-size.ui | 17 ++- 5 files changed, 152 insertions(+), 15 deletions(-) diff --git a/gtk/gtkenums.h b/gtk/gtkenums.h index d356a9154e..69faa56db8 100644 --- a/gtk/gtkenums.h +++ b/gtk/gtkenums.h @@ -285,6 +285,31 @@ typedef enum GTK_MOVEMENT_HORIZONTAL_PAGES } GtkMovementStep; +/** + * GtkNaturalWrapMode: + * @GTK_NATURAL_WRAP_INHERIT: Inherit the minimum size request. + * In particular, this should be used with %PANGO_WRAP_CHAR. + * @GTK_NATURAL_WRAP_NONE: Try not to wrap the text. This mode is the + * closest to GTK3's behavior but can lead to a wide label leaving + * lots of empty space below the text. + * @GTK_NATURAL_WRAP_WORD: Attempt to wrap at word boundaries. This + * is useful in particular when using %PANGO_WRAP_WORD_CHAR as the + * wrap mode. + * + * Options for selecting a different wrap mode for natural size + * requests. + * + * See for example the [property@Gtk.Label:natural-wrap-mode] property. + * + * Since: 4.6 + */ +typedef enum +{ + GTK_NATURAL_WRAP_INHERIT, + GTK_NATURAL_WRAP_NONE, + GTK_NATURAL_WRAP_WORD +} GtkNaturalWrapMode; + /** * GtkScrollStep: * @GTK_SCROLL_STEPS: Scroll in steps. diff --git a/gtk/gtklabel.c b/gtk/gtklabel.c index 9af80f43a9..1f019fadaa 100644 --- a/gtk/gtklabel.c +++ b/gtk/gtklabel.c @@ -272,6 +272,7 @@ struct _GtkLabel guint ellipsize : 3; guint use_markup : 1; guint wrap_mode : 3; + guint natural_wrap_mode : 3; guint single_line_mode : 1; guint in_click : 1; guint track_links : 1; @@ -380,6 +381,7 @@ enum { PROP_JUSTIFY, PROP_WRAP, PROP_WRAP_MODE, + PROP_NATURAL_WRAP_MODE, PROP_SELECTABLE, PROP_MNEMONIC_KEYVAL, PROP_MNEMONIC_WIDGET, @@ -484,6 +486,9 @@ gtk_label_set_property (GObject *object, case PROP_WRAP_MODE: gtk_label_set_wrap_mode (self, g_value_get_enum (value)); break; + case PROP_NATURAL_WRAP_MODE: + gtk_label_set_natural_wrap_mode (self, g_value_get_enum (value)); + break; case PROP_SELECTABLE: gtk_label_set_selectable (self, g_value_get_boolean (value)); break; @@ -551,6 +556,9 @@ gtk_label_get_property (GObject *object, case PROP_WRAP_MODE: g_value_set_enum (value, self->wrap_mode); break; + case PROP_NATURAL_WRAP_MODE: + g_value_set_enum (value, self->natural_wrap_mode); + break; case PROP_SELECTABLE: g_value_set_boolean (value, gtk_label_get_selectable (self)); break; @@ -604,6 +612,7 @@ gtk_label_init (GtkLabel *self) self->jtype = GTK_JUSTIFY_LEFT; self->wrap = FALSE; self->wrap_mode = PANGO_WRAP_WORD; + self->natural_wrap_mode = GTK_NATURAL_WRAP_INHERIT; self->ellipsize = PANGO_ELLIPSIZE_NONE; self->use_underline = FALSE; @@ -1218,8 +1227,6 @@ get_width_for_height (GtkLabel *self, gtk_label_ensure_layout (self); layout = pango_layout_copy (self->layout); pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_NONE); - if (self->wrap_mode == PANGO_WRAP_WORD_CHAR) - pango_layout_set_wrap (layout, PANGO_WRAP_WORD); /* binary search for the smallest width where the height doesn't * eclipse the given height */ @@ -1228,8 +1235,19 @@ get_width_for_height (GtkLabel *self, pango_layout_set_width (layout, -1); pango_layout_get_size (layout, &max, NULL); - *natural_width = my_pango_layout_get_width_for_height (layout, height, min, max); + /* first, do natural width */ + if (self->natural_wrap_mode == GTK_NATURAL_WRAP_NONE) + { + *natural_width = max; + } + else + { + if (self->natural_wrap_mode == GTK_NATURAL_WRAP_WORD) + pango_layout_set_wrap (layout, PANGO_WRAP_WORD); + *natural_width = my_pango_layout_get_width_for_height (layout, height, min, max); + } + /* then, do minimum width */ if (self->ellipsize != PANGO_ELLIPSIZE_NONE) { g_object_unref (layout); @@ -1237,14 +1255,14 @@ get_width_for_height (GtkLabel *self, pango_layout_get_size (layout, minimum_width, NULL); *minimum_width = MAX (*minimum_width, minimum_default); } - else if (self->wrap_mode == PANGO_WRAP_WORD_CHAR) + else if (self->natural_wrap_mode == GTK_NATURAL_WRAP_INHERIT) { - pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR); - *minimum_width = my_pango_layout_get_width_for_height (layout, height, min, *natural_width); + *minimum_width = *natural_width; } else { - *minimum_width = *natural_width; + pango_layout_set_wrap (layout, self->wrap_mode); + *minimum_width = my_pango_layout_get_width_for_height (layout, height, min, *natural_width); } } @@ -2375,6 +2393,9 @@ gtk_label_class_init (GtkLabelClass *class) * This only affects the formatting if line wrapping is on (see the * [property@Gtk.Label:wrap] property). The default is %PANGO_WRAP_WORD, * which means wrap on word boundaries. + * + * For sizing behavior, also consider the [property@Gtk.Label:natural-wrap-mode] + * property. */ label_props[PROP_WRAP_MODE] = g_param_spec_enum ("wrap-mode", @@ -2384,6 +2405,27 @@ gtk_label_class_init (GtkLabelClass *class) PANGO_WRAP_WORD, GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + /** + * GtkLabel:natural-wrap-mode: (attributes org.gtk.Property.get=gtk_label_get_natural_wrap_mode org.gtk.Property.set=gtk_label_set_natural_wrap_mode) + * + * Select the line wrapping for the natural size request. + * + * This only affects the natural size requested. For the actual wrapping used, + * see the [property@Gtk.Label:wrap-mode] property. + * + * The default is %GTK_NATURAL_WRAP_INHERIT, which inherits the behavior of the + * [property@Gtk.Label:wrap-mode] property. + * + * Since: 4.6 + */ + label_props[PROP_NATURAL_WRAP_MODE] = + g_param_spec_enum ("natural-wrap-mode", + P_("Natrural wrap mode"), + P_("If wrap is set, controls linewrapping for natural size requests"), + GTK_TYPE_NATURAL_WRAP_MODE, + GTK_NATURAL_WRAP_INHERIT, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + /** * GtkLabel:selectable: (attributes org.gtk.Property.get=gtk_label_get_selectable og.gtk.Property.set=gtk_label_set_selectable) * @@ -3998,6 +4040,9 @@ gtk_label_get_wrap (GtkLabel *self) * This only affects the label if line wrapping is on. (See * [method@Gtk.Label.set_wrap]) The default is %PANGO_WRAP_WORD * which means wrap on word boundaries. + * + * For sizing behavior, also consider the [property@Gtk.Label:natural-wrap-mode] + * property. */ void gtk_label_set_wrap_mode (GtkLabel *self, @@ -4032,6 +4077,53 @@ gtk_label_get_wrap_mode (GtkLabel *self) return self->wrap_mode; } +/** + * gtk_label_set_natural_wrap_mode: (attributes org.gtk.Method.set_property=natural-wrap-mode) + * @self: a `GtkLabel` + * @wrap_mode: the line wrapping mode + * + * Select the line wrapping for the natural size request. + * + * This only affects the natural size requested, for the actual wrapping used, + * see the [property@Gtk.Label:wrap-mode] property. + * + * Since: 4.6 + */ +void +gtk_label_set_natural_wrap_mode (GtkLabel *self, + GtkNaturalWrapMode wrap_mode) +{ + g_return_if_fail (GTK_IS_LABEL (self)); + + if (self->natural_wrap_mode != wrap_mode) + { + self->natural_wrap_mode = wrap_mode; + g_object_notify_by_pspec (G_OBJECT (self), label_props[PROP_NATURAL_WRAP_MODE]); + + gtk_widget_queue_resize (GTK_WIDGET (self)); + } +} + +/** + * gtk_label_get_natural_wrap_mode: (attributes org.gtk.Method.get_property=natural-wrap-mode) + * @self: a `GtkLabel` + * + * Returns line wrap mode used by the label. + * + * See [method@Gtk.Label.set_natural_wrap_mode]. + * + * Returns: the natural line wrap mode + * + * Since: 4.6 + */ +GtkNaturalWrapMode +gtk_label_get_natural_wrap_mode (GtkLabel *self) +{ + g_return_val_if_fail (GTK_IS_LABEL (self), PANGO_WRAP_CHAR); + + return self->natural_wrap_mode; +} + static void gtk_label_clear_layout (GtkLabel *self) { diff --git a/gtk/gtklabel.h b/gtk/gtklabel.h index 36441a5fe9..4a42a4ba32 100644 --- a/gtk/gtklabel.h +++ b/gtk/gtklabel.h @@ -122,6 +122,11 @@ void gtk_label_set_wrap_mode (GtkLabel *self, PangoWrapMode wrap_mode); GDK_AVAILABLE_IN_ALL PangoWrapMode gtk_label_get_wrap_mode (GtkLabel *self); +GDK_AVAILABLE_IN_4_6 +void gtk_label_set_natural_wrap_mode (GtkLabel *self, + GtkNaturalWrapMode wrap_mode); +GDK_AVAILABLE_IN_4_6 +GtkNaturalWrapMode gtk_label_get_natural_wrap_mode(GtkLabel *self); GDK_AVAILABLE_IN_ALL void gtk_label_set_selectable (GtkLabel *self, gboolean setting); diff --git a/testsuite/reftests/label-wrap-word-char-natural-size.ref.ui b/testsuite/reftests/label-wrap-word-char-natural-size.ref.ui index b8a08efc62..405064420c 100644 --- a/testsuite/reftests/label-wrap-word-char-natural-size.ref.ui +++ b/testsuite/reftests/label-wrap-word-char-natural-size.ref.ui @@ -1,21 +1,27 @@ - 300 + 600 300 center - center - two + lots +of lines - unwrapped + unwrappable +words + + + + + single line of text diff --git a/testsuite/reftests/label-wrap-word-char-natural-size.ui b/testsuite/reftests/label-wrap-word-char-natural-size.ui index 929e5a3089..f70099651e 100644 --- a/testsuite/reftests/label-wrap-word-char-natural-size.ui +++ b/testsuite/reftests/label-wrap-word-char-natural-size.ui @@ -1,23 +1,32 @@ - 300 + 600 300 center - center - two + lots +of lines - unwrapped + unwrappable words 1 word-char + word + + + + + single line of text + 1 + word-char + none