From 35e2dfce08b00577590c0bdc0d67366689449cc1 Mon Sep 17 00:00:00 2001 From: Tristan Van Berkom Date: Fri, 9 Apr 2010 21:47:25 -0400 Subject: [PATCH] Fixing height-for-width wrapping of GtkLabel Merged in fixes from the old branch in a patch prepared by Matthias Clasen, added some fixes of my own to make sure that label wrapping follows allocation and not requisition at show time (allocate time). --- gtk/gtklabel.c | 294 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 203 insertions(+), 91 deletions(-) diff --git a/gtk/gtklabel.c b/gtk/gtklabel.c index c775a66a5d..90904b4677 100644 --- a/gtk/gtklabel.c +++ b/gtk/gtklabel.c @@ -3104,15 +3104,19 @@ gtk_label_ensure_layout (GtkLabel *label) widget->allocation.width * PANGO_SCALE); else if (label->wrap) { - GtkWidgetAuxInfo *aux_info; + GtkWidgetAuxInfo *aux_info = _gtk_widget_get_aux_info (widget, FALSE); gint longest_paragraph; gint width, height; - pango_layout_set_wrap (label->layout, label->wrap_mode); - - aux_info = _gtk_widget_get_aux_info (widget, FALSE); - if (aux_info && aux_info->width > 0) + if (aux_info && aux_info->width > 0) pango_layout_set_width (label->layout, aux_info->width * PANGO_SCALE); + else if (widget->allocation.width > 1) + { + width = widget->allocation.width - label->misc.xpad * 2; + + pango_layout_set_wrap (label->layout, label->wrap_mode); + pango_layout_set_width (label->layout, MAX (width, 1) * PANGO_SCALE); + } else { GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (label)); @@ -3122,7 +3126,6 @@ gtk_label_ensure_layout (GtkLabel *label) pango_layout_get_extents (label->layout, NULL, &logical_rect); width = logical_rect.width; - /* Try to guess a reasonable maximum width */ longest_paragraph = width; @@ -3203,6 +3206,8 @@ gtk_label_get_desired_size (GtkExtendedLayout *layout, GtkLabelPrivate *priv = GTK_LABEL_GET_PRIVATE (layout); GtkLabel *label = GTK_LABEL (layout); PangoRectangle required_rect; + GtkWidgetAuxInfo *aux_info; + PangoLayout *natural_layout; /* * If word wrapping is on, then the height requisition can depend @@ -3222,89 +3227,99 @@ gtk_label_get_desired_size (GtkExtendedLayout *layout, gtk_label_ensure_layout (label); - if (minimum_size) + aux_info = _gtk_widget_get_aux_info (GTK_WIDGET (label), FALSE); + + pango_layout_get_extents (label->layout, NULL, &required_rect); + required_rect.x = required_rect.y = 0; + + if (label->ellipsize || priv->width_chars > 0 || priv->max_width_chars > 0) { - GtkWidgetAuxInfo *aux_info = _gtk_widget_get_aux_info (GTK_WIDGET (label), FALSE); - - pango_layout_get_extents (label->layout, NULL, &required_rect); - required_rect.x = required_rect.y = 0; - - if (label->ellipsize || priv->width_chars > 0 || priv->max_width_chars > 0) - { - /* backup the Pango layout, as get_label_char_width() scrambles it */ - - PangoLayout *backup = label->layout; - label->layout = pango_layout_copy (label->layout); - - required_rect.width = get_label_char_width (label); - - g_object_unref (label->layout); - label->layout = backup; - } - - if (label->single_line_mode) + /* backup the Pango layout, as get_label_char_width() scrambles it */ + + PangoLayout *backup = label->layout; + label->layout = pango_layout_copy (label->layout); + + required_rect.width = get_label_char_width (label); + + g_object_unref (label->layout); + label->layout = backup; + } + + if (label->single_line_mode) required_rect.height = get_single_line_height (GTK_WIDGET (label), label->layout); + + if (label->have_transform) + { + PangoContext *context = pango_layout_get_context (label->layout); + const PangoMatrix *matrix = pango_context_get_matrix (context); + pango_matrix_transform_rectangle (matrix, &required_rect); + } + + required_rect.width = PANGO_PIXELS_CEIL (required_rect.width); + required_rect.height = PANGO_PIXELS_CEIL (required_rect.height); + + if ((label->wrap || label->ellipsize || + priv->width_chars > 0 || priv->max_width_chars > 0) && + aux_info && aux_info->width > 0) + required_rect.width = aux_info->width; - if (label->have_transform) - { - PangoContext *context = pango_layout_get_context (label->layout); - const PangoMatrix *matrix = pango_context_get_matrix (context); - pango_matrix_transform_rectangle (matrix, &required_rect); - } + minimum_size->width = required_rect.width + label->misc.xpad * 2; + minimum_size->height = required_rect.height + label->misc.ypad * 2; - required_rect.width = PANGO_PIXELS_CEIL (required_rect.width); - required_rect.height = PANGO_PIXELS_CEIL (required_rect.height); + /* Natural size */ + natural_layout = pango_layout_copy (label->layout); + pango_layout_set_width (natural_layout, -1); + pango_layout_set_ellipsize (natural_layout, PANGO_ELLIPSIZE_NONE); - if ((label->wrap || label->ellipsize || - priv->width_chars > 0 || priv->max_width_chars > 0) && - aux_info && aux_info->width > 0) - required_rect.width = aux_info->width; + pango_layout_get_extents (natural_layout, NULL, &required_rect); + required_rect.x = required_rect.y = 0; - minimum_size->width = required_rect.width + label->misc.xpad * 2; - minimum_size->height = required_rect.height + label->misc.ypad * 2; + if (label->single_line_mode) + required_rect.height = get_single_line_height (GTK_WIDGET (label), label->layout); + + if (label->have_transform) + { + PangoContext *context = pango_layout_get_context (natural_layout); + const PangoMatrix *matrix = pango_context_get_matrix (context); + pango_matrix_transform_rectangle (matrix, &required_rect); } - if (natural_size) - { - PangoLayout *natural_layout = pango_layout_copy (label->layout); + required_rect.width = PANGO_PIXELS_CEIL (required_rect.width); + required_rect.height = PANGO_PIXELS_CEIL (required_rect.height); - pango_layout_set_width (natural_layout, -1); - pango_layout_set_ellipsize (natural_layout, PANGO_ELLIPSIZE_NONE); - - pango_layout_get_extents (natural_layout, NULL, &required_rect); - required_rect.x = required_rect.y = 0; - - if (label->single_line_mode) - required_rect.height = get_single_line_height (GTK_WIDGET (label), label->layout); - - if (label->have_transform) - { - PangoContext *context = pango_layout_get_context (natural_layout); - const PangoMatrix *matrix = pango_context_get_matrix (context); - pango_matrix_transform_rectangle (matrix, &required_rect); - } - - required_rect.width = PANGO_PIXELS_CEIL (required_rect.width); - required_rect.height = PANGO_PIXELS_CEIL (required_rect.height); - - natural_size->width = required_rect.width + label->misc.xpad * 2; - natural_size->height = required_rect.height + label->misc.ypad * 2; - - g_object_unref (natural_layout); - } + natural_size->width = required_rect.width + label->misc.xpad * 2; + natural_size->height = required_rect.height + label->misc.ypad * 2; + + g_object_unref (natural_layout); } static void -get_size_for_allocation (GtkLabel *label, - gint allocation, - gint *minimum_size, - gint *natural_size) +get_size_for_allocation (GtkLabel *label, + GtkOrientation orientation, + gint allocation, + gint *minimum_size, + gint *natural_size) { PangoLayout *layout; + GtkWidgetAuxInfo *aux_info = + _gtk_widget_get_aux_info (GTK_WIDGET (label), FALSE); + gint aux_size; + + if (label->wrap) + gtk_label_clear_layout (label); gtk_label_ensure_layout (label); layout = pango_layout_copy (label->layout); - pango_layout_set_width (layout, PANGO_SCALE * allocation); + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + aux_size = aux_info->width; + else + aux_size = aux_info->height; + + if (aux_size > 0) + pango_layout_set_width (layout, aux_size * PANGO_SCALE); + else + pango_layout_set_width (layout, allocation * PANGO_SCALE); if (minimum_size) pango_layout_get_pixel_size (layout, NULL, minimum_size); @@ -3328,7 +3343,7 @@ gtk_label_get_width_for_height (GtkExtendedLayout *layout, gdouble angle = gtk_label_get_angle (label); if (90 == angle || 270 == angle) - get_size_for_allocation (label, height, minimum_width, natural_width); + get_size_for_allocation (label, GTK_ORIENTATION_VERTICAL, height, minimum_width, natural_width); else { GtkRequisition minimum_size, natural_size; @@ -3354,7 +3369,7 @@ gtk_label_get_height_for_width (GtkExtendedLayout *layout, gdouble angle = gtk_label_get_angle (label); if (0 == angle || 180 == angle) - get_size_for_allocation (label, width, minimum_height, natural_height); + get_size_for_allocation (label, GTK_ORIENTATION_HORIZONTAL, width, minimum_height, natural_height); else { GtkRequisition minimum_size, natural_size; @@ -3380,21 +3395,86 @@ gtk_label_size_allocate (GtkWidget *widget, GTK_WIDGET_CLASS (gtk_label_parent_class)->size_allocate (widget, allocation); - if (label->ellipsize) + /* The layout may have been recently cleared in get_size_for_allocation(), but the + * width at that point may not be the same as the allocated width + */ + if (label->wrap) + gtk_label_clear_layout (label); + + gtk_label_ensure_layout (label); + + if (label->ellipsize || GTK_LABEL_GET_PRIVATE (label)->full_size) { if (label->layout) { - gint width; - PangoRectangle logical; + PangoRectangle logical; + PangoRectangle bounds; - width = (allocation->width - label->misc.xpad * 2) * PANGO_SCALE; + bounds.x = bounds.y = 0; + bounds.width = allocation->width - label->misc.xpad * 2; + bounds.height = allocation->height - label->misc.ypad * 2; - pango_layout_set_width (label->layout, -1); - pango_layout_get_extents (label->layout, NULL, &logical); + pango_layout_set_width (label->layout, -1); + pango_layout_get_pixel_extents (label->layout, NULL, &logical); - if (logical.width > width) - pango_layout_set_width (label->layout, width); - } + if (label->have_transform) + { + PangoContext *context = gtk_widget_get_pango_context (widget); + const PangoMatrix *matrix = pango_context_get_matrix (context); + + const gdouble dx = matrix->xx; /* cos (M_PI * angle / 180) */ + const gdouble dy = matrix->xy; /* sin (M_PI * angle / 180) */ + if (fabs (dy) < 0.01) + { + if (logical.width > bounds.width) + pango_layout_set_width (label->layout, bounds.width * PANGO_SCALE); + } + else if (fabs (dx) < 0.01) + { + if (logical.width > bounds.height) + pango_layout_set_width (label->layout, bounds.height * PANGO_SCALE); + } + else + { + gdouble x0, y0, x1, y1, length; + gboolean vertical; + gint cy; + + x0 = bounds.width / 2; + y0 = dx ? x0 * dy / dx : dy * INFINITY; + vertical = fabs (y0) > bounds.height / 2; + + if (vertical) + { + y0 = bounds.height/2; + x0 = dy ? y0 * dx / dy : dx * INFINITY; + } + + length = 2 * sqrt (x0 * x0 + y0 * y0); + pango_layout_set_width (label->layout, rint (length * PANGO_SCALE)); + pango_layout_get_pixel_size (label->layout, NULL, &cy); + + x1 = +dy * cy/2; + y1 = -dx * cy/2; + + if (vertical) + { + y0 = bounds.height/2 + y1 - y0; + x0 = -y0 * dx/dy; + } + else + { + x0 = bounds.width/2 + x1 - x0; + y0 = -x0 * dy/dx; + } + + length = length - sqrt (x0 * x0 + y0 * y0) * 2; + pango_layout_set_width (label->layout, rint (length * PANGO_SCALE)); + } + } + else if (logical.width > bounds.width) + pango_layout_set_width (label->layout, bounds.width * PANGO_SCALE); + } } if (label->select_info && label->select_info->window) @@ -3488,12 +3568,13 @@ get_layout_location (GtkLabel *label, gint *yp) { GtkMisc *misc; - GtkWidget *widget; + GtkWidget *widget; GtkLabelPrivate *priv; gfloat xalign; gint req_width, x, y; + gint req_height; PangoRectangle logical; - + misc = GTK_MISC (label); widget = GTK_WIDGET (label); priv = GTK_LABEL_GET_PRIVATE (label); @@ -3503,21 +3584,53 @@ get_layout_location (GtkLabel *label, else xalign = 1.0 - misc->xalign; - pango_layout_get_pixel_extents (label->layout, NULL, &logical); + pango_layout_get_extents (label->layout, NULL, &logical); - if (label->ellipsize || priv->width_chars > 0) + if (label->have_transform) + { + PangoContext *context = gtk_widget_get_pango_context (widget); + const PangoMatrix *matrix = pango_context_get_matrix (context); + pango_matrix_transform_rectangle (matrix, &logical); + } + + pango_extents_to_pixels (&logical, NULL); + + if (label->ellipsize || priv->width_chars > 0 || priv->full_size) { int width; width = pango_layout_get_width (label->layout); req_width = logical.width; + req_height = logical.height; + if (width != -1) req_width = MIN(PANGO_PIXELS (width), req_width); req_width += 2 * misc->xpad; + req_height += 2 * misc->ypad; + } + else if (label->wrap) + { + GtkWidgetAuxInfo *aux_info = _gtk_widget_get_aux_info (widget, FALSE); + + if (aux_info->width > 0) + req_width = aux_info->width; + else + req_width = widget->allocation.width; + + if (aux_info->height > 0) + req_height = aux_info->height; + else + req_height = widget->allocation.height; + + req_width -= 2 * misc->xpad; + req_height -= 2 * misc->ypad; } else - req_width = widget->requisition.width; + { + req_width = widget->requisition.width; + req_height = widget->requisition.height; + } x = floor (widget->allocation.x + (gint)misc->xpad + xalign * (widget->allocation.width - req_width)); @@ -3526,7 +3639,6 @@ get_layout_location (GtkLabel *label, x = MAX (x, widget->allocation.x + misc->xpad); else x = MIN (x, widget->allocation.x + widget->allocation.width - misc->xpad); - x -= logical.x; /* bgo#315462 - For single-line labels, *do* align the requisition with * respect to the allocation, even if we are under-allocated. For multi-line @@ -3543,10 +3655,10 @@ get_layout_location (GtkLabel *label, */ if (pango_layout_get_line_count (label->layout) == 1) y = floor (widget->allocation.y + (gint)misc->ypad - + (widget->allocation.height - widget->requisition.height) * misc->yalign); + + (widget->allocation.height - req_height) * misc->yalign); else y = floor (widget->allocation.y + (gint)misc->ypad - + MAX (((widget->allocation.height - widget->requisition.height) * misc->yalign), + + MAX (((widget->allocation.height - req_height) * misc->yalign), 0)); if (xp)