From 875a92b95f12dadd618c2257057fa6622ef90fa4 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 2 Mar 2021 22:30:11 -0500 Subject: label: Fix markup parsing interactions We were not handling mnemonics vs markup right in all cases. Rewrite the _-stripping code to do it during the link parsing, instead of as a separate function. This avoids the issue of stripping _ from attribute names in markup. Add tests. Fixes: 3706 --- gtk/gtklabel.c | 243 +++++++++++++++++++--------------------------- testsuite/gtk/label.c | 207 +++++++++++++++++++++++++++++++++++++++ testsuite/gtk/meson.build | 1 + 3 files changed, 309 insertions(+), 142 deletions(-) create mode 100644 testsuite/gtk/label.c diff --git a/gtk/gtklabel.c b/gtk/gtklabel.c index e6ca3a631c..fb382d2a1c 100644 --- a/gtk/gtklabel.c +++ b/gtk/gtklabel.c @@ -3078,8 +3078,77 @@ typedef struct GArray *links; GString *new_str; gsize text_len; + gboolean strip_ulines; + GString *text_data; } UriParserData; +static char * +strip_ulines (const char *text) +{ + char *new_text; + const char *p; + char *q; + gboolean after_uline = FALSE; + + new_text = malloc (strlen (text) + 1); + + q = new_text; + for (p = text; *p; p++) + { + if (*p == '_' && !after_uline) + { + after_uline = TRUE; + continue; + } + + *q = *p; + q++; + after_uline = FALSE; + } + + if (after_uline) + { + *q = '_'; + q++; + } + + *q = '\0'; + + return new_text; +} + +static void +finish_text (UriParserData *pdata) +{ + if (pdata->text_data->len > 0) + { + char *text; + gsize text_len; + char *newtext; + + if (pdata->strip_ulines && strchr (pdata->text_data->str, '_')) + { + text = strip_ulines (pdata->text_data->str); + text_len = strlen (text); + } + else + { + text = pdata->text_data->str; + text_len = pdata->text_data->len; + } + + newtext = g_markup_escape_text (text, text_len); + g_string_append (pdata->new_str, newtext); + pdata->text_len += text_len; + g_free (newtext); + + if (text != pdata->text_data->str) + g_free (text); + + g_string_set_size (pdata->text_data, 0); + } +} + static void start_element_handler (GMarkupParseContext *context, const char *element_name, @@ -3091,6 +3160,8 @@ start_element_handler (GMarkupParseContext *context, UriParserData *pdata = user_data; GtkLabel *self = pdata->label; + finish_text (pdata); + if (strcmp (element_name, "a") == 0) { GtkLabelLink link; @@ -3214,6 +3285,8 @@ end_element_handler (GMarkupParseContext *context, { UriParserData *pdata = user_data; + finish_text (pdata); + if (!strcmp (element_name, "a")) { GtkLabelLink *link = &g_array_index (pdata->links, GtkLabelLink, pdata->links->len - 1); @@ -3235,12 +3308,8 @@ text_handler (GMarkupParseContext *context, GError **error) { UriParserData *pdata = user_data; - char *newtext; - newtext = g_markup_escape_text (text, text_len); - g_string_append (pdata->new_str, newtext); - pdata->text_len += text_len; - g_free (newtext); + g_string_append_len (pdata->text_data, text, text_len); } static const GMarkupParser markup_parser = @@ -3261,8 +3330,9 @@ xml_isspace (char c) static gboolean parse_uri_markup (GtkLabel *self, const char *str, + gboolean strip_ulines, char **new_str, - GtkLabelLink **links, + GtkLabelLink **links, guint *out_n_links, GError **error) { @@ -3279,6 +3349,8 @@ parse_uri_markup (GtkLabel *self, pdata.links = NULL; pdata.new_str = g_string_sized_new (length); pdata.text_len = 0; + pdata.strip_ulines = strip_ulines; + pdata.text_data = g_string_new (""); while (p != end && xml_isspace (*p)) p++; @@ -3307,6 +3379,8 @@ parse_uri_markup (GtkLabel *self, g_markup_parse_context_free (context); + g_string_free (pdata.text_data, TRUE); + *new_str = g_string_free (pdata.new_str, FALSE); if (pdata.links) @@ -3351,83 +3425,10 @@ gtk_label_ensure_has_tooltip (GtkLabel *self) gtk_widget_set_has_tooltip (GTK_WIDGET (self), has_tooltip); } -/* Reads @text and extracts the accel key, if any. - * @new_text will be set to the given text with the first _ removed. - * - * Returned will be the one underline attribute used for the mnemonic - * */ -static void -extract_mnemonic_keyval (const char *text, - guint *out_accel_key, - char **out_new_text, - PangoAttribute **out_mnemonic_attribute) -{ - const gsize text_len = strlen (text); - gunichar c; - const char *p; - - p = text; - for (;;) - { - const char *_index; - - c = g_utf8_get_char (p); - - if (c == '\0') - break; - - if (c != '_') - { - p = g_utf8_next_char (p); - continue; - } - - _index = p; - - p = g_utf8_next_char (p); - c = g_utf8_get_char (p); - - if (c != '_' && c != '\0') - { - const gsize byte_index = p - text - 1; /* Of the _ */ - - /* c is the accel key */ - if (out_accel_key) - *out_accel_key = gdk_keyval_to_lower (gdk_unicode_to_keyval (c)); - if (out_new_text) - { - *out_new_text = g_malloc (text_len); - memcpy (*out_new_text, text, byte_index); - memcpy (*out_new_text + byte_index, p, text_len - byte_index); - } - - if (out_mnemonic_attribute) - { - PangoAttribute *attr = pango_attr_underline_new (PANGO_UNDERLINE_LOW); - attr->start_index = _index - text; - attr->end_index = p - text; - *out_mnemonic_attribute = attr; - } - - return; - } - - p = g_utf8_next_char (p); - } - - /* No accel key found */ - if (out_accel_key) - *out_accel_key = GDK_KEY_VoidSymbol; - if (out_new_text) - *out_new_text = NULL; - if (out_mnemonic_attribute) - *out_mnemonic_attribute = NULL; -} - static void -gtk_label_set_markup_internal (GtkLabel *self, +gtk_label_set_markup_internal (GtkLabel *self, const char *str, - gboolean with_uline) + gboolean with_uline) { char *text = NULL; GError *error = NULL; @@ -3435,9 +3436,18 @@ gtk_label_set_markup_internal (GtkLabel *self, char *str_for_display = NULL; GtkLabelLink *links = NULL; guint n_links = 0; - PangoAttribute *mnemonic_attr = NULL; - - if (!parse_uri_markup (self, str, &str_for_display, &links, &n_links, &error)) + guint accel_keyval = 0; + gboolean do_mnemonics; + + do_mnemonics = self->mnemonics_visible && + gtk_widget_is_sensitive (GTK_WIDGET (self)) && + (!self->mnemonic_widget || gtk_widget_is_sensitive (self->mnemonic_widget)); + + if (!parse_uri_markup (self, str, + with_uline && !do_mnemonics, + &str_for_display, + &links, &n_links, + &error)) goto error_set; if (links) @@ -3449,62 +3459,12 @@ gtk_label_set_markup_internal (GtkLabel *self, gtk_widget_add_css_class (GTK_WIDGET (self), "link"); } - if (!with_uline) - { -no_uline: - /* Extract the text to display */ - if (!pango_parse_markup (str_for_display, -1, 0, &attrs, &text, NULL, &error)) - goto error_set; - } - else /* Underline AND markup is a little more complicated... */ - { - char *new_text = NULL; - guint accel_keyval; - gboolean auto_mnemonics = TRUE; - gboolean do_mnemonics = self->mnemonics_visible && - (!auto_mnemonics || gtk_widget_is_sensitive (GTK_WIDGET (self))) && - (!self->mnemonic_widget || gtk_widget_is_sensitive (self->mnemonic_widget)); - - /* Remove the mnemonic underline */ - extract_mnemonic_keyval (str_for_display, - &accel_keyval, - &new_text, - NULL); - if (!new_text) /* No underline found anyway */ - goto no_uline; - - self->mnemonic_keyval = accel_keyval; - - /* Extract the text to display */ - if (!pango_parse_markup (new_text, -1, '_', - do_mnemonics ? &attrs : NULL, &text, NULL, &error)) - { - g_free (new_text); - goto error_set; - } - - if (do_mnemonics) - { - /* text is now the final text, but we need to parse str_for_display once again - * *with* the mnemonic underline so we can remove the markup tags and get the - * proper attribute indices */ - char *text_for_accel; - - if (!pango_parse_markup (str_for_display, -1, 0, NULL, &text_for_accel, NULL, &error)) - { - g_free (new_text); - goto error_set; - } - - extract_mnemonic_keyval (text_for_accel, - NULL, - NULL, - &mnemonic_attr); - g_free (text_for_accel); - } - - g_free (new_text); - } + if (!pango_parse_markup (str_for_display, -1, + with_uline && do_mnemonics ? '_' : 0, + &attrs, &text, + with_uline && do_mnemonics ? &accel_keyval : NULL, + &error)) + goto error_set; g_free (str_for_display); @@ -3514,8 +3474,7 @@ no_uline: g_clear_pointer (&self->markup_attrs, pango_attr_list_unref); self->markup_attrs = attrs; - if (mnemonic_attr) - pango_attr_list_insert_before (self->markup_attrs, mnemonic_attr); + self->mnemonic_keyval = accel_keyval; return; diff --git a/testsuite/gtk/label.c b/testsuite/gtk/label.c new file mode 100644 index 0000000000..11f1eebf23 --- /dev/null +++ b/testsuite/gtk/label.c @@ -0,0 +1,207 @@ +#include + +void +print_attribute (PangoAttribute *attr, GString *string) +{ + GEnumClass *class; + GEnumValue *value; + + g_string_append_printf (string, "[%d,%d]", attr->start_index, attr->end_index); + + class = g_type_class_ref (pango_attr_type_get_type ()); + value = g_enum_get_value (class, attr->klass->type); + g_string_append_printf (string, "%s=", value->value_nick); + g_type_class_unref (class); + + switch (attr->klass->type) + { + case PANGO_ATTR_LANGUAGE: + g_string_append (string, pango_language_to_string (((PangoAttrLanguage *)attr)->value)); + break; + case PANGO_ATTR_FAMILY: + case PANGO_ATTR_FONT_FEATURES: + g_string_append (string, ((PangoAttrString *)attr)->value); + break; + case PANGO_ATTR_STYLE: + case PANGO_ATTR_WEIGHT: + case PANGO_ATTR_VARIANT: + case PANGO_ATTR_STRETCH: + case PANGO_ATTR_SIZE: + case PANGO_ATTR_ABSOLUTE_SIZE: + case PANGO_ATTR_UNDERLINE: + case PANGO_ATTR_OVERLINE: + case PANGO_ATTR_STRIKETHROUGH: + case PANGO_ATTR_RISE: + case PANGO_ATTR_FALLBACK: + case PANGO_ATTR_LETTER_SPACING: + case PANGO_ATTR_GRAVITY: + case PANGO_ATTR_GRAVITY_HINT: + case PANGO_ATTR_FOREGROUND_ALPHA: + case PANGO_ATTR_BACKGROUND_ALPHA: + case PANGO_ATTR_ALLOW_BREAKS: + case PANGO_ATTR_INSERT_HYPHENS: + case PANGO_ATTR_SHOW: + g_string_append_printf (string, "%d", ((PangoAttrInt *)attr)->value); + break; + case PANGO_ATTR_FONT_DESC: + { + char *text = pango_font_description_to_string (((PangoAttrFontDesc *)attr)->desc); + g_string_append (string, text); + g_free (text); + } + break; + case PANGO_ATTR_FOREGROUND: + case PANGO_ATTR_BACKGROUND: + case PANGO_ATTR_UNDERLINE_COLOR: + case PANGO_ATTR_OVERLINE_COLOR: + case PANGO_ATTR_STRIKETHROUGH_COLOR: + { + char *text = pango_color_to_string (&((PangoAttrColor *)attr)->color); + g_string_append (string, text); + g_free (text); + } + break; + case PANGO_ATTR_SHAPE: + g_string_append_printf (string, "shape"); + break; + case PANGO_ATTR_SCALE: + { + char val[20]; + + g_ascii_formatd (val, 20, "%f", ((PangoAttrFloat *)attr)->value); + g_string_append (string, val); + } + break; + case PANGO_ATTR_INVALID: + default: + g_assert_not_reached (); + break; + } +} + +void +print_attr_list (PangoAttrList *attrs, GString *string) +{ + PangoAttrIterator *iter; + + if (!attrs) + return; + + iter = pango_attr_list_get_iterator (attrs); + do { + gint start, end; + GSList *list, *l; + + pango_attr_iterator_range (iter, &start, &end); + g_string_append_printf (string, "range %d %d\n", start, end); + list = pango_attr_iterator_get_attrs (iter); + for (l = list; l; l = l->next) + { + PangoAttribute *attr = l->data; + print_attribute (attr, string); + g_string_append (string, "\n"); + } + g_slist_free_full (list, (GDestroyNotify)pango_attribute_destroy); + } while (pango_attr_iterator_next (iter)); + + pango_attr_iterator_destroy (iter); +} + +static void +test_label_markup (void) +{ + GtkWidget *window; + GtkWidget *label; + PangoAttrList *attrs; + GString *str; + const char *text; + + window = gtk_window_new (); + label = gtk_label_new (""); + + gtk_window_set_child (GTK_WINDOW (window), label); + gtk_window_set_mnemonics_visible (GTK_WINDOW (window), TRUE); + + gtk_label_set_use_underline (GTK_LABEL (label), TRUE); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + gtk_label_set_label (GTK_LABEL (label), "abc _def"); + + g_assert_cmpuint (gtk_label_get_mnemonic_keyval (GTK_LABEL (label)), ==, 'd'); + + text = pango_layout_get_text (gtk_label_get_layout (GTK_LABEL (label))); + g_assert_cmpstr (text, ==, "abc def"); + + attrs = pango_layout_get_attributes (gtk_label_get_layout (GTK_LABEL (label))); + str = g_string_new (""); + print_attr_list (attrs, str); + + g_assert_cmpstr (str->str, ==, + "range 0 3\n" + "[0,4]underline=1\n" + "[0,8]foreground=#1b1b6a6acbcb\n" + "[0,3]style=2\n" + "range 3 4\n" + "[0,4]underline=1\n" + "[0,8]foreground=#1b1b6a6acbcb\n" + "range 4 5\n" + "[0,8]foreground=#1b1b6a6acbcb\n" + "[4,5]underline=3\n" + "range 5 8\n" + "[0,8]foreground=#1b1b6a6acbcb\n" + "[5,8]underline=1\n" + "range 8 2147483647\n"); + + + gtk_window_set_mnemonics_visible (GTK_WINDOW (window), FALSE); + + text = pango_layout_get_text (gtk_label_get_layout (GTK_LABEL (label))); + g_assert_cmpstr (text, ==, "abc def"); + + attrs = pango_layout_get_attributes (gtk_label_get_layout (GTK_LABEL (label))); + g_string_set_size (str, 0); + print_attr_list (attrs, str); + + g_assert_cmpstr (str->str, ==, + "range 0 3\n" + "[0,7]underline=1\n" + "[0,7]foreground=#1b1b6a6acbcb\n" + "[0,3]style=2\n" + "range 3 7\n" + "[0,7]underline=1\n" + "[0,7]foreground=#1b1b6a6acbcb\n" + "range 7 2147483647\n"); + + gtk_window_set_mnemonics_visible (GTK_WINDOW (window), TRUE); + gtk_label_set_use_underline (GTK_LABEL (label), FALSE); + + text = pango_layout_get_text (gtk_label_get_layout (GTK_LABEL (label))); + g_assert_cmpstr (text, ==, "abc _def"); + + attrs = pango_layout_get_attributes (gtk_label_get_layout (GTK_LABEL (label))); + g_string_set_size (str, 0); + print_attr_list (attrs, str); + + g_assert_cmpstr (str->str, ==, + "range 0 3\n" + "[0,8]underline=1\n" + "[0,8]foreground=#1b1b6a6acbcb\n" + "[0,3]style=2\n" + "range 3 8\n" + "[0,8]underline=1\n" + "[0,8]foreground=#1b1b6a6acbcb\n" + "range 8 2147483647\n"); + + g_string_free (str, TRUE); + + gtk_window_destroy (GTK_WINDOW (window)); +} + +int +main (int argc, char *argv[]) +{ + gtk_test_init (&argc, &argv); + + g_test_add_func ("/label/markup-parse", test_label_markup); + + return g_test_run (); +} diff --git a/testsuite/gtk/meson.build b/testsuite/gtk/meson.build index b0af6dc642..4e04908485 100644 --- a/testsuite/gtk/meson.build +++ b/testsuite/gtk/meson.build @@ -50,6 +50,7 @@ tests = [ { 'name': 'grid' }, { 'name': 'grid-layout' }, { 'name': 'icontheme' }, + { 'name': 'label' }, { 'name': 'listbox' }, { 'name': 'main' }, { 'name': 'maplistmodel' }, -- cgit v1.2.1