In this section I will explain functions in TfeTextView object.
tfe.h
is a top header file and it includes
gtk.h
and all the header files. C source files
tfeapplication.c
and tfenotebook.c
include
tfe.h
at the beginning.
../tfetextview/tfetextview.h
is a header file which
describes the public functions in tfetextview.c
.
#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__ */
gtk4
also has the same mechanism to avoid including it multiple times.A TfeTextView instance is created with tfe_text_view_new
or tfe_text_view_new_with_file
.
*tfe_text_view_new (void); GtkWidget
tfe_text_view_new
just creates a new TfeTextView
instance and returns the pointer to the new instance.
*tfe_text_view_new_with_file (GFile *file); GtkWidget
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, NULL));
}
tfe_text_view_new
function. Just returns the
value from the function g_object_new
but casts it to the
pointer to GtkWidget. Initialization is done in
tfe_text_view_init
which is called in the process of
g_object_new
function.tfe_text_view_new_with_file
function.g_return_val_if_fail
is described in GLib API
Reference, g_return_val_if_fail. And also GLib API Reference,
Message Logging. It tests whether the argument file
is
a pointer to GFile. If it’s true, then the program goes on to the next
line. If it’s false, then it returns NULL (the second argument)
immediately. And at the same time it logs out the error message (usually
the log is outputted to stderr or stdout). This function is used to
check the programmer’s error. If an error occurs, the solution is
usually to change the (caller) program and fix the bug. You need to
distinguish programmer’s errors and runtime errors. You shouldn’t use
this function to find runtime errors.tfe_text_view_new
. The function
creates TfeTextView instance and returns the pointer to the instance. If
an error happens in tfe_text_view_new
, it returns
NULL.tv
. The pointer is assigned to tb
tb
.file
and sets tv->file
to point it.gtk_text_buffer_set_modified (tb, FALSE)
sets the
modification flag of tb
to FALSE. The modification flag
indicates that the contents of the buffer has been modified. It is used
when the contents are saved. If the modification flag is FALSE, it
doesn’t need to save the contents.contents
.tv
, which is a pointer to the newly created
TfeTextView instance. If an error happens, NULL is returned.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.
static gboolean
save_file (GFile *file, GtkTextBuffer *tb, GtkWindow *win) {
GtkTextIter start_iter;
GtkTextIter end_iter;
gchar *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);
if (g_file_replace_contents (file, contents, strlen (contents), NULL, TRUE, G_FILE_CREATE_NONE, NULL, NULL, &err)) {
gtk_text_buffer_set_modified (tb, FALSE);
stat = TRUE;
} else {
message_dialog = gtk_message_dialog_new (win, GTK_DIALOG_MODAL,
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
"%s.\n", err->message);
g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
gtk_widget_show (message_dialog);
g_error_free (err);
stat = FALSE;
}
g_free (contents);
return stat;
}
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))) {
if (G_IS_FILE (tv->file))
g_object_unref (tv->file);
tv->file = file;
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
} else
g_object_unref (file);
}
gtk_window_destroy (GTK_WINDOW (dialog));
}
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))
g_error ("TfeTextView: The pointer tv->file isn't NULL nor GFile.\n");
else
save_file (tv->file, tb, GTK_WINDOW (win));
}
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);
}
save_file
function. This function is called from
saveas_dialog_response
and tfe_text_view_save
.
This function saves the contents of the buffer to the file given as an
argument. If error happens, it displays an error message. The class of
this function is static
. Therefore, only functions in this
file (tfeTetview.c
) call this function. Such static
functions usally don’t have g_return_val_if_fail
function.stat
to
be TRUE.gtk_window_destroy
, so that the dialog disappears when a
user clicked on the button.err
and set
stat
to be FLASE.contents
.saveas_dialog_response
function. This is a
signal handler for the “response” signal on GtkFileChooserDialog
instance created by tfe_text_view_saveas
function. This
handler analyzes the response and determines whether to save the
contents.GTK_RESPONSE_ACCEPT
, the user
has clicked on the Save
button. So, it tries to save.file
from GtkFileChooserDialog.save_file
to save the contents
to the file.save_file
has successfully saved the
contents, tv->file
is updated. If the old GFile pointed
by tv->file
exists, it is freed in advance. Emits
“change-file” signal.file
.tfe_text_view_save
function.tfe_text_view_save
is public, i.e. it is open to
the other files. So, it doesn’t have static
class. Public
functions should check the parameter type with
g_return_if_fail
function. If tv
is not a
pointer to a TfeTextView instance, then it logs an error message and
immediately returns. This function is similar to
g_return_val_if_fail
, but no value is returned because
tfe_text_view_save
doesn’t return a value.tb
andwin
respectively.tv->file
is NULL, no file has given yet.
It calls tfe_text_view_saveas
which prompts a user to
select a file or specify a new file to save.tv->file
doesn’t point GFile, somethig bad
has happened. Logs an error message.save_file
to save the contents to the
file.tfe_text_view_saveas
function. It shows
GtkFileChooserDialog and prompts the user to choose a file.win
, which is the
top-level window. The action is save mode. The buttons are Cancel and
Save.saveas_dialog_response
handler.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
.
Open function shows GtkFileChooserDialog to users 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 allows window managers to keep the dialog on top of the
parent window, or center the dialog over the parent window. It is
possible to give no parent window to the dialog. However, it is
encouraged to give a parent window to dialog. This function might 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.
Otherwise probably bad things will happen.
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.\n", 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);
if (G_IS_FILE (tv->file))
g_object_unref (tv->file);
tv->file = file;
gtk_text_buffer_set_modified (tb, FALSE);
g_signal_emit (tv, tfe_text_view_signals[OPEN_RESPONSE], 0, TFE_OPEN_RESPONSE_SUCCESS);
g_signal_emit (tv, tfe_text_view_signals[CHANGE_FILE], 0);
}
gtk_window_destroy (GTK_WINDOW (dialog));
}
void
tfe_text_view_open (TfeTextView *tv, GtkWindow *win) {
g_return_if_fail (TFE_IS_TEXT_VIEW (tv));
g_return_if_fail (GTK_IS_WINDOW (win));
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);
}
tfe_text_view_open
function.open_dialog_response
signal handler.open_dialog_response
signal handler.GTK_RESPONSE_ACCEPT
, the user has clicked on the “Cancel”
button or close button on the header bar. Then, “open-response” signal
is emitted. The parameter of the signal is
TFE_OPEN_RESPONSE_CANCEL
.gtk_file_chooser_get_file
. If it doesn’t point GFile, maybe
an error has occurred. Then it emits “open-response” signal with the
parameter TFE_OPEN_RESPONSE_ERROR
.TFE_OPEN_RESPONSE_ERROR
.contents
and sets tv->file
to point the
file (no duplication is not necessary). Then, it emits “open-response”
signal with the parameter TFE_OPEN_RESPONSE_SUCCESS
and
emits “change-file” signal.Now let’s think about the whole process between the caller and
TfeTextView. It is shown in the following diagram and you would think
that it is really complicated. Because signal is the only way for
GtkFileChooserDialog to communicate with others. In GTK 3,
gtk_dialog_run
function is available. It simplifies the
process. However, in GTK 4, gtk_dialog_run
is unavailable
any more.
tv
to a TfeTextView instance by
calling tfe_text_view_new
.tfe_text_view_open
to prompt the user to
select a file from GtkFileChooserDialog.open_dialog_response
.gtk_text_view_get_file
is a simple function shown as
follows.
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.
Refer API document of TfeTextView. Its original markdown file is
under the directory src/tfetextview
.
All the source files are listed in Section 16. You can find them under src/tfe5 and src/tfetextview directories.