TfeTextView functions are described in this section.
The header file tfetextview.h
provides:
TFE_TYPE_TEXT_VIEW
.G_DECLARE_FINAL_TYPE
includes some
useful macros.open-response
signal is
defined..tfetextview.c
are declared.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__ */
#pragma once
instead of
them. It is non-standard but widely used.gtk4
also has the same mechanism to avoid including it multiple times.TfeTextView
and TfeTextViewClass
are
declared as typedef of C structures._TfeTextView
later._TfeTextViewClass
is defined here.
You don’t need to define it by yourself.TFE_TEXT_VIEW ()
for casting and
TFE_IS_TEXT_VIEW
for type check are defined.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, "wrap-mode", GTK_WRAP_WORD_CHAR, NULL));
}
tfe_text_view_new
function. Just returns the
value from the function g_object_new
but casts it to the
pointer to GtkWidget. The function g_object_new
creates any
instances of its descendant class. The arguments are the type of the
class, property list and NULL. Null is the end mark of the property
list. TfeTextView “wrap-mode” property has GTK_WRAP_WORD_CHAR as the
default value.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;
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;
}
save_file
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. So, a caller
of this function don’t need to take care of errors. The class of this
function is static
. Therefore, only functions in this file
(tfetextview.c
) call this function. Such static functions
usually don’t have g_return_val_if_fail
function.g_file_replace_contents
writes the
contents to the file and returns the status (true = success/ false =
fail). It has many parameters, but some of them are almost always given
the same values.
G_FILE_CREATE_NONE
is
fine.gtk_window_destroy
, so that the dialog disappears when the
user clicks on the button.err
with g_error_free
function.contents
.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));
}
saveas_dialog_response
is a signal handler
for the “response” signal on GtkFileChooserDialog. This handler analyzes
the response and determines whether to save the contents or not.GTK_RESPONSE_ACCEPT
, the user
has clicked on the Save
button and the contents will be
saved.file
from the
GtkFileChooserDialog.save_file
to save the contents
to the file.save_file
has successfully saved the
contents, the following will be done.
tv->file
is GFile and file
is a
different file, unref tv->file
.tv->file
is GFile and file
points
the same file as tv->file
, nothing needs to do.
Otherwise, tv->file
is set to file
and
“change-file” signal is emitted.file
.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_save
writes the contents to
the tv->file
file. It calls
tfe_text_view_saveas
or save_file
.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 (void).tb
and GtkWidget (GtkWindow)
win
are set. The function
gtk_widget_get_ancestor (widget, type)
returns the first
ancestor of the widget with type. The type is a GType. For example, the
type of GtkWindow is GTK_TYPE_WINDOW
and the one of
TfeTextView is TFE_TYPE_TEXT_VIEW
. Be careful. The
parent-child relationship here is the one for widgets, not classes. The
top level window may be a GtkApplicationWindow, but it depends on the
application. Because TfeTextView is a library, it can’t determine the
top level window type (GtkWindow or GtkApplicationWindow). GtkWindow is
a parent class of GtkApplication window so it is more general.
Therefore, TfeTextView takes GtkWindow as a top level window so that it
can be used by any application.tv->file
is NULL, which means no file has
given yet, it calls tfe_text_view_saveas
to prompt a user
to select a file and save the contents.tv->file
doesn’t point GFile, an error
message is logged out. It is not expected.save_file
to save the
contents to the file tv->file
.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);
}
tfe_text_view_saveas
shows
GtkFileChooserDialog and prompts the user to choose a file and save the
contents.tv
because the caller may be
other object. This function is public.win
is set to the top level window.GTK_FILE_CHOOSER_ACTION_OPEN
,
GTK_FILE_CHOOSER_ACTION_SAVE
and
GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
. When you want to
save a file, GTK_FILE_CHOOSER_ACTION_SAVE
is the
action.win
, save mode action, cancel and save button.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 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.
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));
}
open_dialog_response
has three
parameters.
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. It is not expected, though. Then it emits
“open-response” signal with the parameter
TFE_OPEN_RESPONSE_ERROR
.TFE_OPEN_RESPONSE_ERROR
.contents
is freedTFE_OPEN_REPONSE_SUCCESS
tv->file
isn’t NULL,
g_object_unref(tv->file)
is calledtv->file
is assigned with file
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);
}
tv
and
win
. Public functions always need to check the
arguments.win
.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.
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
.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.
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.