summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Clasen <mclasen@redhat.com>2021-03-02 22:30:11 -0500
committerMatthias Clasen <mclasen@redhat.com>2021-03-02 23:25:01 -0500
commit875a92b95f12dadd618c2257057fa6622ef90fa4 (patch)
treec259e004cf13bb64e777f26e8b46b36d523f7f73
parentc23848c03edfe957c87a4c145d6f073d7d43adde (diff)
downloadgtk+-label-mnemonic-fix.tar.gz
label: Fix markup parsing interactionslabel-mnemonic-fix
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
-rw-r--r--gtk/gtklabel.c243
-rw-r--r--testsuite/gtk/label.c207
-rw-r--r--testsuite/gtk/meson.build1
3 files changed, 309 insertions, 142 deletions
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 <gtk/gtk.h>
+
+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), "<a href=\"test\"><span font_style=\"italic\">abc</span> _def</a>");
+
+ 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' },