7. Buffer and Window Coordinates

Sometimes it is necessary to know the position of the text cursor on the screen, or the word in a buffer under the mouse cursor. For example, when you want to display the prototype of a function as a tooltip, when the user types open parenthesis. To do this, you will have to understand buffer coordinates and window coordinates.

Both the buffer and window coordinates are pixel level coordinates. The difference is that the window coordinates takes into account only the portion of the buffer displayed on the screen.

The concept would be better explained using a diagram. The large white box(with grid lines) in the following diagram depicts the text buffer. And the smaller inner grey box is the visible portion of the text buffer, displayed by the text view widget.

The buffer coordinates of the red dot(represented as (x, y)) is (4, 3). But the window coordinates of the red dot is (2, 1). This is because the window coordinates are calculated relative the visible portion of the text buffer. Similarly, the buffer coordinates of the blue dot is (3, 5) and the window coordinates is (1, 3).

7.1. Tooltips under Text Cursor

In this section, you will learn how to display tooltips under the text cursor. The procedure is as follows,

The buffer coordinates of a particular character in a buffer can be obtained using


void gtk_text_view_get_iter_location( GtkTextView *text_view,
                                      const GtkTextIter *iter,
                                      GdkRectangle *location );
The function gets the rectangle that contains the character at iter and store it in location. The x and y members of location gives us the buffer coordinates.

The buffer coordinates can be converted into window coordinates using


void gtk_text_view_buffer_to_window_coords( GtkTextView       *text_view,
                                            GtkTextWindowType  win,
                                            gint               buffer_x,
                                            gint               buffer_y,
                                            gint              *window_x,
                                            gint              *window_y );
The function converts buffer coordinates (buffer_x, buffer_y), to window coordinates (window_x, window_y). You will have to pass GTK_TEXT_WINDOW_WIDGET for win, for things to work properly.(FIXME: What's the meaning of the other values of GtkTextWidowType?)

Now that we know the position of the character within the text view widget, we will have to find the position of the text view widget on the screen.

Each GTK widget has a corresponding GdkWindow associated with it. Once we know the GdkWindow associated with a widget, we can obtain it's X-Y coordinates using gdk_window_get_origin. The GdkWindow of the text view widget can be obtained using,


GdkWindow* gtk_text_view_get_window( GtkTextView *text_view,
                                     GtkTextWindowType win );
Here again you will have to pass GTK_TEXT_WINDOW_WIDGET for win.

We now know the functions required to display a tooltip under the text cursor. Before we proceed to the example, you will have to know which signal has to be trapped to do display the tooltip. Since we want the tooltip to be displayed when the user inserts open parenthesis, "insert_text" emitted by the buffer object can be used. As the signal's name suggests it is called whenever the user inserts text into the buffer. The callback prototype is


void (*insert_text)( GtkTextBuffer *textbuffer, 
                     GtkTextIter *pos,
                     gchar *text, 
                     gint length, 
                     gpointer user_data )
The function is called with position after the inserted text pos, the inserted text text and the length of the inserted text length.

Below is an example program that displays a tooltip for the printf family of functions.


#include <gtk/gtk.h>

/* List of functions and their corresponding tool tips. */
static char *tips[][2] = 
  {
    {"printf", "(const char *format, ...)"},
    {"fprintf", "(FILE *stream, const char *format, ...)"},
    {"sprintf", "(char *str, const char *format, ...)"},
    {"snprintf", "(char *str, size_t size, const char *format, ...)"},
    {"fputc", "(int c, FILE *stream)"},
    {"fputs", "(const char *s, FILE *stream)"},
    {"putc", "(int c, FILE *stream)"},
    {"putchar", "(int c)"},
    {"puts", "(const char *s)"},
  };

#define NUM_TIPS (sizeof (tips) / sizeof (tips[0]))

gchar *
get_tip(gchar *text)
{
  gint i;
  gboolean found;

  found = FALSE;
  for (i = 0; i < NUM_TIPS; i++)
    {
      if (strcmp (text, tips[i][0]) == 0)
        {
          found = TRUE;
          break;
        }
    }
  if (!found)
    return NULL;

  return g_strdup (tips[i][1]);
}

GtkWidget *
tip_window_new (gchar *tip)
{
  GtkWidget *win;
  GtkWidget *label;
  GtkWidget *eb;
  GdkColormap *cmap;
  GdkColor color;
  PangoFontDescription *pfd;

  win = gtk_window_new (GTK_WINDOW_POPUP);
  gtk_container_set_border_width (GTK_CONTAINER (win), 0);

  eb = gtk_event_box_new ();
  gtk_container_set_border_width (GTK_CONTAINER (eb), 1);
  gtk_container_add (GTK_CONTAINER (win), eb);

  label = gtk_label_new (tip);  
  gtk_container_add (GTK_CONTAINER (eb), label);

  pfd = pango_font_description_from_string ("courier");
  gtk_widget_modify_font (label, pfd);
  
  cmap = gtk_widget_get_colormap (win);
  color.red = 0;
  color.green = 0;
  color.blue = 0;
  if (gdk_colormap_alloc_color (cmap, &color, FALSE, TRUE))
    gtk_widget_modify_bg (win, GTK_STATE_NORMAL, &color);
  else
    g_warning ("Color allocation failed!\n");

  cmap = gtk_widget_get_colormap (eb);
  color.red = 65535;
  color.green = 65535;
  color.blue = 45535;
  if (gdk_colormap_alloc_color (cmap, &color, FALSE, TRUE))
    gtk_widget_modify_bg (eb, GTK_STATE_NORMAL, &color);
  else
    g_warning ("Color allocation failed!\n");

  return win;
}

/* Called when main window is destroyed. */
void
win_destroy (void)
{
  gtk_main_quit();
}

void
insert_open_brace(GtkWidget **tip_win, GtkWidget *text_view, GtkTextIter *arg1)
{
  GdkWindow *win;
  GtkTextIter start;
  GdkRectangle buf_loc;
  gint x, y;
  gint win_x, win_y;
  gchar *text;
  gchar *tip_text;
  
  /* Get the word at cursor. */
  start = *arg1;
  if (!gtk_text_iter_backward_word_start (&start))
    return;
  text = gtk_text_iter_get_text (&start, arg1);
  g_strstrip (text);

  /* Get the corresponding tooltip. */
  tip_text = get_tip(text);  
  if (tip_text == NULL)
    return;
  
  /* Calculate the tool tip window location. */
  gtk_text_view_get_iter_location (GTK_TEXT_VIEW (text_view), arg1, 
                                   &buf_loc);
  g_printf ("Buffer: %d, %d\n", buf_loc.x, buf_loc.y);
  gtk_text_view_buffer_to_window_coords (GTK_TEXT_VIEW (text_view),
                                         GTK_TEXT_WINDOW_WIDGET,
                                         buf_loc.x, buf_loc.y,
                                         &win_x, &win_y);
  g_printf ("Window: %d, %d\n", win_x, win_y);
  win = gtk_text_view_get_window (GTK_TEXT_VIEW (text_view), 
                                  GTK_TEXT_WINDOW_WIDGET);
  gdk_window_get_origin (win, &x, &y);

  /* Destroy any previous tool tip window. */
  if (*tip_win != NULL)
    gtk_widget_destroy (GTK_WIDGET (*tip_win));  
  
  /* Create a new tool tip window and place it at the caculated
     location. */
  *tip_win = tip_window_new (tip_text);
  g_free(tip_text);
  gtk_window_move (GTK_WINDOW (*tip_win), win_x + x, 
                   win_y + y + buf_loc.height);
  gtk_widget_show_all (*tip_win);
}

void
insert_close_brace (GtkWidget **tip_win)
{
  if (*tip_win != NULL)
    {
      gtk_widget_destroy (GTK_WIDGET (*tip_win));
      *tip_win = NULL;
    }
}

void
buffer_insert_text (GtkTextBuffer *textbuffer, GtkTextIter *arg1,
                    gchar *arg2, gint arg3, gpointer user_data)
{
  static GtkWidget *tip_win = NULL;

  if (strcmp (arg2, "(") == 0)
    {
      insert_open_brace(&tip_win, GTK_WIDGET (user_data), arg1);
    }

  if (strcmp (arg2, ")") == 0)
    {
      insert_close_brace(&tip_win);
    }
}

int 
main (int argc, char *argv[])
{
  GtkWidget *win;
  GtkWidget *swindow;
  GtkWidget *text_view;
  GtkTextBuffer *buffer;
  PangoFontDescription *pfd;

  gtk_init (&argc, &argv);

  /* Create the window. */
  win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  g_signal_connect (G_OBJECT (win), "destroy", win_destroy, NULL);

  /* Create the text widget inside a scrolled window. */
  swindow = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swindow),
                                  GTK_POLICY_AUTOMATIC,
                                  GTK_POLICY_AUTOMATIC);
  gtk_container_add (GTK_CONTAINER (win), swindow);

  text_view = gtk_text_view_new ();
  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
  g_signal_connect (G_OBJECT (buffer), "insert_text", 
                    G_CALLBACK (buffer_insert_text), text_view);
  gtk_container_add (GTK_CONTAINER (swindow), text_view);

  pfd = pango_font_description_from_string ("courier");
  gtk_widget_modify_font (text_view, pfd);
  
  gtk_widget_show_all (win);

  gtk_main();
}

7.2. More on Buffer and Window Coordinates

In the previous section we obtained the screen coordinates for a position in the buffer. What if we want to do the exact opposite, ie. what if we want to find the position in the buffer corresponding to a particular X-Y coordinate. The GtkTextView has functions for these as well.

Window coordinates can be converted to buffer coordinates using


void gtk_text_view_window_to_buffer_coords( GtkTextView *text_view,
                                            GtkTextWindowType win,
                                            gint window_x,
                                            gint window_y,
                                            gint *buffer_x,
                                            gint *buffer_y );

The iter at a buffer coordinate can be obtained using


void gtk_text_view_get_iter_at_location( GtkTextView *text_view,
                                         GtkTextIter *iter,
                                         gint x,
                                         gint y );