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.

../../../_images/cursor_position.png

Add the cursor position indicator

Update the UI definition

  1. 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

  2. The label has the cursor_pos identifier that is going to be used to bind it in the TextViewerWindow template

  3. The label has an initial content of Ln 0, Col 0 set using the label property

  4. 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

  1. 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;
};
  1. Bind the newly added cursor_pos widget to the template in the class initialization function text_viewer_window_class_init of 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);
}

Update the cursor position label

  1. 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-position property 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);
}
  1. Define the notify::cursor-position callback 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);
}

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.