The source files are in the Gtk4 tutorial GitHub
repository. Download it and see src/tfe6
directory.
Tfe text editor will be restructured. The program is divided into six parts.
This section describes TfeAlert. Others will be explained in the following sections.
The alert dialog is like this:
Tfe uses it when a user quits the application or closes a notebook without saving data to files.
The dialog has a title, buttons, an icon and a message. Therefore, it consists of several widgets. Such dialog is called a composite widget.
Composite widgets are defined with template XMLs. The class is built in the class initialization function and the instances are built and desposed by the following functions.
TfeAlert is a good example to know composite widgets. It is defined with the three files.
A template tag is used in a composite widget XML.
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="TfeAlert" parent="GtkWindow">
<property name="resizable">FALSE</property>
<property name="modal">TRUE</property>
<property name="titlebar">
<object class="GtkHeaderBar">
<property name="show-title-buttons">FALSE</property>
<property name="title-widget">
<object class="GtkLabel" id="lb_title">
<property name="label">Are you sure?</property>
<property name="single-line-mode">True</property>
</object>
</property>
<child type="start">
<object class="GtkButton" id="btn_cancel">
<property name="label">Cancel</property>
<style>
<class name="suggested-action"/>
</style>
<signal name="clicked" handler="cancel_cb" swapped="TRUE" object="TfeAlert"></signal>
</object>
</child>
<child type="end">
<object class="GtkButton" id="btn_accept">
<property name="label">Close</property>
<style>
<class name="destructive-action"/>
</style>
<signal name="clicked" handler="accept_cb" swapped="TRUE" object="TfeAlert"></signal>
</object>
</child>
</object>
</property>
<child>
<object class="GtkBox">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<property name="spacing">12</property>
<property name="margin-top">12</property>
<property name="margin-bottom">12</property>
<property name="margin-start">12</property>
<property name="margin-end">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_message">
</object>
</child>
</object>
</child>
</template>
</interface>
type="start"
attribute.
The accept button is on the right so the child tag has
type="end"
attribute. The dialog is shown when the user
clicked the close button or the quit menu without saving the data.
Therefore, it is safer for the user to click on the cancel button of the
alert dialog. So, the cancel button has a “suggested-action” CSS class.
Ubuntu colors the button green but the color can be blue or other
appropriate one defined by the system. In the same way the accept button
has a “destructive-action” CSS class and is colored red. Two buttons
have signals which are defined by the signal tags.$ gtk4-icon-browser
The “dialog-warning” icon is something like this.
These are made by my hand. The real image on the alert dialog is nicer.
It is possible to define the alert widget as a child of GtkDialog. But GtkDialog is deprecated since GTK version 4.10. And users should use GtkWindow instead of GtkDialog.
The header file is similar to the one of TfeTextView.
#pragma once
#include <gtk/gtk.h>
#define TFE_TYPE_ALERT tfe_alert_get_type ()
G_DECLARE_FINAL_TYPE (TfeAlert, tfe_alert, TFE, ALERT, GtkWindow)
/* "response" signal id */
enum TfeAlertResponseType
{
TFE_ALERT_RESPONSE_ACCEPT,
TFE_ALERT_RESPONSE_CANCEL
};
const char *
tfe_alert_get_title (TfeAlert *alert);
const char *
tfe_alert_get_message (TfeAlert *alert);
const char *
tfe_alert_get_button_label (TfeAlert *alert);
void
tfe_alert_set_title (TfeAlert *alert, const char *title);
void
tfe_alert_set_message (TfeAlert *alert, const char *message);
void
tfe_alert_set_button_label (TfeAlert *alert, const char *btn_label);
GtkWidget *
tfe_alert_new (void);
GtkWidget *
tfe_alert_new_with_data (const char *title, const char *message, const char* btn_label);
TFE_TYPE_ALERT
is the type of TfeAlert object and it is a
macro expanded into tfe_alert_get_type ()
.
G_DECLARE_FINAL_TYPE macro is expanded into:
tfe_alert_get_type
TfeAlert
is defined as a typedef of
struct _TfeAlert
, which is defined in the C file.TFE_ALERT
and TFE_IS_ALERT
macro is
defined as a cast and type check function.TfeAlertClass
structure is defined as a final
class.TfeAlertResponseType
enumerative constant.tfe_alert_new_with_data
is a convenience function, which
creates an instance and sets data at once.The following codes are extracted from tfealert.c
.
#include <gtk/gtk.h>
#include "tfealert.h"
struct _TfeAlert {
;
GtkWindow parent*lb_title;
GtkLabel *lb_message;
GtkLabel *btn_accept;
GtkButton *btn_cancel;
GtkButton };
(TfeAlert, tfe_alert, GTK_TYPE_WINDOW);
G_DEFINE_FINAL_TYPE
static void
(TfeAlert *alert) {
cancel_cb ... ... ...
}
static void
(TfeAlert *alert) {
accept_cb ... ... ...
}
static void
(GObject *gobject) { // gobject is actually a TfeAlert instance.
tfe_alert_dispose (GTK_WIDGET (gobject), TFE_TYPE_ALERT);
gtk_widget_dispose_template (tfe_alert_parent_class)->dispose (gobject);
G_OBJECT_CLASS }
static void
(TfeAlert *alert) {
tfe_alert_init (GTK_WIDGET (alert));
gtk_widget_init_template }
static void
(TfeAlertClass *class) {
tfe_alert_class_init (class)->dispose = tfe_alert_dispose;
G_OBJECT_CLASS (GTK_WIDGET_CLASS (class), "/com/github/ToshioCP/tfe/tfealert.ui");
gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class), TfeAlert, lb_title);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeAlert, lb_message);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeAlert, btn_accept);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeAlert, btn_cancel);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), cancel_cb);
gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), accept_cb);
gtk_widget_class_bind_template_callback ... ... ...
}
*
GtkWidget (void) {
tfe_alert_new return GTK_WIDGET (g_object_new (TFE_TYPE_ALERT, NULL));
}
G_DEFINE_FINAL_TYPE
is available since GLib
version 2.70. It is used only for a final type class. You can use
G_DEFINE_TYPE
macro instead. They are expanded into:
tfe_alert_init
and
tfe_alert_class_init
. They are defined in the following
part of the C program.tfe_alert_parent_class
.tfe_alert_get_type
._TfeAlert
, which are
lb_title
, lb_message
, btn_accept
and btn_cancel
, must be the same as the id attribute in the
XML file tfealert.ui
.tfe_alert_class_init
initializes the
composite widget class.
gtk_widget_class_set_template_from_resource
sets the
template of the class. The template is built from the XML resource
“tfealert.ui”. 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.gtk_widget_class_bind_template_child
connects the member of TfeAlert and the object class in the template.
So, for example, you can access to lb_title
GtkLabel
instance via alert->lb_title
where alert
is
an instance of TfeAlert class.gtk_widget_class_bind_template_callback
connects the callback function and the handler
attribute of
the signal tag in the XML. For example, the “clicked” signal on the
cancel button has a handler named “cancel_cb” in the signal tag. And the
function cancel_cb
exists in the C file above. These two
are connected so when the signal is emitted the function
cancel_cb
is called. You can add static
storage class to the callback function thanks to this connection.tfe_alert_init
initializes the newly
created instance. You need to call gtk_widget_init_template
to create and initialize the child widgets in the template.tfe_alert_despose
releases objects. The
function gtk_widget_despose_template
clears the template
children.tfe_alert_new
creates the composite widget
TfeAlert instance. It creates not only TfeAlert itself but also all the
child widgets that the composite widget has.The following is the full codes of tfealert.c
.
#include <gtk/gtk.h>
#include "tfealert.h"
struct _TfeAlert {
GtkWindow parent;
GtkLabel *lb_title;
GtkLabel *lb_message;
GtkButton *btn_accept;
GtkButton *btn_cancel;
};
G_DEFINE_FINAL_TYPE (TfeAlert, tfe_alert, GTK_TYPE_WINDOW);
enum {
RESPONSE,
NUMBER_OF_SIGNALS
};
static guint tfe_alert_signals[NUMBER_OF_SIGNALS];
static void
cancel_cb (TfeAlert *alert) {
g_signal_emit (alert, tfe_alert_signals[RESPONSE], 0, TFE_ALERT_RESPONSE_CANCEL);
gtk_window_destroy (GTK_WINDOW (alert));
}
static void
accept_cb (TfeAlert *alert) {
g_signal_emit (alert, tfe_alert_signals[RESPONSE], 0, TFE_ALERT_RESPONSE_ACCEPT);
gtk_window_destroy (GTK_WINDOW (alert));
}
const char *
tfe_alert_get_title (TfeAlert *alert) {
return gtk_label_get_text (alert->lb_title);
}
const char *
tfe_alert_get_message (TfeAlert *alert) {
return gtk_label_get_text (alert->lb_message);
}
const char *
tfe_alert_get_button_label (TfeAlert *alert) {
return gtk_button_get_label (alert->btn_accept);
}
void
tfe_alert_set_title (TfeAlert *alert, const char *title) {
gtk_label_set_text (alert->lb_title, title);
}
void
tfe_alert_set_message (TfeAlert *alert, const char *message) {
gtk_label_set_text (alert->lb_message, message);
}
void
tfe_alert_set_button_label (TfeAlert *alert, const char *btn_label) {
gtk_button_set_label (alert->btn_accept, btn_label);
}
static void
tfe_alert_dispose (GObject *gobject) { // gobject is actually a TfeAlert instance.
gtk_widget_dispose_template (GTK_WIDGET (gobject), TFE_TYPE_ALERT);
G_OBJECT_CLASS (tfe_alert_parent_class)->dispose (gobject);
}
static void
tfe_alert_init (TfeAlert *alert) {
gtk_widget_init_template (GTK_WIDGET (alert));
}
static void
tfe_alert_class_init (TfeAlertClass *class) {
G_OBJECT_CLASS (class)->dispose = tfe_alert_dispose;
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_title);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeAlert, lb_message);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeAlert, btn_accept);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), TfeAlert, btn_cancel);
gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), cancel_cb);
gtk_widget_class_bind_template_callback (GTK_WIDGET_CLASS (class), accept_cb);
tfe_alert_signals[RESPONSE] = g_signal_new ("response",
G_TYPE_FROM_CLASS (class),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
0 /* class offset */,
NULL /* accumulator */,
NULL /* accumulator data */,
NULL /* C marshaller */,
G_TYPE_NONE /* return_type */,
1 /* n_params */,
G_TYPE_INT
);
}
GtkWidget *
tfe_alert_new (void) {
return GTK_WIDGET (g_object_new (TFE_TYPE_ALERT, NULL));
}
GtkWidget *
tfe_alert_new_with_data (const char *title, const char *message, const char* btn_label) {
GtkWidget *alert = tfe_alert_new ();
tfe_alert_set_title (TFE_ALERT (alert), title);
tfe_alert_set_message (TFE_ALERT (alert), message);
tfe_alert_set_button_label (TFE_ALERT (alert), btn_label);
return alert;
}
The function tfe_alert_new_with_data
is used more often
than tfe_alert_new
to create a new instance. It creates the
instance and sets three data at the same time. The following is the
common process when you use the TfeAlert class.
tfe_alert_new_with_data
and create an
instance.gtk_window_set_transient_for
to set the transient
parent window.gtk_window_present
to show the TfeAlert
dialog.The rest of the program is:
tfe_alert_new_with_data
creates an instance and sets labels.There’s an example in the src/tfe6/example
directory. It
shows how to use TfeAlert. The program is
src/example/ex_alert.c
.
#include <gtk/gtk.h>
#include "../tfealert.h"
static void
alert_response_cb (TfeAlert *alert, int response, gpointer user_data) {
if (response == TFE_ALERT_RESPONSE_ACCEPT)
g_print ("%s\n", tfe_alert_get_button_label (alert));
else if (response == TFE_ALERT_RESPONSE_CANCEL)
g_print ("Cancel\n");
else
g_print ("Unexpected error\n");
}
static void
app_activate (GApplication *application) {
GtkWidget *alert;
char *title, *message, *btn_label;
title = "Example for TfeAlert"; message = "Click on Cancel or Accept button"; btn_label = "Accept";
alert = tfe_alert_new_with_data (title, message, btn_label);
g_signal_connect (TFE_ALERT (alert), "response", G_CALLBACK (alert_response_cb), NULL);
gtk_window_set_application (GTK_WINDOW (alert), GTK_APPLICATION (application));
gtk_window_present (GTK_WINDOW (alert));
}
static void
app_startup (GApplication *application) {
}
#define APPLICATION_ID "com.github.ToshioCP.example_tfe_alert"
int
main (int argc, char **argv) {
GtkApplication *app;
int stat;
app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}
The “activate” signal handler app_activate
initializes
the alert dialog.
alert_response_cb
.gtk_window_set_application
does that.A user clicks on either the cancel button or the accept button. Then,
the “response” signal is emitted and the dialog is destroyed. The signal
handler alert_response_cb
checks the response and prints
“Accept” or “Cancel”. If an error happens, it prints “Unexpected
error”.
You can compile it with meson and ninja.
$ cd src/tfe6/example
$ meson setup _build
$ ninja -C _build
$ _build/ex_alert
Accept #<= if you clicked on the accept button