From 53acff167b2ccb60203b9de0e436be8dfa9f570d Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Thu, 4 Nov 2021 13:28:45 +0100 Subject: [PATCH 1/4] Revert "label: Never measure more than max-width-chars" This reverts commit ba44e7a228534ff066694ad97d25eaa23ec5f3af. The change was meant to revert to old GTK3 behavior but it actually broke new GTK4 behavior that is in use where max-width-chars is used to determine an ideal size, but where we don't want to limit the width to that size. So what happens is the reintroduction of GTK3-style lots of whitepsace bugs, and we really don't want those. We also don't want to break backwards compat if we can avoid it. So let's revert this. The reftest that was made for this purpose has been adapted. Fixes #4399 --- gtk/gtklabel.c | 15 +-------------- ...dth-chars-and-halign-and-infinite-width.ref.ui | 4 +--- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/gtk/gtklabel.c b/gtk/gtklabel.c index 661902a275..b9886dd471 100644 --- a/gtk/gtklabel.c +++ b/gtk/gtklabel.c @@ -1045,20 +1045,7 @@ get_height_for_width (GtkLabel *self, PangoLayout *layout; int text_height, baseline; - width *= PANGO_SCALE; - if (self->max_width_chars > -1) - { - int char_pixels, width_chars; - - layout = gtk_label_get_measuring_layout (self, NULL, -1); - char_pixels = get_char_pixels (GTK_WIDGET (self), layout); - if (self->width_chars > self->max_width_chars) - width_chars = self->width_chars; - else - width_chars = self->max_width_chars; - width = MIN (char_pixels * width_chars, width); - } - layout = gtk_label_get_measuring_layout (self, NULL, width); + layout = gtk_label_get_measuring_layout (self, NULL, width * PANGO_SCALE); pango_layout_get_pixel_size (layout, NULL, &text_height); diff --git a/testsuite/reftests/label-max-width-chars-and-halign-and-infinite-width.ref.ui b/testsuite/reftests/label-max-width-chars-and-halign-and-infinite-width.ref.ui index 68c5d0a112..d3f075290b 100644 --- a/testsuite/reftests/label-max-width-chars-and-halign-and-infinite-width.ref.ui +++ b/testsuite/reftests/label-max-width-chars-and-halign-and-infinite-width.ref.ui @@ -8,9 +8,7 @@ start end 0 - ABCDE -ABCD - 4 + ABCDE ABCD From 8e27fc7f9b0f34880bdb3650ba141ad65883c094 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Thu, 4 Nov 2021 15:56:37 +0100 Subject: [PATCH 2/4] label: Redo measure() code The old code couldn't properly do height-for-width because it only computed the widest and smallest layout instead of looking at the actual passed in for-size. The label-sizing reftest has been adapted as the label code is now smart enough to always display the whole text and no longer requests a too small width-for-single-row when wrapping. --- gtk/gtklabel.c | 294 ++++++++++++++----------- testsuite/reftests/label-sizing.ref.ui | 108 +++------ 2 files changed, 199 insertions(+), 203 deletions(-) diff --git a/gtk/gtklabel.c b/gtk/gtklabel.c index b9886dd471..3580deae1e 100644 --- a/gtk/gtklabel.c +++ b/gtk/gtklabel.c @@ -1018,8 +1018,7 @@ gtk_label_get_measuring_layout (GtkLabel *self, } static int -get_char_pixels (GtkWidget *self, - PangoLayout *layout) +get_char_pixels (PangoLayout *layout) { PangoContext *context; PangoFontMetrics *metrics; @@ -1034,6 +1033,88 @@ get_char_pixels (GtkWidget *self, return MAX (char_width, digit_width); } +static void +get_default_widths (GtkLabel *self, + int *minimum, + int *natural) +{ + int char_pixels; + + if (self->width_chars < 0 && self->max_width_chars < 0) + { + if (minimum) + *minimum = -1; + if (natural) + *natural = -1; + return; + } + + gtk_label_ensure_layout (self); + char_pixels = get_char_pixels (self->layout); + + if (minimum) + { + if (self->width_chars < 0) + *minimum = -1; + else + *minimum = char_pixels * self->width_chars; + } + + if (natural) + { + if (self->max_width_chars < 0) + *natural = -1; + else + *natural = char_pixels * MAX (self->width_chars, self->max_width_chars); + } +} + +static void +get_static_size (GtkLabel *self, + GtkOrientation orientation, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + PangoLayout *layout; + + layout = gtk_label_get_measuring_layout (self, NULL, -1); + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + { + int minimum_default, natural_default; + + pango_layout_get_size (layout, natural, NULL); + if (self->ellipsize) + { + layout = gtk_label_get_measuring_layout (self, layout, 0); + pango_layout_get_size (layout, minimum, NULL); + /* yes, Pango ellipsizes even when that needs more space */ + *minimum = MIN (*minimum, *natural); + } + else + *minimum = *natural; + + get_default_widths (self, &minimum_default, &natural_default); + if (minimum_default > *minimum) + *minimum = minimum_default; + if (natural_default > -1) + *natural = natural_default; + *natural = MAX (*minimum, *natural); + } + else + { + pango_layout_get_size (layout, NULL, minimum); + *minimum_baseline = pango_layout_get_baseline (layout); + + *natural = *minimum; + *natural_baseline = *minimum_baseline; + } + + g_object_unref (layout); +} + static void get_height_for_width (GtkLabel *self, int width, @@ -1043,152 +1124,95 @@ get_height_for_width (GtkLabel *self, int *natural_baseline) { PangoLayout *layout; - int text_height, baseline; + int natural_width, text_height, baseline; - layout = gtk_label_get_measuring_layout (self, NULL, width * PANGO_SCALE); + if (width < 0) + { + /* Minimum height is assuming infinite width */ + layout = gtk_label_get_measuring_layout (self, NULL, -1); + pango_layout_get_size (layout, NULL, minimum_height); + baseline = pango_layout_get_baseline (layout); + *minimum_baseline = baseline; - pango_layout_get_pixel_size (layout, NULL, &text_height); + /* Natural height is assuming natural width */ + get_default_widths (self, NULL, &natural_width); - *minimum_height = text_height; - *natural_height = text_height; + layout = gtk_label_get_measuring_layout (self, layout, natural_width); + pango_layout_get_size (layout, NULL, natural_height); + baseline = pango_layout_get_baseline (layout); + *natural_baseline = baseline; + } + else + { + /* minimum = natural for any given width */ + layout = gtk_label_get_measuring_layout (self, NULL, width); - baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; - *minimum_baseline = baseline; - *natural_baseline = baseline; + pango_layout_get_size (layout, NULL, &text_height); + + *minimum_height = text_height; + *natural_height = text_height; + + baseline = pango_layout_get_baseline (layout); + *minimum_baseline = baseline; + *natural_baseline = baseline; + } g_object_unref (layout); } static void -gtk_label_get_preferred_layout_size (GtkLabel *self, - PangoRectangle *smallest, - PangoRectangle *widest, - int *smallest_baseline, - int *widest_baseline) +get_width_for_height (GtkLabel *self, + int height, + int *minimum_width, + int *natural_width) { PangoLayout *layout; - int char_pixels; + int minimum_default, natural_default; - /* "width-chars" Hard-coded minimum width: - * - minimum size should be MAX (width-chars, strlen ("...")); - * - natural size should be MAX (width-chars, strlen (self->text)); - * - * "max-width-chars" User specified maximum size requisition - * - minimum size should be MAX (width-chars, 0) - * - natural size should be MIN (max-width-chars, strlen (self->text)) - * - * For ellipsizing labels; if max-width-chars is specified: either it is used as - * a minimum size or the label text as a minimum size (natural size still overflows). - * - * For wrapping labels; A reasonable minimum size is useful to naturally layout - * interfaces automatically. In this case if no "width-chars" is specified, the minimum - * width will default to the wrap guess that gtk_label_ensure_layout() does. - */ + get_default_widths (self, &minimum_default, &natural_default); - /* Start off with the pixel extents of an as-wide-as-possible layout */ - layout = gtk_label_get_measuring_layout (self, NULL, -1); - - if (self->width_chars > -1 || self->max_width_chars > -1) - char_pixels = get_char_pixels (GTK_WIDGET (self), layout); - else - char_pixels = 0; - - pango_layout_get_extents (layout, NULL, widest); - widest->width = MAX (widest->width, char_pixels * self->width_chars); - widest->x = widest->y = 0; - *widest_baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; - - if (self->ellipsize || self->wrap) + if (height < 0) { - /* a layout with width 0 will be as small as humanly possible */ - layout = gtk_label_get_measuring_layout (self, - layout, - self->width_chars > -1 ? char_pixels * self->width_chars - : 0); + /* Minimum width is as many line breaks as possible */ + layout = gtk_label_get_measuring_layout (self, NULL, MAX (minimum_default, 0)); + pango_layout_get_size (layout, minimum_width, NULL); - pango_layout_get_extents (layout, NULL, smallest); - smallest->width = MAX (smallest->width, char_pixels * self->width_chars); - smallest->x = smallest->y = 0; - - *smallest_baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; - - if (self->max_width_chars > -1 && widest->width > char_pixels * self->max_width_chars) - { - layout = gtk_label_get_measuring_layout (self, - layout, - MAX (smallest->width, char_pixels * self->max_width_chars)); - pango_layout_get_extents (layout, NULL, widest); - widest->width = MAX (widest->width, char_pixels * self->width_chars); - widest->x = widest->y = 0; - - *widest_baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; - } - - if (widest->width < smallest->width) - { - *smallest = *widest; - *smallest_baseline = *widest_baseline; - } + /* Natural width is natural width - or as wide as possible */ + layout = gtk_label_get_measuring_layout (self, layout, natural_default); + pango_layout_get_size (layout, natural_width, NULL); + *natural_width = MAX (*natural_width, natural_default); + *natural_width = MAX (*natural_width, *minimum_width); } else { - *smallest = *widest; - *smallest_baseline = *widest_baseline; + int min, max, mid, text_width, text_height; + + /* binary search for the smallest width where the height doesn't + * eclipse the given height */ + min = MAX (minimum_default, 0); + layout = gtk_label_get_measuring_layout (self, NULL, -1); + pango_layout_get_size (layout, &max, NULL); + + while (min < max) + { + mid = (min + max) / 2; + layout = gtk_label_get_measuring_layout (self, layout, mid); + pango_layout_get_size (layout, &text_width, &text_height); + if (text_width > mid) + min = mid = text_width; + if (text_height > height) + min = mid + 1; + else + max = text_width; + } + + *minimum_width = min; + *natural_width = min; } g_object_unref (layout); } -static void -gtk_label_get_preferred_size (GtkWidget *widget, - GtkOrientation orientation, - int *minimum_size, - int *natural_size, - int *minimum_baseline, - int *natural_baseline) -{ - GtkLabel *self = GTK_LABEL (widget); - PangoRectangle widest_rect; - PangoRectangle smallest_rect; - int smallest_baseline; - int widest_baseline; - - gtk_label_get_preferred_layout_size (self, - &smallest_rect, &widest_rect, - &smallest_baseline, &widest_baseline); - - widest_rect.width = PANGO_PIXELS_CEIL (widest_rect.width); - widest_rect.height = PANGO_PIXELS_CEIL (widest_rect.height); - - smallest_rect.width = PANGO_PIXELS_CEIL (smallest_rect.width); - smallest_rect.height = PANGO_PIXELS_CEIL (smallest_rect.height); - - if (orientation == GTK_ORIENTATION_HORIZONTAL) - { - /* Normal desired width */ - *minimum_size = smallest_rect.width; - *natural_size = widest_rect.width; - } - else /* GTK_ORIENTATION_VERTICAL */ - { - if (smallest_rect.height < widest_rect.height) - { - *minimum_size = smallest_rect.height; - *natural_size = widest_rect.height; - *minimum_baseline = smallest_baseline; - *natural_baseline = widest_baseline; - } - else - { - *minimum_size = widest_rect.height; - *natural_size = smallest_rect.height; - *minimum_baseline = widest_baseline; - *natural_baseline = smallest_baseline; - } - } -} - - static void gtk_label_measure (GtkWidget *widget, GtkOrientation orientation, @@ -1200,14 +1224,22 @@ gtk_label_measure (GtkWidget *widget, { GtkLabel *self = GTK_LABEL (widget); - if (orientation == GTK_ORIENTATION_VERTICAL && for_size != -1 && self->wrap) - { - gtk_label_clear_layout (self); + if (for_size > 0) + for_size *= PANGO_SCALE; - get_height_for_width (self, for_size, minimum, natural, minimum_baseline, natural_baseline); - } + if (!self->wrap) + get_static_size (self, orientation, minimum, natural, minimum_baseline, natural_baseline); + else if (orientation == GTK_ORIENTATION_VERTICAL) + get_height_for_width (self, for_size, minimum, natural, minimum_baseline, natural_baseline); else - gtk_label_get_preferred_size (widget, orientation, minimum, natural, minimum_baseline, natural_baseline); + get_width_for_height (self, for_size, minimum, natural); + + *minimum = PANGO_PIXELS_CEIL (*minimum); + *natural = PANGO_PIXELS_CEIL (*natural); + if (*minimum_baseline > 0) + *minimum_baseline = PANGO_PIXELS_CEIL (*minimum_baseline); + if (*natural_baseline > 0) + *natural_baseline = PANGO_PIXELS_CEIL (*natural_baseline); } static void diff --git a/testsuite/reftests/label-sizing.ref.ui b/testsuite/reftests/label-sizing.ref.ui index 1616ef4f2f..4235424e80 100644 --- a/testsuite/reftests/label-sizing.ref.ui +++ b/testsuite/reftests/label-sizing.ref.ui @@ -212,8 +212,7 @@ end end 0 - ABCDE -ABCD + ABCDE ABCD @@ -224,8 +223,7 @@ ABCD end start 0 - ABCDE -ABCD + ABCDE ABCD @@ -646,8 +644,7 @@ ABCD end end 0 - ABCDE -ABCD + ABCDE ABCD 4 @@ -659,8 +656,7 @@ ABCD end start 0 - ABCDE -ABCD + ABCDE ABCD 4 @@ -1092,8 +1088,7 @@ ABCD end end 0 - ABCDE -ABCD + ABCDE ABCD 8 @@ -1105,8 +1100,7 @@ ABCD end start 0 - ABCDE -ABCD + ABCDE ABCD 8 @@ -1982,8 +1976,7 @@ ABCD end end 0 - ABCDE -ABCD + ABCDE ABCD 4 @@ -1995,8 +1988,7 @@ ABCD end start 0 - ABCDE -ABCD + ABCDE ABCD 4 @@ -2016,8 +2008,7 @@ ABCD start end 0 - ABCDE -ABCD + ABCDE ABCD 4 @@ -2029,8 +2020,7 @@ ABCD start start 0 - ABCDE -ABCD + ABCDE ABCD 4 @@ -2434,8 +2424,7 @@ ABCD end end 0 - ABCDE -ABCD + ABCDE ABCD 4 4 @@ -2448,8 +2437,7 @@ ABCD end start 0 - ABCDE -ABCD + ABCDE ABCD 4 4 @@ -2470,8 +2458,7 @@ ABCD start end 0 - ABCDE -ABCD + ABCDE ABCD 4 4 @@ -2484,8 +2471,7 @@ ABCD start start 0 - ABCDE -ABCD + ABCDE ABCD 4 4 @@ -2898,8 +2884,7 @@ ABCD end end 0 - ABCDE -ABCD + ABCDE ABCD 8 4 @@ -2912,8 +2897,7 @@ ABCD end start 0 - ABCDE -ABCD + ABCDE ABCD 8 4 @@ -2934,8 +2918,7 @@ ABCD start end 0 - ABCDE -ABCD + ABCDE ABCD 8 4 @@ -2948,8 +2931,7 @@ ABCD start start 0 - ABCDE -ABCD + ABCDE ABCD 8 4 @@ -3818,8 +3800,7 @@ ABCD end end 0 - ABCDE -ABCD + ABCDE ABCD 8 @@ -3831,8 +3812,7 @@ ABCD end start 0 - ABCDE -ABCD + ABCDE ABCD 8 @@ -3852,8 +3832,7 @@ ABCD start end 0 - ABCDE -ABCD + ABCDE ABCD 8 @@ -3865,8 +3844,7 @@ ABCD start start 0 - ABCDE -ABCD + ABCDE ABCD 8 @@ -4270,8 +4248,7 @@ ABCD end end 0 - ABCDE -ABCD + ABCDE ABCD 4 8 @@ -4284,8 +4261,7 @@ ABCD end start 0 - ABCDE -ABCD + ABCDE ABCD 4 8 @@ -4306,8 +4282,7 @@ ABCD start end 0 - ABCDE -ABCD + ABCDE ABCD 4 8 @@ -4320,8 +4295,7 @@ ABCD start start 0 - ABCDE -ABCD + ABCDE ABCD 4 8 @@ -4734,8 +4708,7 @@ ABCD end end 0 - ABCDE -ABCD + ABCDE ABCD 8 8 @@ -4748,8 +4721,7 @@ ABCD end start 0 - ABCDE -ABCD + ABCDE ABCD 8 8 @@ -4770,8 +4742,7 @@ ABCD start end 0 - ABCDE -ABCD + ABCDE ABCD 8 8 @@ -4784,8 +4755,7 @@ ABCD start start 0 - ABCDE -ABCD + ABCDE ABCD 8 8 @@ -5654,8 +5624,7 @@ ABCD end end 0 - ABCDE -ABCD + ABCDE ABCD 12 @@ -5667,8 +5636,7 @@ ABCD end start 0 - ABCDE -ABCD + ABCDE ABCD 12 @@ -6104,8 +6072,7 @@ ABCD end end 0 - ABCDE -ABCD + ABCDE ABCD 4 12 @@ -6118,8 +6085,7 @@ ABCD end start 0 - ABCDE -ABCD + ABCDE ABCD 4 12 @@ -6566,8 +6532,7 @@ ABCD end end 0 - ABCDE -ABCD + ABCDE ABCD 8 12 @@ -6580,8 +6545,7 @@ ABCD end start 0 - ABCDE -ABCD + ABCDE ABCD 8 12 From 5c9ae289373b967ba395fbe562c6db4c83e6415c Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 5 Nov 2021 20:30:49 +0100 Subject: [PATCH 3/4] boxlayout: Compute opposite size properly For size -1 in the opposite orientation, GtkBoxLayout used to measure the children based on their min size in the box's orientation instead of -1. That wasn't really intended, but was a side effect of how the sizing code did (not) distribute extra size above the minimum size. This is clearly not what we want. What we want is measuring the orientation as is for size -1. Then we want to just take the maximum of all children and use that. A reftest is incldued that ensures a vbox wraps a label just like an hbox does. --- gtk/gtkboxlayout.c | 52 +++++++++++++++++-- testsuite/reftests/meson.build | 2 + .../vbox-with-max-width-chars-label.ref.ui | 18 +++++++ .../vbox-with-max-width-chars-label.ui | 18 +++++++ 4 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 testsuite/reftests/vbox-with-max-width-chars-label.ref.ui create mode 100644 testsuite/reftests/vbox-with-max-width-chars-label.ui diff --git a/gtk/gtkboxlayout.c b/gtk/gtkboxlayout.c index f080748830..99feca37e5 100644 --- a/gtk/gtkboxlayout.c +++ b/gtk/gtkboxlayout.c @@ -256,11 +256,46 @@ gtk_box_layout_compute_size (GtkBoxLayout *self, static void gtk_box_layout_compute_opposite_size (GtkBoxLayout *self, GtkWidget *widget, - int for_size, int *minimum, int *natural, int *min_baseline, int *nat_baseline) +{ + GtkWidget *child; + int largest_min = 0, largest_nat = 0; + + for (child = gtk_widget_get_first_child (widget); + child != NULL; + child = gtk_widget_get_next_sibling (child)) + { + int child_min = 0; + int child_nat = 0; + + if (!gtk_widget_should_layout (child)) + continue; + + gtk_widget_measure (child, + OPPOSITE_ORIENTATION (self->orientation), + -1, + &child_min, &child_nat, + NULL, NULL); + + largest_min = MAX (largest_min, child_min); + largest_nat = MAX (largest_nat, child_nat); + } + + *minimum = largest_min; + *natural = largest_nat; +} + +static void +gtk_box_layout_compute_opposite_size_for_size (GtkBoxLayout *self, + GtkWidget *widget, + int for_size, + int *minimum, + int *natural, + int *min_baseline, + int *nat_baseline) { GtkWidget *child; int nvis_children; @@ -437,9 +472,18 @@ gtk_box_layout_measure (GtkLayoutManager *layout_manager, if (self->orientation != orientation) { - gtk_box_layout_compute_opposite_size (self, widget, for_size, - minimum, natural, - min_baseline, nat_baseline); + if (for_size < 0) + { + gtk_box_layout_compute_opposite_size (self, widget, + minimum, natural, + min_baseline, nat_baseline); + } + else + { + gtk_box_layout_compute_opposite_size_for_size (self, widget, for_size, + minimum, natural, + min_baseline, nat_baseline); + } } else { diff --git a/testsuite/reftests/meson.build b/testsuite/reftests/meson.build index b033760ed7..692a427438 100644 --- a/testsuite/reftests/meson.build +++ b/testsuite/reftests/meson.build @@ -498,6 +498,8 @@ testdata = [ 'unresolvable.css', 'unresolvable.ref.ui', 'unresolvable.ui', + 'vbox-with-max-width-chars-label.ref.ui', + 'vbox-with-max-width-chars-label.ui', 'window-border-width.ref.ui', 'window-border-width.ui', 'window-default-size.ref.ui', diff --git a/testsuite/reftests/vbox-with-max-width-chars-label.ref.ui b/testsuite/reftests/vbox-with-max-width-chars-label.ref.ui new file mode 100644 index 0000000000..aa732c608d --- /dev/null +++ b/testsuite/reftests/vbox-with-max-width-chars-label.ref.ui @@ -0,0 +1,18 @@ + + + + 0 + + + horizontal + + + Hello World + 1 + 1 + + + + + + diff --git a/testsuite/reftests/vbox-with-max-width-chars-label.ui b/testsuite/reftests/vbox-with-max-width-chars-label.ui new file mode 100644 index 0000000000..3a7d52e4a4 --- /dev/null +++ b/testsuite/reftests/vbox-with-max-width-chars-label.ui @@ -0,0 +1,18 @@ + + + + 0 + + + vertical + + + Hello World + 1 + 1 + + + + + + From c4e5242be0664a5e94a40657c5644cdeadc0097e Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 5 Nov 2021 20:51:00 +0100 Subject: [PATCH 4/4] picture: Setting can-shrink requires a resize So queue one. --- gtk/gtkpicture.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gtk/gtkpicture.c b/gtk/gtkpicture.c index 7694888c68..6821ecacfb 100644 --- a/gtk/gtkpicture.c +++ b/gtk/gtkpicture.c @@ -884,6 +884,9 @@ gtk_picture_set_can_shrink (GtkPicture *self, return; self->can_shrink = can_shrink; + + gtk_widget_queue_resize (GTK_WIDGET (self)); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CAN_SHRINK]); }