The tfe program in the previous section is not so good because many
things are crammed into tfepplication.c
. And many static
variables in tfepplication.c
. The file
tfeapplication.c
should be divided into several files.
tfeapplication.c
only has codes related to the
application.The preference dialog is defined by a ui file. And it has GtkBox, GtkLabel and GtkFontButton in it. Such widget can be defined as a composite widget. Composite widget is:
Next subsection shows how to build a preference dialog.
First, write a template XML file.
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="TfePref" parent="GtkDialog">
<property name="title">Preferences</property>
<property name="resizable">FALSE</property>
<property name="modal">TRUE</property>
<child internal-child="content_area">
<object class="GtkBox" id="content_area">
<child>
<object class="GtkBox" id="pref_boxh">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<property name="spacing">12</property>
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<property name="margin-top">12</property>
<property name="margin-bottom">12</property>
<child>
<object class="GtkLabel" id="fontlabel">
<property name="label">Font:</property>
<property name="xalign">1</property>
</object>
</child>
<child>
<object class="GtkFontButton" id="fontbtn">
</object>
</child>
</object>
</child>
</object>
</child>
</template>
</interface>
Template tag specifies a composite widget. The value of a class
attribute is the object name. It is “TfePref”. A parent attribute
specifies the direct parent class of the composite widget. Therefore.
TfePref
is a child class of GtkDialog
. A
parent attribute is optional. But it is recommended to specify it. Other
lines are the same as before.
The class TfePref
is defined like TfeTextView. There are
two files tfepref.h
and tfepref.c
.
The file tfepref.h
defines types and declares public
functions. The definitions are public and open to any C files.
#ifndef __TFE_PREF_H__
#define __TFE_PREF_H__
#include <gtk/gtk.h>
#define TFE_TYPE_PREF tfe_pref_get_type ()
G_DECLARE_FINAL_TYPE (TfePref, tfe_pref, TFE, PREF, GtkDialog)
GtkWidget *
tfe_pref_new (void);
#endif /* __TFE_PREF_H__ */
TFE_TYPE_PREF
, which is a macro
replaced by tfe_pref_get_type ()
.G_DECLAER_FINAL_TYPE
expands to:
tfe_pref_get_type ()
is declared.struct _TfePrep
.struct {GtkDialogClass *parent;}
.TFE_PREF ()
and
TFE_IS_PREF ()
is defined.tfe_pref_new
creates a new TfePref object.The file tfepref.c
includes:
struct _TfePrep
structureG_DEFINE_TYPE
macro#include <gtk/gtk.h>
#include "tfepref.h"
struct _TfePref
{
GtkDialog parent;
GSettings *settings;
GtkFontButton *fontbtn;
};
G_DEFINE_TYPE (TfePref, tfe_pref, GTK_TYPE_DIALOG);
static void
tfe_pref_dispose (GObject *gobject) {
TfePref *pref = TFE_PREF (gobject);
g_clear_object (&pref->settings);
G_OBJECT_CLASS (tfe_pref_parent_class)->dispose (gobject);
}
static void
tfe_pref_init (TfePref *pref) {
gtk_widget_init_template (GTK_WIDGET (pref));
pref->settings = g_settings_new ("com.github.ToshioCP.tfe");
g_settings_bind (pref->settings, "font", pref->fontbtn, "font", G_SETTINGS_BIND_DEFAULT);
}
static void
tfe_pref_class_init (TfePrefClass *class) {
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->dispose = tfe_pref_dispose;
gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class), "/com/github/ToshioCP/tfe/tfepref.ui");
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfePref, fontbtn);
}
GtkWidget *
tfe_pref_new (void) {
return GTK_WIDGET (g_object_new (TFE_TYPE_PREF, NULL));
}
struct _TfePref
is defined. Every
TfePref instance has its own data of the structure. The structure has
references to:
G_DEFINE_TYPE
macro. The macro expands to:
tfe_pref_class_init
tfe_pref_init
tfe_pref_parent_class
that points the
parent class (GtkDialogClass) structure.tfe_pref_get_type ()
functiontfe_pref_dispose
function. It is called in the
destruction process and releases all the reference to other objects. For
further information about destruction process, refer to Section 11.g_clear_object (&pref->gsettings)
does:
g_object_unref (pref->gsettings)
pref->settings = NULL
pref
points a newly created TfePref instance.gtk_widget_init_template
creates and
initializes the child widgets. The widgets are created based on the
template which is created in the
gtk_widget_class_set_template_from_resource
function.pref->settings
. The instance refers to a GSetting id
com.github.ToshioCP.tfe
.font
and the
font
property of pref->fontbtn
(GtkFontButton). The element pref->fontbtn
points the
GtkFontButton, which is the instance of fontbtn
in the ui
file. The relation was made by the
gtk_widget_class_bind_template_child
function.gtk_widget_class_set_template_from_resource
function associates the description in the XML file
(tfepref.ui
) with the widget. At this moment no instance is
created. It just makes the class recognize the structure of the object.
That’s why the top level tag is not <object>
but
<template>
in the XML file. The instance will be
created in the gtk_widget_init_template
function
later.gtk_widget_class_bind_template_child
macro binds
the structure member (fontbtn
in
struct _TfePref
) and the id fontbtn
in the XML
file. The two names must be the same. This binding is between the member
and the template (not an instance).tfe_pref_new
creates a TfePref
instance.Now, It is very simple to use this dialog. A caller just creates this object and shows it.
*pref;
TfePref = tfe_pref_new ();
pref (GTK_WINDOW (pref), win); /* win is the main window */
gtk_window_set_transient_for (GTK_WINDOW (pref)); gtk_window_present
This instance is automatically destroyed when a user clicks on the close button. That’s all. If you want to show the dialog again, just create and show it.
It is almost same as preference dialog.
Its ui file is:
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="TfeAlert" parent="GtkDialog">
<property name="title">Are you sure?</property>
<property name="resizable">FALSE</property>
<property name="modal">TRUE</property>
<child internal-child="content_area">
<object class="GtkBox">
<child>
<object class="GtkBox">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<property name="spacing">12</property>
<property name="margin-start">12</property>
<property name="margin-end">12</property>
<property name="margin-top">12</property>
<property name="margin-bottom">12</property>
<child>
<object class="GtkImage">
<property name="icon-name">dialog-warning</property>
<property name="icon-size">GTK_ICON_SIZE_LARGE</property>
</object>
</child>
<child>
<object class="GtkLabel" id="lb_alert">
</object>
</child>
</object>
</child>
</object>
</child>
<child type="action">
<object class="GtkButton" id="btn_cancel">
<property name="label">Cancel</property>
</object>
</child>
<child type="action">
<object class="GtkButton" id="btn_accept">
<property name="label">Close</property>
</object>
</child>
<action-widgets>
<action-widget response="cancel" default="true">btn_cancel</action-widget>
<action-widget response="accept">btn_accept</action-widget>
</action-widgets>
</template>
</interface>
The header file is:
#ifndef __TFE_ALERT_H__
#define __TFE_ALERT_H__
#include <gtk/gtk.h>
#define TFE_TYPE_ALERT tfe_alert_get_type ()
G_DECLARE_FINAL_TYPE (TfeAlert, tfe_alert, TFE, ALERT, GtkDialog)
void
tfe_alert_set_message (TfeAlert *alert, const char *message);
void
tfe_alert_set_button_label (TfeAlert *alert, const char *label);
GtkWidget *
tfe_alert_new (void);
#endif /* __TFE_ALERT_H__ */
There are three public functions. The functions
tfe_alert_set_message
and
tfe_alert_set_button_label
sets the label and button name
of the alert dialog. For example, if you want to show an alert that the
user tries to close without saving the content, set them like:
(alert, "Contents aren't saved yet.\nAre you sure to close?");
tfe_alert_set_message (alert, "Close"); tfe_alert_set_button_label
The function tfe_alert_new
creates a TfeAlert
dialog.
The C source file is:
#include <gtk/gtk.h>
#include "tfealert.h"
struct _TfeAlert
{
GtkDialog parent;
GtkLabel *lb_alert;
GtkButton *btn_accept;
};
G_DEFINE_TYPE (TfeAlert, tfe_alert, GTK_TYPE_DIALOG);
void
tfe_alert_set_message (TfeAlert *alert, const char *message) {
gtk_label_set_text (alert->lb_alert, message);
}
void
tfe_alert_set_button_label (TfeAlert *alert, const char *label) {
gtk_button_set_label (alert->btn_accept, label);
}
static void
tfe_alert_init (TfeAlert *alert) {
gtk_widget_init_template (GTK_WIDGET (alert));
}
static void
tfe_alert_class_init (TfeAlertClass *class) {
gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class), "/com/github/ToshioCP/tfe/tfealert.ui");
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeAlert, lb_alert);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeAlert, btn_accept);
}
GtkWidget *
tfe_alert_new (void) {
return GTK_WIDGET (g_object_new (TFE_TYPE_ALERT, NULL));
}
The program is almost same as tfepref.c
.
The Usage of the alert object is as follows.
TfeWindow
is a child class of GtkApplicationWindow.
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="TfeWindow" parent="GtkApplicationWindow">
<property name="title">file editor</property>
<property name="default-width">600</property>
<property name="default-height">400</property>
<child>
<object class="GtkBox" id="boxv">
<property name="orientation">GTK_ORIENTATION_VERTICAL</property>
<child>
<object class="GtkBox" id="boxh">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<child>
<object class="GtkLabel" id="dmy1">
<property name="width-chars">10</property>
</object>
</child>
<child>
<object class="GtkButton" id="btno">
<property name="label">Open</property>
<property name="action-name">win.open</property>
</object>
</child>
<child>
<object class="GtkButton" id="btns">
<property name="label">Save</property>
<property name="action-name">win.save</property>
</object>
</child>
<child>
<object class="GtkLabel" id="dmy2">
<property name="hexpand">TRUE</property>
</object>
</child>
<child>
<object class="GtkButton" id="btnc">
<property name="label">Close</property>
<property name="action-name">win.close</property>
</object>
</child>
<child>
<object class="GtkMenuButton" id="btnm">
<property name="direction">down</property>
<property name="halign">start</property>
<property name="icon-name">open-menu-symbolic</property>
</object>
</child>
<child>
<object class="GtkLabel" id="dmy3">
<property name="width-chars">10</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkNotebook" id="nb">
<property name="scrollable">TRUE</property>
<property name="hexpand">TRUE</property>
<property name="vexpand">TRUE</property>
</object>
</child>
</object>
</child>
</template>
</interface>
This XML file is almost same as before except template tag and “action-name” property in buttons.
GtkButton implements GtkActionable interface, which has “action-name”
property. If this property is set, GtkButton activates the action when
it is clicked. For example, if an open button is clicked, “win.open”
action will be activated and open_activated
handler will be
invoked.
This action is also used by “<Control>o” accelerator (See
tfeapplication.c
). If you used “clicked” signal for the
button, you would need its signal handler. Then, there would be two
handlers:
These two handlers are almost same. It is inefficient. Connecting buttons to actions is a good way to reduce unnecessary codes.
#ifndef __TFE_WINDOW_H__
#define __TFE_WINDOW_H__
#include <gtk/gtk.h>
#define TFE_TYPE_WINDOW tfe_window_get_type ()
G_DECLARE_FINAL_TYPE (TfeWindow, tfe_window, TFE, WINDOW, GtkApplicationWindow)
void
tfe_window_notebook_page_new (TfeWindow *win);
void
tfe_window_notebook_page_new_with_files (TfeWindow *win, GFile **files, int n_files);
GtkWidget *
tfe_window_new (GtkApplication *app);
#endif /* __TFE_WINDOW_H__ */
There are three public functions. The function
tfe_window_notebook_page_new
creates a new notebook page.
This is a wrapper function for notebook_page_new
. It is
called by TfeApplication object. The function
tfe_window_notebook_page_new_with_files
creates notebook
pages with a contents read from the given files. The function
tfe_window_new
creates a TfeWindow instance.
#include <gtk/gtk.h>
#include "tfewindow.h"
#include "tfenotebook.h"
#include "tfepref.h"
#include "tfealert.h"
struct _TfeWindow {
GtkApplicationWindow parent;
GtkMenuButton *btnm;
GtkNotebook *nb;
gboolean is_quit;
};
G_DEFINE_TYPE (TfeWindow, tfe_window, GTK_TYPE_APPLICATION_WINDOW);
/* alert response signal handler */
static void
alert_response_cb (GtkDialog *alert, int response_id, gpointer user_data) {
TfeWindow *win = TFE_WINDOW (user_data);
gtk_window_destroy (GTK_WINDOW (alert));
if (response_id == GTK_RESPONSE_ACCEPT) {
if (win->is_quit)
gtk_window_destroy(GTK_WINDOW (win));
else
notebook_page_close (win->nb);
}
}
static gboolean
close_request_cb (TfeWindow *win) {
TfeAlert *alert;
if (has_saved_all (GTK_NOTEBOOK (win->nb)))
return false;
else {
win->is_quit = true;
alert = TFE_ALERT (tfe_alert_new ());
gtk_window_set_transient_for (GTK_WINDOW (alert), GTK_WINDOW (win));
tfe_alert_set_message (alert, "Contents aren't saved yet.\nAre you sure to quit?");
tfe_alert_set_button_label (alert, "Quit");
g_signal_connect (GTK_DIALOG (alert), "response", G_CALLBACK (alert_response_cb), win);
gtk_window_present (GTK_WINDOW (alert));
return true;
}
}
/* ----- action activated handlers ----- */
static void
open_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
TfeWindow *win = TFE_WINDOW (user_data);
notebook_page_open (GTK_NOTEBOOK (win->nb));
}
static void
save_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
TfeWindow *win = TFE_WINDOW (user_data);
notebook_page_save (GTK_NOTEBOOK (win->nb));
}
static void
close_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
TfeWindow *win = TFE_WINDOW (user_data);
TfeAlert *alert;
if (has_saved (win->nb))
notebook_page_close (win->nb);
else {
win->is_quit = false;
alert = TFE_ALERT (tfe_alert_new ());
gtk_window_set_transient_for (GTK_WINDOW (alert), GTK_WINDOW (win));
tfe_alert_set_message (alert, "Contents aren't saved yet.\nAre you sure to close?");
tfe_alert_set_button_label (alert, "Close");
g_signal_connect (GTK_DIALOG (alert), "response", G_CALLBACK (alert_response_cb), win);
gtk_widget_show (GTK_WIDGET (alert));
}
}
static void
new_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
TfeWindow *win = TFE_WINDOW (user_data);
notebook_page_new (GTK_NOTEBOOK (win->nb));
}
static void
saveas_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
TfeWindow *win = TFE_WINDOW (user_data);
notebook_page_saveas (GTK_NOTEBOOK (win->nb));
}
static void
pref_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
TfeWindow *win = TFE_WINDOW (user_data);
GtkWidget *pref;
pref = tfe_pref_new ();
gtk_window_set_transient_for (GTK_WINDOW (pref), GTK_WINDOW (win));
gtk_window_present (GTK_WINDOW (pref));
}
static void
close_all_activated (GSimpleAction *action, GVariant *parameter, gpointer user_data) {
TfeWindow *win = TFE_WINDOW (user_data);
if (close_request_cb (win) == false)
gtk_window_destroy (GTK_WINDOW (win));
}
/* --- public functions --- */
void
tfe_window_notebook_page_new (TfeWindow *win) {
notebook_page_new (win->nb);
}
void
tfe_window_notebook_page_new_with_files (TfeWindow *win, GFile **files, int n_files) {
int i;
for (i = 0; i < n_files; i++)
notebook_page_new_with_file (win->nb, files[i]);
if (gtk_notebook_get_n_pages (win->nb) == 0)
notebook_page_new (win->nb);
}
static void
tfe_window_init (TfeWindow *win) {
GtkBuilder *build;
GMenuModel *menu;
gtk_widget_init_template (GTK_WIDGET (win));
build = gtk_builder_new_from_resource ("/com/github/ToshioCP/tfe/menu.ui");
menu = G_MENU_MODEL (gtk_builder_get_object (build, "menu"));
gtk_menu_button_set_menu_model (win->btnm, menu);
g_object_unref(build);
/* ----- action ----- */
const GActionEntry win_entries[] = {
{ "open", open_activated, NULL, NULL, NULL },
{ "save", save_activated, NULL, NULL, NULL },
{ "close", close_activated, NULL, NULL, NULL },
{ "new", new_activated, NULL, NULL, NULL },
{ "saveas", saveas_activated, NULL, NULL, NULL },
{ "pref", pref_activated, NULL, NULL, NULL },
{ "close-all", close_all_activated, NULL, NULL, NULL }
};
g_action_map_add_action_entries (G_ACTION_MAP (win), win_entries, G_N_ELEMENTS (win_entries), win);
g_signal_connect (GTK_WINDOW (win), "close-request", G_CALLBACK (close_request_cb), NULL);
}
static void
tfe_window_class_init (TfeWindowClass *class) {
gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class), "/com/github/ToshioCP/tfe/tfewindow.ui");
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeWindow, btnm);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeWindow, nb);
}
GtkWidget *
tfe_window_new (GtkApplication *app) {
return GTK_WIDGET (g_object_new (TFE_TYPE_WINDOW, "application", app, NULL));
}
_TfeWindow
structure. A TfeWindow instance points
the structure.G_DEFINE_TYPE
macro.alert_response_cb
is a call back function for
the “response” signal of TfeAlert dialog.has_saved_all
returns true, the handler returns
false and the window will close. Otherwise, it shows an alert
dialog.user_data
is a pointer to the TfeWindow instance.gtk_widget_init_template
creates a
child widgets and initializes them.menu
. It is inserted to the
menu button.tfe_window_new
. This function creates
TfeWindow instance.The file tfeaplication.h
and
tfeapplication.c
are now very simple. The following is the
header file.
#pragma once
#include <gtk/gtk.h>
#define TFE_TYPE_APPLICATION tfe_application_get_type ()
G_DECLARE_FINAL_TYPE (TfeApplication, tfe_application, TFE, APPLICATION, GtkApplication)
TfeApplication *
tfe_application_new (const char* application_id, GApplicationFlags flags);
#pragma once
isn’t an official pre-processor
command, but widely used. It makes the header file be read only
once.TFE_TYPE_APPLICATION
is defined as the type of
TfeApplication. G_DECLARE_FINAL_TYPE
is a macro used in the
header file to define a new object.tfe_application_new
creates a new
TfeApplication instance.The following is tfeapplication.c
. It defines the
application and supports:
#include <gtk/gtk.h>
#include "tfeapplication.h"
#include "tfewindow.h"
#include "pfd2css.h"
struct _TfeApplication {
GtkApplication parent;
TfeWindow *win;
GSettings *settings;
GtkCssProvider *provider;
};
G_DEFINE_TYPE (TfeApplication, tfe_application, GTK_TYPE_APPLICATION);
/* gsettings changed::font signal handler */
static void
changed_font_cb (GSettings *settings, char *key, gpointer user_data) {
TfeApplication *app = TFE_APPLICATION (user_data);
char *font, *s, *css;
PangoFontDescription *pango_font_desc;
font = g_settings_get_string (app->settings, "font");
pango_font_desc = pango_font_description_from_string (font);
g_free (font);
s = pfd2css (pango_font_desc); // converts Pango Font Description into CSS style string
css = g_strdup_printf ("textview {%s}", s);
gtk_css_provider_load_from_data (app->provider, css, -1);
g_free (s);
g_free (css);
}
/* ----- activate, open, startup handlers ----- */
static void
app_activate (GApplication *application) {
TfeApplication *app = TFE_APPLICATION (application);
tfe_window_notebook_page_new (app->win);
gtk_window_present (GTK_WINDOW (app->win));
}
static void
app_open (GApplication *application, GFile ** files, gint n_files, const gchar *hint) {
TfeApplication *app = TFE_APPLICATION (application);
tfe_window_notebook_page_new_with_files (app->win, files, n_files);
gtk_window_present (GTK_WINDOW (app->win));
}
static void
app_startup (GApplication *application) {
TfeApplication *app = TFE_APPLICATION (application);
GtkCssProvider *provider0;
GdkDisplay *display;
int i;
app->win = TFE_WINDOW (tfe_window_new (GTK_APPLICATION (app)));
provider0 = gtk_css_provider_new ();
gtk_css_provider_load_from_data (provider0, "textview {padding: 10px;}", -1);
display = gdk_display_get_default ();
gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider0),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
g_object_unref (provider0);
app->provider = gtk_css_provider_new ();
gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (app->provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
app->settings = g_settings_new ("com.github.ToshioCP.tfe");
g_signal_connect (app->settings, "changed::font", G_CALLBACK (changed_font_cb), app);
changed_font_cb(app->settings, "font", app);
/* ----- accelerator ----- */
struct {
const char *action;
const char *accels[2];
} action_accels[] = {
{ "win.open", { "<Control>o", NULL } },
{ "win.save", { "<Control>s", NULL } },
{ "win.close", { "<Control>w", NULL } },
{ "win.new", { "<Control>n", NULL } },
{ "win.saveas", { "<Shift><Control>s", NULL } },
{ "win.close-all", { "<Control>q", NULL } },
};
for (i = 0; i < G_N_ELEMENTS(action_accels); i++)
gtk_application_set_accels_for_action(GTK_APPLICATION(app), action_accels[i].action, action_accels[i].accels);
}
static void
tfe_application_dispose (GObject *gobject) {
TfeApplication *app = TFE_APPLICATION (gobject);
g_clear_object (&app->settings);
g_clear_object (&app->provider);
G_OBJECT_CLASS (tfe_application_parent_class)->dispose (gobject);
}
static void
tfe_application_init (TfeApplication *app) {
g_signal_connect (G_APPLICATION (app), "startup", G_CALLBACK (app_startup), NULL);
g_signal_connect (G_APPLICATION (app), "activate", G_CALLBACK (app_activate), NULL);
g_signal_connect (G_APPLICATION (app), "open", G_CALLBACK (app_open), NULL);
}
static void
tfe_application_class_init (TfeApplicationClass *class) {
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->dispose = tfe_application_dispose;
}
TfeApplication *
tfe_application_new (const char* application_id, GApplicationFlags flags) {
return TFE_APPLICATION (g_object_new (TFE_TYPE_APPLICATION, "application-id", application_id, "flags", flags, NULL));
}
_TfeApplication
structure. The members
are:
G_DEFINE_TYPE
macro.changed_font_cb
is a handler for “changed::font”
signal on the GSettings instance. The signal name is “changed” and
“font” is a key name. When the valeu of “font” key is changed, the
signal is emitted. So, this handler doesn’t directly relate to the font
button, but through the GSettings database. A user changes the font in
the font button => GSettings font key data is changed => the
handler is called.tfe_window_notebook_page_new
instead of
notebook_page_new
.tfe_window_notebook_page_new_with_files
and shows the main
window. Be careful that the activate and open handlers don’t create a
new window. They just create a new notebook page. Therefore, even if the
second application runs, no new window appears. Just a new notebook page
is inserted to the same main window.app->win
.provider0
). It includes
only the padding data for the textview. The provider is inserted to the
default display.app->provider
) and inserted to the default display. It
will include the font data for the textview.changed_font_cb
is called to set the CSS with
the font data. The handler gets the font data from the GSettings data
which is the last font in the previous run of the application.tfe_application_new
creates a new
TfeApplication instance. The parameters are an application-id and
flags.main.c
#include <gtk/gtk.h>
#include "tfeapplication.h"
#define APPLICATION_ID "com.github.ToshioCP.tfe"
int
main (int argc, char **argv) {
TfeApplication *app;
int stat;
app = tfe_application_new (APPLICATION_ID, G_APPLICATION_HANDLES_OPEN);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}
CSS related files pfd2css.h
and pfd2css.c
are the same as the previous section.
Resource XML file.
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/com/github/ToshioCP/tfe">
<file>tfewindow.ui</file>
<file>tfepref.ui</file>
<file>tfealert.ui</file>
<file>menu.ui</file>
</gresource>
</gresources>
GSchema XML file
<?xml version="1.0" encoding="UTF-8"?>
<schemalist>
<schema path="/com/github/ToshioCP/tfe/" id="com.github.ToshioCP.tfe">
<key name="font" type="s">
<default>'Monospace 12'</default>
<summary>Font</summary>
<description>A font to be used for textview.</description>
</key>
</schema>
</schemalist>
Meson.build
project('tfe', 'c')
gtkdep = dependency('gtk4')
gnome=import('gnome')
resources = gnome.compile_resources('resources','tfe.gresource.xml')
gnome.compile_schemas(build_by_default: true, depend_files: 'com.github.ToshioCP.tfe.gschema.xml')
sourcefiles=files('main.c', 'tfeapplication.c', 'tfewindow.c', 'tfenotebook.c', 'tfepref.c', 'tfealert.c', 'pfd2css.c', '../tfetextview/tfetextview.c')
executable('tfe', sourcefiles, resources, dependencies: gtkdep, export_dynamic: true, install: true)
schema_dir = get_option('prefix') / get_option('datadir') / 'glib-2.0/schemas/'
install_data('com.github.ToshioCP.tfe.gschema.xml', install_dir: schema_dir)
gnome.post_install (glib_compile_schemas: true)
If you want to install it to your local area, use
--prefix=$HOME/.local
or --prefix=$HOME
option. If you want to install it to the system area, no option is
needed. It will be installed under /user/local
directory.
$ meson --prefix=$HOME/.local _build
$ ninja -C _build
$ ninja -C _build install
You need root privilege to install it to the system area..
$ meson _build
$ ninja -C _build
$ sudo ninja -C _build install
Source files are in src/tfe7 directory.
Composite widgets give us two advantages.
We made a very small text editor. You can add features to this editor. When you add a new feature, be careful about the structure of the program. Maybe you need to divide a file into several files like this section. It isn’t good to put many things into one file. And it is important to think about the relationship between source files and widget structures.