Showing The Cursor Position¶
In this lesson you will learn how to use GtkTextBuffer to be notified of the position of the cursor inside the text area widget, and update a label in the header bar of the text viewer application.
Add the cursor position indicator¶
Update the UI definition¶
Add a GtkLabel as the child of the AdwHeaderBar in the UI definition file for the TextViewerWindow class; the label must be packed as a child of type end, and placed after the GtkMenuButton
The label has the cursor_pos identifier that is going to be used to bind it in the TextViewerWindow template
The label has an initial content of Ln 0, Col 0 set using the label property
Additionally, the label has two style classes:
dim-label, to reduce the contrast in the default theme
numeric, which will use tabular numbers in the font used by the label
<object class="AdwHeaderBar" id="header_bar">
  <child type="start">
    <object class="GtkButton" id="open_button">
      <property name="label">Open</property>
      <property name="action-name">win.open</property>
    </object>
  </child>
  <child type="end">
    <object class="GtkMenuButton">
      <property name="primary">True</property>
      <property name="icon-name">open-menu-symbolic</property>
      <property name="tooltip-text" translatable="yes">Menu</property>
      <property name="menu-model">primary_menu</property>
    </object>
  </child>
  <child type="end">
    <object class="GtkLabel" id="cursor_pos">
      <property name="label">Ln 0, Col 0</property>
      <style>
        <class name="dim-label"/>
        <class name="numeric"/>
      </style>
    </object>
  </child>
</object>
Bind the template in your source code¶
You now must add a new member to the TextViewerWindow instance structure for the cursor_pos label:
struct _TextViewerWindow
{
  AdwApplicationWindow  parent_instance;
  /* Template widgets */
  AdwHeaderBar *header_bar;
  GtkTextView *main_text_view;
  GtkButton *open_button;
  GtkLabel *cursor_pos;
};
Bind the newly added cursor_pos widget to the template in the class initialization function
text_viewer_window_class_initof the TextViewerWindow type:
static void
text_viewer_window_class_init (TextViewerWindowClass *klass)
{
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
  gtk_widget_class_set_template_from_resource (widget_class, "/com/example/TextViewer/text_viewer-window.ui");
  gtk_widget_class_bind_template_child (widget_class, TextViewerWindow, header_bar);
  gtk_widget_class_bind_template_child (widget_class, TextViewerWindow, main_text_view);
  gtk_widget_class_bind_template_child (widget_class, TextViewerWindow, open_button);
  gtk_widget_class_bind_template_child (widget_class, TextViewerWindow, cursor_pos);
}
Add the cursor_pos widget to the TextViewerWindow class
@Gtk.Template(resource_path='/com/example/TextViewer/window.ui')
class TextViewerWindow(Adw.ApplicationWindow):
    __gtype_name__ = 'TextViewerWindow'
    main_text_view = Gtk.Template.Child()
    open_button = Gtk.Template.Child()
    cursor_pos = Gtk.Template.Child()
Add the cursor_pos widget to the TextViewer.Window class
namespace TextViewer {
    [GtkTemplate (ui = "/org/example/app/window.ui")]
    public class Window : Adw.ApplicationWindow {
        [GtkChild]
        private unowned Gtk.TextView main_text_view;
        [GtkChild]
        private unowned Gtk.Button open_button;
        [GtkChild]
        private unowned Gtk.Label cursor_pos;
        public Window (Gtk.Application app) {
            Object (application: app);
        }
        construct {
            var open_action = new SimpleAction ("open", null);
            open_action.activate.connect (this.open_file_dialog);
            this.add_action (open_action);
        }
        // ...
    }
}
Add the cursor_pos widget to the TextViewer.Window class
export const TextViewerWindow = GObject.registerClass({
    GTypeName: 'TextViewerWindow',
    Template: 'resource:///com/example/TextViewer/window.ui',
    InternalChildren: ['main_text_view', 'open_button', 'cursor_pos'],
}, class TextViewerWindow extends Adw.ApplicationWindow {
    constructor(application) {
        super({ application });
        const openAction = new Gio.SimpleAction({name: 'open'});
        openAction.connect('activate', () => this.openFileDialog());
        this.add_action(openAction);
    }
    // ...
});
Update the cursor position label¶
Retrieve the GtkTextBuffer from the main_text_view widget and connect a callback to the notify::cursor-position signal to receive a notification every time the
cursor-positionproperty changes:
static void
text_viewer_window__update_cursor_position (GtkTextBuffer *buffer,
                                            GParamSpec *pspec,
                                            TextViewerWindow *self);
static void
text_viewer_window_init (TextViewerWindow *self)
{
  gtk_widget_init_template (GTK_WIDGET (self));
  g_autoptr (GSimpleAction) open_action = g_simple_action_new ("open", NULL);
  g_signal_connect (open_action, "activate", G_CALLBACK (text_viewer_window__open), self);
  g_action_map_add_action (G_ACTION_MAP (self), G_ACTION (open_action));
  GtkTextBuffer *buffer = gtk_text_view_get_buffer (self->main_text_view);
  g_signal_connect (buffer,
                    "notify::cursor-position",
                    G_CALLBACK (text_viewer_window__update_cursor_position),
                    self);
}
@Gtk.Template(resource_path='/com/example/TextViewer/window.ui')
class TextViewerWindow(Gtk.ApplicationWindow):
    __gtype_name__ = 'TextViewerWindow'
    main_text_view = Gtk.Template.Child()
    open_button = Gtk.Template.Child()
    cursor_pos = Gtk.Template.Child()
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        open_action = Gio.SimpleAction(name="open")
        open_action.connect("activate", self.open_file_dialog)
        self.add_action(open_action)
        buffer = self.main_text_view.get_buffer()
        buffer.connect("notify::cursor-position", self.update_cursor_position)
namespace TextViewer {
    public class Window : Adw.ApplicationWindow {
        // ...
        public Window (Gtk.Application app) {
            Object (application: app);
        }
        construct {
            var open_action = new SimpleAction ("open", null);
            open_action.activate.connect (this.open_file_dialog);
            this.add_action (open_action);
            Gtk.TextBuffer buffer = this.main_text_view.buffer;
            buffer.notify["cursor-position"].connect (this.update_cursor_position);
        }
        // ...
    }
}
export const TextViewerWindow = GObject.registerClass({
    GTypeName: 'TextViewerWindow',
    Template: 'resource:///com/example/TextViewer/window.ui',
    InternalChildren: ['main_text_view', 'open_button', 'cursor_pos'],
}, class TextViewerWindow extends Adw.ApplicationWindow {
    constructor(application) {
        super({ application });
        const openAction = new Gio.SimpleAction({name: 'open'});
        openAction.connect('activate', () => this.openFileDialog());
        this.add_action(openAction);
        const buffer = this._main_text_view.buffer;
        buffer.connect(
           "notify::cursor-position",
           this.updateCursorPosition.bind(this));
    }
    // ...
});
Define the
notify::cursor-positioncallback to retrieve the position of the cursor from the GtkTextBuffer object, and update the contents of the cursor_pos label:
static void
text_viewer_window__update_cursor_position (GtkTextBuffer    *buffer,
                                            GParamSpec       *pspec G_GNUC_UNUSED,
                                            TextViewerWindow *self)
{
  int cursor_pos = 0;
  // Retrieve the value of the "cursor-position" property
  g_object_get (buffer, "cursor-position", &cursor_pos, NULL);
  // Construct the text iterator for the position of the cursor
  GtkTextIter iter;
  gtk_text_buffer_get_iter_at_offset (buffer, &iter, cursor_pos);
  // Set the new contents of the label
  g_autofree char *cursor_str =
    g_strdup_printf ("Ln %d, Col %d",
                     gtk_text_iter_get_line (&iter) + 1,
                     gtk_text_iter_get_line_offset (&iter) + 1);
  gtk_label_set_text (self->cursor_pos, cursor_str);
}
def update_cursor_position(self, buffer, _):
    # Retrieve the value of the "cursor-position" property
    cursor_pos = buffer.props.cursor_position
    # Construct the text iterator for the position of the cursor
    iter = buffer.get_iter_at_offset(cursor_pos)
    line = iter.get_line() + 1
    column = iter.get_line_offset() + 1
    # Set the new contents of the label
    self.cursor_pos.set_text(f"Ln {line}, Col {column}")
private void update_cursor_position (Object source_object, ParamSpec pspec) {
    var buffer = source_object as Gtk.TextBuffer;
    int cursor_position = buffer.cursor_position;
    Gtk.TextIter iter;
    buffer.get_iter_at_offset (out iter, cursor_position);
    this.cursor_pos.label = @"Ln $(iter.get_line ()), Col $(iter.get_line_offset ())";
}
updateCursorPosition(buffer) {
    const iterator = buffer.get_iter_at_offset(buffer.cursor_position);
    this._cursor_pos.label = `Ln ${iterator.get_line()}, Col ${iterator.get_line_offset()}`;
}
The objective of this lesson is to update the contents of a GtkLabel widget every time the position of the cursor in the GtkTextView widget changes by using the property notification mechanism provided by GObject.