6. Images/Widgets

A text buffer can hold images and anchor location for widgets. In this section, you will learn how to insert images and widgets. You will also learn how to retrieve an inserted image/widget.

6.1. Inserting Images

An image can be inserted into a buffer using the following function


void gtk_text_buffer_insert_pixbuf( GtkTextBuffer *buffer,
                                    GtkTextIter *iter,
                                    GdkPixbuf *pixbuf );
An image represented by pixbuf is inserted at iter. The pixbuf can be created from an image file using gdk_pixbuf_new_from_file. See the gdk manual for more details about GdkPixbuf objects.

The example program given below takes in an image filename and inserts the corresponding image into a buffer.


#include <gtk/gtk.h>

typedef struct _App
{
  GtkWidget *window;
  GtkWidget *text_view;
} App;

void
on_window_destroy (GtkWidget *widget, gpointer data)
{
  gtk_main_quit ();
}

void
insert_button_clicked (GtkWidget *widget, gpointer data)
{
  App *app = (App *)data;
  GtkWidget *dialog;
  int ret;
  gchar *filename;
  GError *error = NULL;
  GdkPixbuf *pixbuf;
  GtkTextBuffer *buffer;
  GtkTextMark *cursor;
  GtkTextIter iter;

  dialog = gtk_file_chooser_dialog_new ("Image file...", 
                                        GTK_WINDOW (app->window), 
                                        GTK_FILE_CHOOSER_ACTION_OPEN,
                                        GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
                                        GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                                        NULL);
  ret = gtk_dialog_run (GTK_DIALOG (dialog));
  switch (ret)
    {
    case GTK_RESPONSE_ACCEPT:
      filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));  
      gtk_widget_destroy (dialog);      
      break;
    case GTK_RESPONSE_DELETE_EVENT:
    case GTK_RESPONSE_CANCEL:
      gtk_widget_destroy (dialog);
      /* Fall through */
    case GTK_RESPONSE_NONE:
      return;
    }

  pixbuf = gdk_pixbuf_new_from_file (filename, &error);
  g_free (filename);
  if (error)
    {
      GtkWidget *msg;
      msg = gtk_message_dialog_new (GTK_WINDOW (app->window), 
                                    GTK_DIALOG_MODAL,
                                    GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, 
                                    error->message);
      gtk_dialog_run (GTK_DIALOG (msg));
      gtk_widget_destroy (msg);
      g_error_free (error);
      return;
    }

  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (app->text_view));
  cursor = gtk_text_buffer_get_insert (buffer);
  gtk_text_buffer_get_iter_at_mark (buffer, &iter, cursor);
  gtk_text_buffer_insert_pixbuf (buffer, &iter, pixbuf);
}

int 
main(int argc, char *argv[])
{
  App app;
  GtkWidget *vbox;
  GtkWidget *insert_button;
  
  gtk_init (&argc, &argv);

  /* Create a Window. */
  app.window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title (GTK_WINDOW (app.window), 
                        "Insert image!");

  /* Set a decent default size for the window. */
  gtk_window_set_default_size (GTK_WINDOW (app.window), 200, 200);
  g_signal_connect (G_OBJECT (app.window), "destroy", 
                    G_CALLBACK (on_window_destroy),
                    NULL);

  vbox = gtk_vbox_new (FALSE, 2);
  gtk_container_add (GTK_CONTAINER (app.window), vbox);

  insert_button = gtk_button_new_with_label("Insert Image");
  gtk_box_pack_start (GTK_BOX (vbox), insert_button, FALSE, FALSE, 0);
  g_signal_connect (G_OBJECT (insert_button), "clicked",
                    G_CALLBACK (insert_button_clicked), &app);
  

  /* Create a multiline text widget. */
  app.text_view = gtk_text_view_new ();
  gtk_box_pack_start (GTK_BOX (vbox), app.text_view, 1, 1, 0);

  gtk_widget_show_all (app.window);

  gtk_main ();
  return 0;
}

6.2. Retrieving Images

Images in a buffer are represented by the character 0xFFFC(Unicode object replacement character). When text containing images is retrieved from a buffer using gtk_text_buffer_get_text the 0xFFFC characters representing images are dropped off in the returned text. If these characters representing images are required, use the slice variant - gtk_text_buffer_get_slice.

The image at a given position can be retrieved using


GdkPixbuf* gtk_text_iter_get_pixbuf( const GtkTextIter *iter );

6.3. Inserting Widgets

Inserting a widget, unlike inserting an image, is a two step process. The additional complexity is due to the functionality split between GtkTextView and GtkTextBuffer.

The first step is to create and insert a GtkTextChildAnchor. A widget is held in a buffer using a GtkTextChildAnchor. A child anchor according to the GTK manual is a spot in the buffer where child widgets can be anchored.

A child anchor can be created and inserted into a buffer using


GtkTextChildAnchor* gtk_text_buffer_create_child_anchor( GtkTextBuffer *buffer,
                                                         GtkTextIter *iter );
Where iter specifies the position in the buffer, where the widget is to be inserted.

The next step is to add a child widget to the text view, at the anchor location.


void gtk_text_view_add_child_at_anchor( GtkTextView *text_view,
                                        GtkWidget *child,
                                        GtkTextChildAnchor *anchor );
An anchor can hold only one widget(it could be a container widget, which in turn can contain many widgets), unless you are doing tricky things like displaying the same buffer using different GtkTextView objects.

The following program inserts a button widget into a text buffer, whenever the user clicks on the Insert button.


#include <gtk/gtk.h>

typedef struct _App
{
  GtkWidget *window;
  GtkWidget *text_view;
} App;

void
on_window_destroy (GtkWidget *widget, gpointer data)
{
  gtk_main_quit ();
}

void
insert_button_clicked (GtkWidget *widget, gpointer data)
{
  App *app = (App *)data;
  GtkTextBuffer *buffer;
  GtkTextMark *cursor;
  GtkTextIter iter;
  GtkTextChildAnchor *anchor;
  GtkWidget *button;
  
  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (app->text_view));
  cursor = gtk_text_buffer_get_insert (buffer);
  gtk_text_buffer_get_iter_at_mark (buffer, &iter, cursor);
  anchor = gtk_text_buffer_create_child_anchor (buffer, &iter);
  button = gtk_button_new_with_label ("New button!");
  gtk_text_view_add_child_at_anchor (GTK_TEXT_VIEW (app->text_view),
                                     button, anchor);
  gtk_widget_show (button);
}

int 
main(int argc, char *argv[])
{
  App app;
  GtkWidget *vbox;
  GtkWidget *insert_button;
  
  gtk_init (&argc, &argv);

  /* Create a Window. */
  app.window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title (GTK_WINDOW (app.window), 
                        "Insert button!");

  /* Set a decent default size for the window. */
  gtk_window_set_default_size (GTK_WINDOW (app.window), 200, 200);
  g_signal_connect (G_OBJECT (app.window), "destroy", 
                    G_CALLBACK (on_window_destroy),
                    NULL);

  vbox = gtk_vbox_new (FALSE, 2);
  gtk_container_add (GTK_CONTAINER (app.window), vbox);

  insert_button = gtk_button_new_with_label ("Insert button");
  gtk_box_pack_start (GTK_BOX (vbox), insert_button, FALSE, FALSE, 0);
  g_signal_connect (G_OBJECT (insert_button), "clicked",
                    G_CALLBACK (insert_button_clicked), &app);
  

  /* Create a multiline text widget. */
  app.text_view = gtk_text_view_new ();
  gtk_box_pack_start (GTK_BOX (vbox), app.text_view, 1, 1, 0);

  gtk_widget_show_all (app.window);

  gtk_main ();
  return 0;
}

6.4. Retrieving Widgets

Child anchors are also represented in the buffer using the object replacement character 0xFFFC. Retrieving a widget is also a two step process.

First, the child anchor has to be retrieved. This can be done using


GtkTextChildAnchor* gtk_text_iter_get_child_anchor( const GtkTextIter *iter );

Next, the widget(s) associated with the child anchor has to be retrieved. This can be done using


GList* gtk_text_child_anchor_get_widgets( GtkTextChildAnchor *anchor );
The function returns a list of widgets. As mentioned earlier, if you are not doing tricky things like multiple views for the same buffer, you will find only one widget in this list. The list has to be freed with g_list_free.