Functions in TfeTextView

TfeTextView functions are described in this section.

tfetextview.h

The header file tfetextview.h provides:

Therefore, Any programs use TfeTextView needs to include tfetextview.h.

#ifndef __TFE_TEXT_VIEW_H__
#define __TFE_TEXT_VIEW_H__

#include <gtk/gtk.h>

#define TFE_TYPE_TEXT_VIEW tfe_text_view_get_type ()
G_DECLARE_FINAL_TYPE (TfeTextView, tfe_text_view, TFE, TEXT_VIEW, GtkTextView)

/* "open-response" signal response */
enum TfeTextViewOpenResponseType
{
  TFE_OPEN_RESPONSE_SUCCESS,
  TFE_OPEN_RESPONSE_CANCEL,
  TFE_OPEN_RESPONSE_ERROR
};

GFile *
tfe_text_view_get_file (TfeTextView *tv);

void
tfe_text_view_open (TfeTextView *tv, GtkWindow *win);

void
tfe_text_view_save (TfeTextView *tv);

void
tfe_text_view_saveas (TfeTextView *tv);

GtkWidget *
tfe_text_view_new_with_file (GFile *file);

GtkWidget *
tfe_text_view_new (void);

#endif /* __TFE_TEXT_VIEW_H__ */

Instance creation Functions

A TfeTextView instance is created with tfe_text_view_new or tfe_text_view_new_with_file.

GtkWidget *tfe_text_view_new (void);

tfe_text_view_new just creates a new TfeTextView instance and returns the pointer to the new instance.

GtkWidget *tfe_text_view_new_with_file (GFile *file);

tfe_text_view_new_with_file is given a Gfile object as an argument and it loads the file into the GtkTextBuffer instance, then returns the pointer to the new instance. If an error occurs during the creation process, NULL is returned.

Each function is defined as follows.

GtkWidget *
tfe_text_view_new_with_file (GFile *file) {
  g_return_val_if_fail (G_IS_FILE (file), NULL);

  GtkWidget *tv;
  GtkTextBuffer *tb;
  char *contents;
  gsize length;

  if (! g_file_load_contents (file, NULL, &contents, &length, NULL, NULL)) /* read error */
    return NULL;

  if ((tv = tfe_text_view_new()) != NULL) {
    tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
    gtk_text_buffer_set_text (tb, contents, length);
    TFE_TEXT_VIEW (tv)->file = g_file_dup (file);
    gtk_text_buffer_set_modified (tb, FALSE);
  }
  g_free (contents);
  return tv;
}

GtkWidget *
tfe_text_view_new (void) {
  return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, "wrap-mode", GTK_WRAP_WORD_CHAR, NULL));
}

Save and saveas functions write the contents in the GtkTextBuffer to a file.

void tfe_text_view_save (TfeTextView *tv)

The function tfe_text_view_save writes the contents in the GtkTextBuffer to a file specified by tv->file. If tv->file is NULL, then it shows GtkFileChooserDialog and prompts the user to choose a file to save. Then it saves the contents to the file and sets tv->file to point the GFile instance for the file.

void tfe_text_view_saveas (TfeTextView *tv)

The function saveas uses GtkFileChooserDialog and prompts the user to select a existed file or specify a new file to save. Then, the function changes tv->file and save the contents to the specified file. If an error occurs, it is shown to the user through the message dialog. The error is managed only in the TfeTextView and no information is notified to the caller.

save_file function

static gboolean
save_file (GFile *file, GtkTextBuffer *tb, GtkWindow *win) {
  GtkTextIter start_iter;
  GtkTextIter end_iter;
  char *contents;
  gboolean stat;
  GtkWidget *message_dialog;
  GError *err = NULL;

  gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
  contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
  stat = g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err);
  if (stat)
    gtk_text_buffer_set_modified (tb, FALSE);
  else {
    // Because error message is displayed here, the caller of 'save_file' doesn't need to do anything about error.
    message_dialog = gtk_message_dialog_new (win, GTK_DIALOG_MODAL,
                        GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s.", err->message);
    g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
    gtk_widget_show (message_dialog);
    g_error_free (err);
  }
  g_free (contents);
  return stat;
}

saveas_dialog_response function

static void
saveas_dialog_response (GtkWidget *dialog, gint response, TfeTextView *tv) {
  GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
  GFile *file;
  GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);

  if (response == GTK_RESPONSE_ACCEPT) {
    file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog));
    if (! G_IS_FILE (file))
      g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile.\n");
    else if (save_file(file, tb, GTK_WINDOW (win))) {
      // The following is complicated. The comments here will help your understanding
      // G_IS_FILE(tv->file) && tv->file == file  => nothing to do
      // G_IS_FILE(tv->file) && tv->file != file  => unref(tv->file), tv->file=file, signal emit
      // tv->file==NULL                           =>                  tv->file=file, signal emit
      if (G_IS_FILE (tv->file) && (! g_file_equal (tv->file, file)))
        g_object_unref (tv->file);
      if (! (G_IS_FILE (tv->file) && g_file_equal (tv->file, file))) {
        tv->file = file; // The ownership of 'file' moves to TfeTextView.
        g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
      }
      g_object_unref (file);
    } else
      g_object_unref (file);
  }
  gtk_window_destroy (GTK_WINDOW (dialog));
}

tfe_text_view_save function

void
tfe_text_view_save (TfeTextView *tv) {
  g_return_if_fail (TFE_IS_TEXT_VIEW (tv));

  GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
  GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);

  if (! gtk_text_buffer_get_modified (tb))
    return; /* no need to save it */
  else if (tv->file == NULL)
    tfe_text_view_saveas (tv);
  else if (! G_IS_FILE (tv->file)) // Unexpected error
    g_error ("TfeTextView: The pointer tv->file isn't NULL nor GFile.\n");
  else
    save_file (tv->file, tb, GTK_WINDOW (win));
}

tfe_text_view_saveas function

void
tfe_text_view_saveas (TfeTextView *tv) {
  g_return_if_fail (TFE_IS_TEXT_VIEW (tv));

  GtkWidget *dialog;
  GtkWidget *win = gtk_widget_get_ancestor (GTK_WIDGET (tv), GTK_TYPE_WINDOW);

  dialog = gtk_file_chooser_dialog_new ("Save file", GTK_WINDOW (win), GTK_FILE_CHOOSER_ACTION_SAVE,
                                      "Cancel", GTK_RESPONSE_CANCEL,
                                      "Save", GTK_RESPONSE_ACCEPT,
                                      NULL);
  g_signal_connect (dialog, "response", G_CALLBACK (saveas_dialog_response), tv);
  gtk_widget_show (dialog);
}

When you use GtkFileChooserDialog, you need to divide the program into two parts. One is a function which creates GtkFileChooserDialog and the other is a signal handler. The function just creates and shows GtkFileChooserDialog. The rest is done by the handler. It gets Gfile from GtkFileChooserDialog and saves the buffer to the file by calling save_file.

Saveas process

Open function shows GtkFileChooserDialog to a user and prompts them to choose a file. Then it reads the file and puts the text into GtkTextBuffer.

void tfe_text_view_open (TfeTextView *tv, GtkWindow *win);

The parameter win is the top-level window. It will be a transient parent window of GtkFileChooserDialog when the dialog is created.

This function may be called just after tv has been created. In that case, tv has not been incorporated into the widget hierarchy. Therefore it is impossible to get the top-level window from tv. That’s why the function needs win parameter.

This function is usually called when the buffer of tv is empty. However, even if the buffer is not empty, tfe_text_view_open doesn’t treat it as an error. If you want to revert the buffer, calling this function is appropriate.

Open and read process is divided into two phases. One is showing GtkFileChooserDialog and the other is its response handler. The response handler gets the filename, reads the contents of the file and puts it into the GtkTextBuffer.

open_dialog_response function

static void
open_dialog_response(GtkWidget *dialog, gint response, TfeTextView *tv) {
  GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
  GFile *file;
  char *contents;
  gsize length;
  GtkWidget *message_dialog;
  GError *err = NULL;

  if (response != GTK_RESPONSE_ACCEPT)
    g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_CANCEL);
  else if (! G_IS_FILE (file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)))) {
    g_warning ("TfeTextView: gtk_file_chooser_get_file returns non GFile.\n");
    g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
  } else if (! g_file_load_contents (file, NULL, &contents, &length, NULL, &err)) { /* read error */
    g_object_unref (file);
    message_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL,
                        GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", err->message);
    g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
    gtk_widget_show (message_dialog);
    g_error_free (err);
    g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_ERROR);
  } else {
    gtk_text_buffer_set_text (tb, contents, length);
    g_free (contents);
    gtk_text_buffer_set_modified (tb, FALSE);
    // G_IS_FILE(tv->file) && tv->file == file => unref(tv->file), tv->file=file, emit response with SUCCESS
    // G_IS_FILE(tv->file) && tv->file != file => unref(tv->file), tv->file=file, emit response with SUCCESS, emit change-file
    // tv->file==NULL =>                                           tv->file=file, emit response with SUCCESS, emit change-file
    // The order is important. If you unref tv->file first, you can't compare tv->file and file anymore.
    // And open-response signal is emitted after new tv->file is set. Or the handler can't catch the new file.
    if (! (G_IS_FILE (tv->file) && g_file_equal (tv->file, file)))
      g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
    if (G_IS_FILE (tv->file))
      g_object_unref (tv->file);
    tv->file = file; // The ownership of 'file' moves to TfeTextView
    g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
  }
  gtk_window_destroy (GTK_WINDOW (dialog));
}

tfe_text_view_open function

void
tfe_text_view_open (TfeTextView *tv, GtkWindow *win) {
  g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
  // 'win' is used for a transient window of the GtkFileChooserDialog.
  // It can be NULL.
  g_return_if_fail (GTK_IS_WINDOW (win) || win == NULL);

  GtkWidget *dialog;

  dialog = gtk_file_chooser_dialog_new ("Open file", win, GTK_FILE_CHOOSER_ACTION_OPEN,
            "Cancel", GTK_RESPONSE_CANCEL, "Open", GTK_RESPONSE_ACCEPT, NULL);
  g_signal_connect (dialog, "response", G_CALLBACK (open_dialog_response), tv);
  gtk_widget_show (dialog);
}

The whole process between the caller and TfeTextView is shown in the following diagram. It is really complicated. Because signal is the only way for GtkFileChooserDialog to communicate with others.

Caller and TfeTextView
  1. A caller gets a pointer tv to a TfeTextView instance by calling tfe_text_view_new.
  2. The caller connects the handler (left bottom in the diagram) and the signal “open-response”.
  3. It calls tfe_text_view_open to prompt the user to select a file from GtkFileChooserDialog.
  4. The dialog emits a signal and it invokes the handler open_dialog_response.
  5. The handler reads the file and inserts the text into GtkTextBuffer and emits a signal to inform the status as a response code.
  6. The handler out of the TfeTextView receives the signal.

Getting GFile in TfeTextView

You can get the GFile in a TfeTextView instance with tfe_text_view_get_file. It is very simple.

GFile *
tfe_text_view_get_file (TfeTextView *tv) {
  g_return_val_if_fail (TFE_IS_TEXT_VIEW (tv), NULL);

  if (G_IS_FILE (tv->file))
    return g_file_dup (tv->file);
  else
    return NULL;
}

The important thing is to duplicate tv->file. Otherwise, if the caller frees the GFile object, tv->file is no more guaranteed to point the GFile. Another reason to use g_file_dup is that GFile isn’t thread-safe. If you use GFile in the different thread, the duplication is necessary. See Gio API Reference – g_file_dup.

The API document and source file of tfetextview.c

Refer API document of TfeTextView. The markdown file is under the directory src/tfetextview.

You can find all the TfeTextView source codes under src/tfetextview directories.