From 393ef4d0a23053fdfd0445c37af55da72aa66f30 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sat, 11 Jun 2022 16:28:27 +0200 Subject: [PATCH 1/5] inscription: Fixate layout at top when it doesn't fit Ellipsized and clipped layouts shouldn't reposition themselves according to yalign when they don't fully fit. --- gtk/gtkinscription.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gtk/gtkinscription.c b/gtk/gtkinscription.c index 155f334ad8..6a2260027a 100644 --- a/gtk/gtkinscription.c +++ b/gtk/gtkinscription.c @@ -395,9 +395,15 @@ gtk_inscription_get_layout_location (GtkInscription *self, /* yalign is 0 because we can't support yalign while baseline aligning */ y = baseline - layout_baseline; } + else if (pango_layout_is_ellipsized (self->layout)) + { + y = 0.f; + } else { y = floor ((widget_height - logical.height) * self->yalign); + if (y < 0) + y = 0.f; } *x_out = x; From 4809efd6300d9e447758d6ab929863e40e451933 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sat, 11 Jun 2022 22:15:43 +0200 Subject: [PATCH 2/5] reftests: Test overflowing inscription yalign Overflowing inscriptions should always align to the top, even when half an extra line is available. --- .../inscription-overflow-yalign.ref.ui | 49 +++++++++++++++++++ .../reftests/inscription-overflow-yalign.ui | 45 +++++++++++++++++ testsuite/reftests/meson.build | 2 + 3 files changed, 96 insertions(+) create mode 100644 testsuite/reftests/inscription-overflow-yalign.ref.ui create mode 100644 testsuite/reftests/inscription-overflow-yalign.ui diff --git a/testsuite/reftests/inscription-overflow-yalign.ref.ui b/testsuite/reftests/inscription-overflow-yalign.ref.ui new file mode 100644 index 0000000000..448a949f37 --- /dev/null +++ b/testsuite/reftests/inscription-overflow-yalign.ref.ui @@ -0,0 +1,49 @@ + + + + 200 + + + vertical + 1 + + + THIS +HIGH + + + + + + + + Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. + clip + 0.0 + + + + + Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. + ellipsize-start + 0.0 + + + + + Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. + ellipsize-middle + 0.0 + + + + + Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. + ellipsize-end + 0.0 + + + + + + diff --git a/testsuite/reftests/inscription-overflow-yalign.ui b/testsuite/reftests/inscription-overflow-yalign.ui new file mode 100644 index 0000000000..ee0e92ee9e --- /dev/null +++ b/testsuite/reftests/inscription-overflow-yalign.ui @@ -0,0 +1,45 @@ + + + + 200 + + + vertical + 1 + + + THIS +HIGH + + + + + + + + Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. + clip + + + + + Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. + ellipsize-start + + + + + Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. + ellipsize-middle + + + + + Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. + ellipsize-end + + + + + + diff --git a/testsuite/reftests/meson.build b/testsuite/reftests/meson.build index 7c03cb7062..c59c337297 100644 --- a/testsuite/reftests/meson.build +++ b/testsuite/reftests/meson.build @@ -371,6 +371,8 @@ testdata = [ 'inscription-markup.ui', 'inscription-overflow.ref.ui', 'inscription-overflow.ui', + 'inscription-overflow-yalign.ref.ui', + 'inscription-overflow-yalign.ui', 'label-attribute-preference.css', 'label-attribute-preference.ref.ui', 'label-attribute-preference.ui', From 98e0ca7477a31fb2d40c7d6ede36acbf809efe7b Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sat, 11 Jun 2022 20:27:53 +0200 Subject: [PATCH 3/5] gtk-demo: Add a "Read More" label demo. --- demos/gtk-demo/demo.gresource.xml | 1 + demos/gtk-demo/meson.build | 1 + demos/gtk-demo/read_more.c | 238 ++++++++++++++++++++++++++++++ 3 files changed, 240 insertions(+) create mode 100644 demos/gtk-demo/read_more.c diff --git a/demos/gtk-demo/demo.gresource.xml b/demos/gtk-demo/demo.gresource.xml index f1caed2b8d..849ef462dc 100644 --- a/demos/gtk-demo/demo.gresource.xml +++ b/demos/gtk-demo/demo.gresource.xml @@ -329,6 +329,7 @@ pickers.c printing.c revealer.c + read_more.c rotated_text.c scale.c search_entry.c diff --git a/demos/gtk-demo/meson.build b/demos/gtk-demo/meson.build index 1347baf2aa..ef89fbc7d2 100644 --- a/demos/gtk-demo/meson.build +++ b/demos/gtk-demo/meson.build @@ -73,6 +73,7 @@ demos = files([ 'peg_solitaire.c', 'pickers.c', 'printing.c', + 'read_more.c', 'revealer.c', 'rotated_text.c', 'scale.c', diff --git a/demos/gtk-demo/read_more.c b/demos/gtk-demo/read_more.c new file mode 100644 index 0000000000..3d772068eb --- /dev/null +++ b/demos/gtk-demo/read_more.c @@ -0,0 +1,238 @@ +/* Read More + * + * A simple implementation of a widget that can either + * display a lot of text or just the first few lines with a + * "Read More" button. + */ + +#include + +#define READ_TYPE_MORE (read_more_get_type ()) +G_DECLARE_FINAL_TYPE(ReadMore, read_more, READ, MORE, GtkWidget) + +struct _ReadMore { + GtkWidget parent_instance; + + GtkWidget *label; + GtkWidget *inscription; + GtkWidget *box; + gboolean show_more; +}; + +G_DEFINE_TYPE (ReadMore, read_more, GTK_TYPE_WIDGET) + +static GtkSizeRequestMode +read_more_get_request_mode (GtkWidget *widget) +{ + return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; +} + +static void +read_more_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + ReadMore *self = READ_MORE (widget); + int label_min, label_nat, label_min_baseline, label_nat_baseline; + int box_min, box_nat, box_min_baseline, box_nat_baseline; + int min_check; + + if (self->show_more) + min_check = G_MAXINT; + else if (for_size >= 0) + gtk_widget_measure (self->box, 1 - orientation, -1, &min_check, NULL, NULL, NULL); + else + min_check = -1; + + if (min_check > for_size) + { + gtk_widget_measure (self->label, + orientation, + for_size, + minimum, natural, + minimum_baseline, natural_baseline); + return; + } + + else if (for_size >= 0) + gtk_widget_measure (self->label, 1 - orientation, -1, &min_check, NULL, NULL, NULL); + else + min_check = -1; + + if (min_check > for_size) + { + gtk_widget_measure (self->box, + orientation, + for_size, + minimum, natural, + minimum_baseline, natural_baseline); + return; + } + + gtk_widget_measure (self->label, + orientation, + for_size, + &label_min, &label_nat, + &label_min_baseline, &label_nat_baseline); + gtk_widget_measure (self->box, + orientation, + for_size, + &box_min, &box_nat, + &box_min_baseline, &box_nat_baseline); + + *minimum = MIN (label_min, box_min); + *natural = MIN (label_nat, box_nat); + + /* FIXME: Figure out baselines! */ +} + +static void +read_more_allocate (GtkWidget *widget, + int width, + int height, + int baseline) +{ + ReadMore *self = READ_MORE (widget); + gboolean show_more; + + if (self->show_more) + { + show_more = TRUE; + } + else + { + int needed; + + /* check to see if we have enough space to show all text */ + gtk_widget_measure (self->label, + GTK_ORIENTATION_VERTICAL, + width, + &needed, NULL, NULL, NULL); + + show_more = needed <= height; + } + + gtk_widget_set_child_visible (self->label, show_more); + gtk_widget_set_child_visible (self->box, !show_more); + + if (show_more) + gtk_widget_size_allocate (self->label, &(GtkAllocation) { 0, 0, width, height }, baseline); + else + gtk_widget_size_allocate (self->box, &(GtkAllocation) { 0, 0, width, height }, baseline); +} + +static void +read_more_dispose (GObject *object) +{ + ReadMore *self = READ_MORE (object); + + g_clear_pointer (&self->label, gtk_widget_unparent); + g_clear_pointer (&self->box, gtk_widget_unparent); + + G_OBJECT_CLASS (read_more_parent_class)->dispose (object); +} + +static void +read_more_class_init (ReadMoreClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + widget_class->get_request_mode = read_more_get_request_mode; + widget_class->measure = read_more_measure; + widget_class->size_allocate = read_more_allocate; + + object_class->dispose = read_more_dispose; +} + +static void +read_more_clicked (GtkButton *button, + ReadMore *self) +{ + self->show_more = TRUE; + gtk_widget_queue_resize (GTK_WIDGET (self)); +} + +static void +read_more_init (ReadMore *self) +{ + GtkWidget *button; + + self->label = gtk_label_new (NULL); + gtk_label_set_xalign (GTK_LABEL (self->label), 0.0); + gtk_label_set_yalign (GTK_LABEL (self->label), 0.0); + gtk_label_set_wrap (GTK_LABEL (self->label), TRUE); + gtk_label_set_width_chars (GTK_LABEL (self->label), 3); + gtk_label_set_max_width_chars (GTK_LABEL (self->label), 30); + gtk_widget_set_parent (self->label, GTK_WIDGET (self)); + + self->box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_widget_set_vexpand (self->box, FALSE); + gtk_widget_set_parent (self->box, GTK_WIDGET (self)); + + self->inscription = gtk_inscription_new (NULL); + gtk_inscription_set_xalign (GTK_INSCRIPTION (self->inscription), 0.0); + gtk_inscription_set_yalign (GTK_INSCRIPTION (self->inscription), 0.0); + gtk_inscription_set_min_lines (GTK_INSCRIPTION (self->inscription), 3); + gtk_inscription_set_nat_chars (GTK_INSCRIPTION (self->inscription), 30); + gtk_widget_set_vexpand (self->inscription, TRUE); + gtk_box_append (GTK_BOX (self->box), self->inscription); + + button = gtk_button_new_with_label ("Read More"); + g_signal_connect (button, "clicked", G_CALLBACK (read_more_clicked), self); + gtk_box_append (GTK_BOX (self->box), button); +} + +static void +read_more_set_text (ReadMore *self, + const char *text) +{ + gtk_label_set_label (GTK_LABEL (self->label), text); + gtk_inscription_set_text (GTK_INSCRIPTION (self->inscription), text); +} + +static GtkWidget * +read_more_new (const char *text) +{ + ReadMore *self = g_object_new (READ_TYPE_MORE, NULL); + + read_more_set_text (self, text); + + return GTK_WIDGET (self); +} + +GtkWidget * +do_read_more (GtkWidget *do_widget) +{ + static GtkWidget *window = NULL; + + if (!window) + { + GtkWidget *readmore; + + window = gtk_window_new (); + gtk_window_set_display (GTK_WINDOW (window), + gtk_widget_get_display (do_widget)); + gtk_window_set_title (GTK_WINDOW (window), "Read More"); + g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window); + + readmore = read_more_new ( +"I'd just like to interject for a moment. What you're referring to as Linux, is in fact, GNU/Linux, or as I've recently taken to calling it, GNU plus Linux. Linux is not an operating system unto itself, but rather another free component of a fully functioning GNU system made useful by the GNU corelibs, shell utilities and vital system components comprising a full OS as defined by POSIX.\n" +"\n" +"Many computer users run a modified version of the GNU system every day, without realizing it. Through a peculiar turn of events, the version of GNU which is widely used today is often called \"Linux\", and many of its users are not aware that it is basically the GNU system, developed by the GNU Project.\n" +"\n" +"There really is a Linux, and these people are using it, but it is just a part of the system they use. Linux is the kernel: the program in the system that allocates the machine's resources to the other programs that you run. The kernel is an essential part of an operating system, but useless by itself; it can only function in the context of a complete operating system. Linux is normally used in combination with the GNU operating system: the whole system is basically GNU with Linux added, or GNU/Linux. All the so-called \"Linux\" distributions are really distributions of GNU/Linux."); + gtk_window_set_child (GTK_WINDOW (window), readmore); + } + + if (!gtk_widget_get_visible (window)) + gtk_widget_show (window); + else + gtk_window_destroy (GTK_WINDOW (window)); + + return window; +} From 4c1fc4f5d7dc920cc1c42c71db84ba8b8da4bad1 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sun, 12 Jun 2022 02:42:00 +0200 Subject: [PATCH 4/5] reftests: Test inscription multiline overflow --- .../inscription-overflow-multiline.ref.ui | 66 +++++++++++++++++++ .../inscription-overflow-multiline.ui | 40 +++++++++++ testsuite/reftests/meson.build | 2 + 3 files changed, 108 insertions(+) create mode 100644 testsuite/reftests/inscription-overflow-multiline.ref.ui create mode 100644 testsuite/reftests/inscription-overflow-multiline.ui diff --git a/testsuite/reftests/inscription-overflow-multiline.ref.ui b/testsuite/reftests/inscription-overflow-multiline.ref.ui new file mode 100644 index 0000000000..3995238810 --- /dev/null +++ b/testsuite/reftests/inscription-overflow-multiline.ref.ui @@ -0,0 +1,66 @@ + + + + 200 + + + vertical + + + + + + + + + + + We wrap supercalifragilisticexpialidocious into 3 lines + 1 + word-char + 0 + 0 + + 1 + + + + + + + + We wrap supercalifragilisticexpialidocious into 3 lines + 1 + word-char + 0 + 0 + 2 + start + + + + + We wrap supercalifragilisticexpialidocious into 3 lines + 1 + word-char + 0 + 0 + 2 + middle + + + + + We wrap supercalifragilisticexpialidocious into 3 lines + 1 + word-char + 0 + 0 + 2 + end + + + + + + diff --git a/testsuite/reftests/inscription-overflow-multiline.ui b/testsuite/reftests/inscription-overflow-multiline.ui new file mode 100644 index 0000000000..3abca36da4 --- /dev/null +++ b/testsuite/reftests/inscription-overflow-multiline.ui @@ -0,0 +1,40 @@ + + + + 200 + + + vertical + + + We wrap supercalifragilisticexpialidocious into 3 lines + clip + 0 + 2 + + + + + We wrap supercalifragilisticexpialidocious into 3 lines + ellipsize-start + 2 + + + + + We wrap supercalifragilisticexpialidocious into 3 lines + ellipsize-middle + 2 + + + + + We wrap supercalifragilisticexpialidocious into 3 lines + ellipsize-end + 2 + + + + + + diff --git a/testsuite/reftests/meson.build b/testsuite/reftests/meson.build index c59c337297..eecb7f91ad 100644 --- a/testsuite/reftests/meson.build +++ b/testsuite/reftests/meson.build @@ -371,6 +371,8 @@ testdata = [ 'inscription-markup.ui', 'inscription-overflow.ref.ui', 'inscription-overflow.ui', + 'inscription-overflow-multiline.ref.ui', + 'inscription-overflow-multiline.ui', 'inscription-overflow-yalign.ref.ui', 'inscription-overflow-yalign.ui', 'label-attribute-preference.css', From 6d15549f5156c583421275298094a4cbbaa0f2d3 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sun, 12 Jun 2022 02:58:22 +0200 Subject: [PATCH 5/5] inscription: Add ::wrap-mode We use a different wrap-mode than GtkLabel, because we cannot just resize the widget to make long words fit, we have to fit the size we are given. --- gtk/gtkinscription.c | 69 ++++++++++++++++++++++++++++++++++++++++++++ gtk/gtkinscription.h | 5 ++++ 2 files changed, 74 insertions(+) diff --git a/gtk/gtkinscription.c b/gtk/gtkinscription.c index 6a2260027a..c8937df9e9 100644 --- a/gtk/gtkinscription.c +++ b/gtk/gtkinscription.c @@ -86,6 +86,7 @@ enum PROP_NAT_LINES, PROP_TEXT, PROP_TEXT_OVERFLOW, + PROP_WRAP_MODE, PROP_XALIGN, PROP_YALIGN, @@ -154,6 +155,10 @@ gtk_inscription_get_property (GObject *object, g_value_set_enum (value, self->overflow); break; + case PROP_WRAP_MODE: + g_value_set_enum (value, pango_layout_get_wrap (self->layout)); + break; + case PROP_XALIGN: g_value_set_float (value, self->xalign); break; @@ -210,6 +215,10 @@ gtk_inscription_set_property (GObject *object, gtk_inscription_set_text_overflow (self, g_value_get_enum (value)); break; + case PROP_WRAP_MODE: + gtk_inscription_set_wrap_mode (self, g_value_get_enum (value)); + break; + case PROP_XALIGN: gtk_inscription_set_xalign (self, g_value_get_float (value)); break; @@ -626,6 +635,21 @@ gtk_inscription_class_init (GtkInscriptionClass *klass) GTK_INSCRIPTION_OVERFLOW_CLIP, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + /** + * GtkInscription:wrap-mode: (attributes org.gtk.Property.get=gtk_inscription_get_wrap_mode org.gtk.Property.set=gtk_inscription_set_wrap_mode) + * + * Controls how the line wrapping is done. + * + * Note that unlike `GtkLabel`, the default here is %PANGO_WRAP_WORD_CHAR. + * + * Since: 4.8 + */ + properties[PROP_WRAP_MODE] = + g_param_spec_enum ("wrap-mode", NULL, NULL, + PANGO_TYPE_WRAP_MODE, + PANGO_WRAP_WORD_CHAR, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + /** * GtkInscription:xalign: (attributes org.gtk.Property.get=gtk_inscription_get_xalign org.gtk.Property.set=gtk_inscription_set_xalign) * @@ -1152,6 +1176,51 @@ gtk_inscription_get_text_overflow (GtkInscription *self) return self->overflow; } +/** + * gtk_inscription_set_wrap_mode: (attributes org.gtk.Method.set_property=wrap-mode) + * @self: a `GtkInscription` + * @wrap_mode: the line wrapping mode + * + * Controls how line wrapping is done. + * + * Since:4.8 + */ +void +gtk_inscription_set_wrap_mode (GtkInscription *self, + PangoWrapMode wrap_mode) +{ + g_return_if_fail (GTK_IS_INSCRIPTION (self)); + + if (pango_layout_get_wrap (self->layout) == wrap_mode) + return; + + pango_layout_set_wrap (self->layout, wrap_mode); + + gtk_widget_queue_draw (GTK_WIDGET (self)); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_WRAP_MODE]); +} + +/** + * gtk_inscription_get_wrap_mode: (attributes org.gtk.Method.get_property=wrap-mode) + * @self: a `GtkInscription` + * + * Returns line wrap mode used by the inscription. + * + * See [method@Gtk.Inscription.set_wrap_mode]. + * + * Returns: the line wrap mode + * + * Since:4.8 + */ +PangoWrapMode +gtk_inscription_get_wrap_mode (GtkInscription *self) +{ + g_return_val_if_fail (GTK_IS_INSCRIPTION (self), PANGO_WRAP_WORD_CHAR); + + return pango_layout_get_wrap (self->layout); +} + /** * gtk_inscription_set_markup: (attributes org.gtk.Method.set_property=markup) * @self: a `GtkInscription` diff --git a/gtk/gtkinscription.h b/gtk/gtkinscription.h index af53a7917d..41b2738fd8 100644 --- a/gtk/gtkinscription.h +++ b/gtk/gtkinscription.h @@ -71,6 +71,11 @@ GtkInscriptionOverflow gtk_inscription_get_text_overflow (GtkInscription GDK_AVAILABLE_IN_4_8 void gtk_inscription_set_text_overflow (GtkInscription *self, GtkInscriptionOverflow overflow); +GDK_AVAILABLE_IN_4_8 +PangoWrapMode gtk_inscription_get_wrap_mode (GtkInscription *self); +GDK_AVAILABLE_IN_4_8 +void gtk_inscription_set_wrap_mode (GtkInscription *self, + PangoWrapMode wrap_mode); GDK_AVAILABLE_IN_4_8 guint gtk_inscription_get_min_chars (GtkInscription *self);