From c517e945de399488b5c7577a25617a62bc2cb92a Mon Sep 17 00:00:00 2001 From: Georg Vienna Date: Fri, 3 Dec 2021 16:02:48 +0000 Subject: [PATCH] textchildanchor: allow to specify replacement character --- demos/gtk-demo/hypertext.c | 10 ++++-- gtk/gtktextbtree.c | 35 +++++++++++++++++-- gtk/gtktextchild.c | 33 +++++++++++++----- gtk/gtktextchild.h | 14 +++++--- gtk/gtktextiter.c | 4 +++ gtk/gtktextlayout.c | 2 +- testsuite/gtk/textbuffer.c | 71 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 150 insertions(+), 19 deletions(-) diff --git a/demos/gtk-demo/hypertext.c b/demos/gtk-demo/hypertext.c index b38f474c33..e47f691f53 100644 --- a/demos/gtk-demo/hypertext.c +++ b/demos/gtk-demo/hypertext.c @@ -7,7 +7,8 @@ * shows. * * We also demonstrate adding other things to a text view, such as - * clickable icons. + * clickable icons and widgets which can also replace a character + * (try copying the ghost text). */ #include @@ -113,7 +114,12 @@ show_page (GtkTextView *text_view, gtk_level_bar_set_value (GTK_LEVEL_BAR (child), 50); gtk_widget_set_size_request (child, 100, -1); gtk_text_view_add_child_at_anchor (text_view, child, anchor); - gtk_text_buffer_insert (buffer, &iter, ".", -1); + gtk_text_buffer_insert (buffer, &iter, " and labels with ", -1); + anchor = gtk_text_child_anchor_new_with_replacement ("👻"); + gtk_text_buffer_insert_child_anchor (buffer, &iter, anchor); + child = gtk_label_new ("ghost"); + gtk_text_view_add_child_at_anchor (text_view, child, anchor); + gtk_text_buffer_insert (buffer, &iter, " text.", -1); } else if (page == 2) { diff --git a/gtk/gtktextbtree.c b/gtk/gtktextbtree.c index 91ab3351a8..d11d4300c7 100644 --- a/gtk/gtktextbtree.c +++ b/gtk/gtktextbtree.c @@ -2362,8 +2362,7 @@ copy_segment (GString *string, /* printf (" :%s\n", string->str); */ } - else if (seg->type == >k_text_paintable_type || - seg->type == >k_text_child_type) + else if (seg->type == >k_text_paintable_type) { gboolean copy = TRUE; @@ -2382,7 +2381,27 @@ copy_segment (GString *string, g_string_append_len (string, _gtk_text_unknown_char_utf8, GTK_TEXT_UNKNOWN_CHAR_UTF8_LEN); + } + } + else if (seg->type == >k_text_child_type) + { + gboolean copy = TRUE; + if (!include_nonchars && + g_strcmp0 (_gtk_text_unknown_char_utf8, seg->body.child.obj->chars) == 0) + { + copy = FALSE; + } + else if (!include_hidden && + _gtk_text_btree_char_is_invisible (start)) + { + copy = FALSE; + } + if (copy) + { + g_string_append_len (string, + seg->body.child.obj->chars, + seg->byte_count); } } } @@ -7121,6 +7140,12 @@ _gtk_text_btree_spew_line_short (GtkTextLine *line, int indent) printf ("%s chars '%s'...\n", spaces, str); g_free (str); } + else if (seg->type == >k_text_child_type) + { + char *str = g_strndup (seg->body.child.obj->chars, seg->byte_count); + printf ("%s child '%s'...\n", spaces, str); + g_free (str); + } else if (seg->type == >k_text_right_mark_type) { printf ("%s right mark '%s' visible: %d\n", @@ -7223,6 +7248,12 @@ _gtk_text_btree_spew_segment (GtkTextBTree* tree, GtkTextLineSegment * seg) printf (" '%s'\n", str); g_free (str); } + else if (seg->type == >k_text_child_type) + { + char *str = g_strndup (seg->body.child.obj->chars, seg->byte_count); + printf (" '%s'\n", str); + g_free (str); + } else if (seg->type == >k_text_right_mark_type) { printf (" right mark '%s' visible: %d not_deleteable: %d\n", diff --git a/gtk/gtktextchild.c b/gtk/gtktextchild.c index f1716efb75..2976550301 100644 --- a/gtk/gtktextchild.c +++ b/gtk/gtktextchild.c @@ -267,9 +267,6 @@ child_segment_check_func (GtkTextLineSegment *seg, if (seg->next == NULL) g_error ("child segment is the last segment in a line"); - if (seg->byte_count != GTK_TEXT_UNKNOWN_CHAR_UTF8_LEN) - g_error ("child segment has byte count of %d", seg->byte_count); - if (seg->char_count != 1) g_error ("child segment has char count of %d", seg->char_count); } @@ -301,11 +298,8 @@ _gtk_widget_segment_new (GtkTextChildAnchor *anchor) seg->next = NULL; - /* We convert to the 0xFFFC "unknown character", - * a 3-byte sequence in UTF-8. - */ - seg->byte_count = GTK_TEXT_UNKNOWN_CHAR_UTF8_LEN; - seg->char_count = 1; + seg->byte_count = strlen (anchor->chars); + seg->char_count = g_utf8_strlen (anchor->chars, seg->byte_count); seg->body.child.obj = anchor; seg->body.child.obj->segment = seg; @@ -410,7 +404,28 @@ gtk_text_child_anchor_class_init (GtkTextChildAnchorClass *klass) GtkTextChildAnchor* gtk_text_child_anchor_new (void) { - return g_object_new (GTK_TYPE_TEXT_CHILD_ANCHOR, NULL); + return gtk_text_child_anchor_new_with_replacement (_gtk_text_unknown_char_utf8); +} + +/** + * gtk_text_child_anchor_new_with_replacement: + * + * Creates a new `GtkTextChildAnchor` with the given replacement character. + * + * Usually you would then insert it into a `GtkTextBuffer` with + * [method@Gtk.TextBuffer.insert_child_anchor]. + * + * Returns: a new `GtkTextChildAnchor` + **/ +GtkTextChildAnchor * +gtk_text_child_anchor_new_with_replacement (const char *replacement_character) +{ + /* only a single character can be set as replacement */ + g_return_val_if_fail (g_utf8_strlen (replacement_character, -1) == 1, NULL); + + GtkTextChildAnchor *anchor = g_object_new (GTK_TYPE_TEXT_CHILD_ANCHOR, NULL); + anchor->chars = g_strdup (replacement_character); + return anchor; } static void diff --git a/gtk/gtktextchild.h b/gtk/gtktextchild.h index 65432262a1..31e8324c59 100644 --- a/gtk/gtktextchild.h +++ b/gtk/gtktextchild.h @@ -60,6 +60,7 @@ struct _GtkTextChildAnchor /*< private >*/ gpointer segment; + char *chars; /* replacement character */ }; struct _GtkTextChildAnchorClass @@ -74,16 +75,19 @@ struct _GtkTextChildAnchorClass }; GDK_AVAILABLE_IN_ALL -GType gtk_text_child_anchor_get_type (void) G_GNUC_CONST; +GType gtk_text_child_anchor_get_type (void) G_GNUC_CONST; GDK_AVAILABLE_IN_ALL -GtkTextChildAnchor* gtk_text_child_anchor_new (void); +GtkTextChildAnchor *gtk_text_child_anchor_new (void); + +GDK_AVAILABLE_IN_4_6 +GtkTextChildAnchor *gtk_text_child_anchor_new_with_replacement (const char *character); GDK_AVAILABLE_IN_ALL -GtkWidget ** gtk_text_child_anchor_get_widgets (GtkTextChildAnchor *anchor, - guint *out_len); +GtkWidget **gtk_text_child_anchor_get_widgets (GtkTextChildAnchor *anchor, + guint *out_len); GDK_AVAILABLE_IN_ALL -gboolean gtk_text_child_anchor_get_deleted (GtkTextChildAnchor *anchor); +gboolean gtk_text_child_anchor_get_deleted (GtkTextChildAnchor *anchor); G_END_DECLS diff --git a/gtk/gtktextiter.c b/gtk/gtktextiter.c index 7157233641..a9e79ea51f 100644 --- a/gtk/gtktextiter.c +++ b/gtk/gtktextiter.c @@ -892,6 +892,10 @@ gtk_text_iter_get_char (const GtkTextIter *iter) return g_utf8_get_char (real->segment->body.chars + real->segment_byte_offset); } + else if (real->segment->type == >k_text_child_type) + { + return g_utf8_get_char (real->segment->body.child.obj->chars); + } else { /* Unicode "unknown character" 0xFFFC */ diff --git a/gtk/gtktextlayout.c b/gtk/gtktextlayout.c index 1873b03d74..b57cd65e50 100644 --- a/gtk/gtktextlayout.c +++ b/gtk/gtktextlayout.c @@ -2470,7 +2470,7 @@ gtk_text_layout_create_display (GtkTextLayout *layout, size_only, FALSE); add_child_attrs (layout, display, style, seg, attrs, layout_byte_offset); - memcpy (text + layout_byte_offset, _gtk_text_unknown_char_utf8, + memcpy (text + layout_byte_offset, seg->body.child.obj->chars, seg->byte_count); layout_byte_offset += seg->byte_count; buffer_byte_offset += seg->byte_count; diff --git a/testsuite/gtk/textbuffer.c b/testsuite/gtk/textbuffer.c index 3ac7643a6a..2dcb0b5fd7 100644 --- a/testsuite/gtk/textbuffer.c +++ b/testsuite/gtk/textbuffer.c @@ -1581,6 +1581,75 @@ test_get_iter (void) g_object_unref (buffer); } +static void +test_iter_with_anchor (void) +{ + GtkTextView *text_view; + GtkTextBuffer *buffer; + GtkTextIter iter; + GtkTextChildAnchor *anchor; + GtkWidget *child; + + text_view = GTK_TEXT_VIEW (gtk_text_view_new ()); + buffer = gtk_text_view_get_buffer (text_view); + + gtk_text_buffer_set_text (buffer, "ab\ncd\r\nef", -1); + g_assert_true (gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 0, 1)); + anchor = gtk_text_buffer_create_child_anchor (buffer, &iter); + child = gtk_label_new ("text"); + gtk_text_view_add_child_at_anchor (text_view, child, anchor); + g_object_unref (child); + + gtk_text_buffer_get_iter_at_child_anchor (buffer, &iter, anchor); + g_assert_cmpint (gtk_text_iter_get_char (&iter), ==, GTK_TEXT_UNKNOWN_CHAR); + + g_assert_true (gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 1, 1)); + /* ß takes 2 bytes in UTF-8 */ + anchor = gtk_text_child_anchor_new_with_replacement ("ß"); + gtk_text_buffer_insert_child_anchor (buffer, &iter, anchor); + child = gtk_label_new ("text"); + gtk_text_view_add_child_at_anchor (text_view, child, anchor); + + gtk_text_buffer_get_iter_at_child_anchor (buffer, &iter, anchor); + g_assert_cmpint (gtk_text_iter_get_char (&iter), ==, 223); + + g_object_unref (child); + g_object_ref_sink (text_view); +} + +static void +test_get_text_with_anchor (void) +{ + GtkTextView *text_view; + GtkTextBuffer *buffer; + GtkTextIter iter, start, end; + GtkTextChildAnchor *anchor; + GtkWidget *child; + + text_view = GTK_TEXT_VIEW (gtk_text_view_new ()); + buffer = gtk_text_view_get_buffer (text_view); + + gtk_text_buffer_set_text (buffer, "ab\ncd\r\nef", -1); + g_assert_true (gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 0, 1)); + anchor = gtk_text_buffer_create_child_anchor (buffer, &iter); + child = gtk_label_new ("text"); + gtk_text_view_add_child_at_anchor (text_view, child, anchor); + g_object_unref (child); + + g_assert_true (gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, 1, 1)); + /* ß takes 2 bytes in UTF-8 */ + anchor = gtk_text_child_anchor_new_with_replacement ("ß"); + gtk_text_buffer_insert_child_anchor (buffer, &iter, anchor); + child = gtk_label_new ("text"); + gtk_text_view_add_child_at_anchor (text_view, child, anchor); + + gtk_text_buffer_get_bounds (buffer, &start, &end); + g_assert_cmpstr (gtk_text_buffer_get_text (buffer, &start, &end, FALSE), ==, "ab\ncßd\r\nef"); + + g_object_unref (child); + g_object_ref_sink (text_view); +} + /* Check that basic undo works */ static void test_undo0 (void) @@ -1768,6 +1837,8 @@ main (int argc, char** argv) g_test_add_func ("/TextBuffer/Tag", test_tag); g_test_add_func ("/TextBuffer/Clipboard", test_clipboard); g_test_add_func ("/TextBuffer/Get iter", test_get_iter); + g_test_add_func ("/TextBuffer/Iter with anchor", test_iter_with_anchor); + g_test_add_func ("/TextBuffer/Get text with anchor", test_get_text_with_anchor); g_test_add_func ("/TextBuffer/Undo 0", test_undo0); g_test_add_func ("/TextBuffer/Undo 1", test_undo1); g_test_add_func ("/TextBuffer/Undo 2", test_undo2);