Probably not what you’re looking to hear, but that’s outdated Gtk 3 API that’s no longer present in Gtk 4. The new API is built around cursor positioning using iters, so there are no x/y offsets to confuse things. And because iters are invalidated by buffer manipulations, the cursor can also be represented within the GtkTextBuffer
itself as a GtkTextMark
, a particular kind of placeholder markup. The Gtk 4 Text Widget Overview explains:
Most text manipulation is accomplished with iterators, represented by a GtkTextIter
. An iterator represents a position between two characters in the text buffer. GtkTextIter
is a struct designed to be allocated on the stack; it’s guaranteed to be copiable by value and never contain any heap-allocated data. Iterators are not valid indefinitely; whenever the buffer is modified in a way that affects the number of characters in the buffer, all outstanding iterators become invalid. (Note that deleting 5 characters and then reinserting 5 still invalidates iterators, though you end up with the same number of characters you pass through a state with a different number).
Because of this, iterators can’t be used to preserve positions across buffer modifications. To preserve a position, the GtkTextMark
object is ideal. You can think of a mark as an invisible cursor or insertion point; it floats in the buffer, saving a position. If the text surrounding the mark is deleted, the mark remains in the position the text once occupied; if text is inserted at the mark, the mark ends up either to the left or to the right of the new text, depending on its gravity. The standard text cursor in left-to-right languages is a mark with right gravity, because it stays to the right of inserted text.
Like tags, marks can be either named or anonymous. There are two marks built-in to GtkTextBuffer
; these are named “insert” and “selection_bound” and refer to the insertion point and the boundary of the selection which is not the insertion point, respectively. If no text is selected, these two marks will be in the same position. You can manipulate what is selected and where the cursor appears by moving these marks around.
If you want to place the cursor in response to a user action, be sure to use gtk_text_buffer_place_cursor()
, which moves both at once without causing a temporary selection (moving one then the other temporarily selects the range in between the old and new positions).
It also mentions, at the end of the page:
The gtk4-demo
application that comes with GTK contains more example code for GtkTextView
.
…That being said, the gtk3-demo
application has example GtkTextView
code as well, and it seems to follow a similar pattern. It never once calls gtk_render_insert_cursor()
— I don’t think you’re meant to be doing that, as the application. What it does do is query the cursor position from the buffer, using similar principles to the Gtk 4 overview:
(The mark_set_callback
is bound to the GtkTextBuffer
’s mark-set
signal, in the UI definition. It will report cursor motions performed by the user; the widget itself handles both the moving and the drawing.)
static void
mark_set_callback (GtkTextBuffer *buffer,
const GtkTextIter *new_location,
GtkTextMark *mark,
DemoApplicationWindow *window)
{
The buffer’s changed
signal is also bound directly to update_statusbar
.
static void
update_statusbar (GtkTextBuffer *buffer,
DemoApplicationWindow *window)
{
gchar *msg;
gint row, col;
gint count;
GtkTextIter iter;
/* clear any previous message, underflow is allowed */
gtk_statusbar_pop (GTK_STATUSBAR (window->status), 0);
count = gtk_text_buffer_get_char_count (buffer);
gtk_text_buffer_get_iter_at_mark (buffer,
&iter,
gtk_text_buffer_get_insert (buffer));
row = gtk_text_iter_get_line (&iter);
col = gtk_text_iter_get_line_offset (&iter);
msg = g_strdup_printf ("Cursor at row %d column %d - %d chars in document",
row, col, count);
gtk_statusbar_push (GTK_STATUSBAR (window->status), 0, msg);
g_free (msg);
}
The application.ui
code that creates the GtkTextView
and its buffer, omitting the tool, menu, and statusbars:
<?xml version="1.0"?>
<interface>
<template class="DemoApplicationWindow" parent="GtkApplicationWindow">
<property name="title" translatable="yes">Application Class</property>
<property name="default-width">200</property>
<property name="default-height">200</property>
<property name="icon-name">document-open</property>
<child>
<object class="GtkGrid">
<property name="visible">1</property>
<child>
<object class="GtkScrolledWindow">
<property name="visible">1</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTextView">
<property name="visible">1</property>
<property name="hexpand">1</property>
<property name="vexpand">1</property>
<property name="buffer">buffer</property>
</object>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">2</property>
</packing>
</child>
</object>
</child>
</template>
<object class="GtkTextBuffer" id="buffer">
<signal name="changed" handler="update_statusbar"/>
<signal name="mark-set" handler="mark_set_callback"/>
</object>