If the user clicks on the preference menu, a preference dialog appears.
It has only one button, which is a GtkFontDialogButton widget. You can add more widgets on the dialog but this simple dialog isn’t so bad for the first example program.
If the button is clicked, a FontDialog appears like this.
If the user chooses a font and clicks on the select button, the font is changed.
GtkFontDialogButton and GtkFontDialog are available since GTK version 4.10. They replace GtkFontButton and GtkFontChooserDialog, which are deprecated since 4.10.
The preference dialog has GtkBox, GtkLabel and GtkFontButton in it and is defined as a composite widget. The following is the template ui file for TfePref.
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="TfePref" parent="GtkWindow">
<property name="title">Preferences</property>
<property name="resizable">FALSE</property>
<property name="modal">TRUE</property>
<child>
<object class="GtkBox">
<property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
<property name="spacing">12</property>
<property name="halign">GTK_ALIGN_CENTER</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">
<property name="label">Font:</property>
<property name="xalign">1</property>
</object>
</child>
<child>
<object class="GtkFontDialogButton" id="font_dialog_btn">
<property name="dialog">
<object class="GtkFontDialog"/>
</property>
</object>
</child>
</object>
</child>
</template>
</interface>
GtkWindow
. Therefore. TfePref
is a child class
of GtkWindow
. A parent attribute is optional but it is
recommended to write it explicitly. You can make TfePref as a child of
GtkDialog
, but GtkDialog
is deprecated since
version 4.10.The file tfepref.h
defines types and declares a public
function.
#pragma once
#include <gtk/gtk.h>
#define TFE_TYPE_PREF tfe_pref_get_type ()
G_DECLARE_FINAL_TYPE (TfePref, tfe_pref, TFE, PREF, GtkWindow)
GtkWidget *
tfe_pref_new (void);
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 {GtkWindowClass *parent;}
.TFE_PREF ()
and
TFE_IS_PREF ()
is defined.tfe_pref_new
is declared. It creates a
new TfePref instance.The following codes are extracted from the file
tfepref.c
.
#include <gtk/gtk.h>
#include "tfepref.h"
struct _TfePref
{
;
GtkWindow parent*font_dialog_btn;
GtkFontDialogButton };
(TfePref, tfe_pref, GTK_TYPE_WINDOW);
G_DEFINE_FINAL_TYPE
static void
(GObject *gobject) {
tfe_pref_dispose *pref = TFE_PREF (gobject);
TfePref (GTK_WIDGET (pref), TFE_TYPE_PREF);
gtk_widget_dispose_template (tfe_pref_parent_class)->dispose (gobject);
G_OBJECT_CLASS }
static void
(TfePref *pref) {
tfe_pref_init (GTK_WIDGET (pref));
gtk_widget_init_template }
static void
(TfePrefClass *class) {
tfe_pref_class_init (class)->dispose = tfe_pref_dispose;
G_OBJECT_CLASS (GTK_WIDGET_CLASS (class), "/com/github/ToshioCP/tfe/tfepref.ui");
gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class), TfePref, font_dialog_btn);
gtk_widget_class_bind_template_child }
*
GtkWidget (void) {
tfe_pref_new return GTK_WIDGET (g_object_new (TFE_TYPE_PREF, NULL));
}
_TfePref
has font_dialog_btn
member. It points the GtkFontDialogButton object specified in the XML
file “tfepref.ui”. The member name font_dialog_btn
must be
the same as the GtkFontDialogButton id attribute in the XML file.G_DEFINE_FINAL_TYPE
macro expands to:
tfe_pref_init
and
tfe_pref_class_init
. They are defined in the following part
of the program.tfe_pref_parent_class
.tfe_pref_get_type
.tfe_pref_class_init
initializes the
TfePref class. The function
gtk_widget_class_set_template_from_resource
initializes the
composite widget template from the XML resource. The function
gtk_widget_class_bind_template_child
connects the TfePref
structure member font_dialog_btn
and the
GtkFontDialogButton in the XML. The member name and the id attribute
value must be the same.tfe_pref_init
initializes a newly created
instance. The function gtk_widget_init_template
creates and
initializes child widgets.tfe_pref_dispose
releases objects. The
function gtk_widget_dispose_template
releases child
widgets.If the GtkFontDialogButton button is clicked, the GtkFontDialog
dialog appears. A user can choose a font on the dialog. If the user
clicks on the “select” button, the dialog disappears. And the font
information is given to the GtkFontDialogButton instance. The font data
is taken with the method
gtk_font_dialog_button_get_font_desc
. It returns a pointer
to the PangoFontDescription structure.
Pango is a text layout engine. The documentation is on the internet.
PangoFontDescription is a C structure and it isn’t allowed to access directly. The document is here. If you want to retrieve the font information, there are several functions.
pango_font_description_to_string
returns a string like
“Jamrul Bold Italic Semi-Expanded 12”.pango_font_description_get_family
returns a font family
like “Jamrul”.pango_font_description_get_weight
returns a PangoWeight
constant like PANGO_WEIGHT_BOLD
.pango_font_description_get_style
returns a PangoStyle
constant like PANGO_STYLE_ITALIC
.pango_font_description_get_stretch
returns a
PangoStretch constant like
PANGO_STRETCH_SEMI_EXPANDED
.pango_font_description_get_size
returns an integer like
12
. Its unit is point or pixel (device unit). The function
pango_font_description_get_size_is_absolute
returns TRUE if
the unit is absolute that means device unit. Otherwise the unit is
point.We want to maintain the font data after the application quits. There are some ways to implement.
GSettings is simple and easy to use but a bit hard to understand the concept. This subsection describes the concept first and then how to program it.
GSettings schema describes a set of keys, value types and some other information. GSettings object uses this schema and it writes/reads the value of a key to/from the right place in the database.
font-desc
is defined with a path
/com/github/ToshioCP/tfe/
, the key’s location in the
database is /com/github/ToshioCP/tfe/font-desc
. Path is a
string begins with and ends with a slash (/
). And it is
delimited by slashes.-
) and ends with lower case or digit. No consecutive
dashes are allowed. Values can be any type. GSettings stores values as
GVariant type, which can be, for example, integer, double, boolean,
string or complex types like an array. The type of values needs to be
defined in the schema.Schemas are described in an XML format. For example,
<?xml version="1.0" encoding="UTF-8"?>
<schemalist>
<schema path="/com/github/ToshioCP/tfe/" id="com.github.ToshioCP.tfe">
<key name="font-desc" type="s">
<default>'Monospace 12'</default>
<summary>Font</summary>
<description>A font to be used for textview.</description>
</key>
</schema>
</schemalist>
Further information is in:
First, let’s try gsettings
application. It is a
configuration tool for GSettings.
$ gsettings help
Usage:
gsettings --version
gsettings [--schemadir SCHEMADIR] COMMAND [ARGS?]
Commands:
help Show this information
list-schemas List installed schemas
list-relocatable-schemas List relocatable schemas
list-keys List keys in a schema
list-children List children of a schema
list-recursively List keys and values, recursively
range Queries the range of a key
describe Queries the description of a key
get Get the value of a key
set Set the value of a key
reset Reset the value of a key
reset-recursively Reset all values in a given schema
writable Check if a key is writable
monitor Watch for changes
Use "gsettings help COMMAND" to get detailed help.
List schemas.
$ gsettings list-schemas
org.gnome.rhythmbox.podcast
ca.desrt.dconf-editor.Demo.Empty
org.gnome.gedit.preferences.ui
org.gnome.evolution-data-server.calendar
org.gnome.rhythmbox.plugins.generic-player
... ...
Each line is an id of a schema. Each schema has a key-value
configuration data. You can see them with list-recursively command.
Let’s look at the keys and values of org.gnome.calculator
schema.
$ gsettings list-recursively org.gnome.calculator
org.gnome.calculator accuracy 9
org.gnome.calculator angle-units 'degrees'
org.gnome.calculator base 10
org.gnome.calculator button-mode 'basic'
org.gnome.calculator number-format 'automatic'
org.gnome.calculator precision 2000
org.gnome.calculator refresh-interval 604800
org.gnome.calculator show-thousands false
org.gnome.calculator show-zeroes false
org.gnome.calculator source-currency ''
org.gnome.calculator source-units 'degree'
org.gnome.calculator target-currency ''
org.gnome.calculator target-units 'radian'
org.gnome.calculator window-position (-1, -1)
org.gnome.calculator word-size 64
This schema is used by GNOME Calculator. Run the calculator and change the mode, then check the schema again.
$ gnome-calculator
Change the mode to advanced and quit.
Run gsettings and check the value of button-mode
.
$ gsettings list-recursively org.gnome.calculator
... ...
org.gnome.calculator button-mode 'advanced'
... ...
Now we know that GNOME Calculator used gsettings and it has set
button-mode
key to “advanced”. The value remains even the
calculator quits. So when the calculator runs again, it will appear as
an advanced mode.
GSettings schemas are specified with an XML format. The XML schema
files must have the filename extension .gschema.xml
. The
following is the XML schema file for the application
tfe
.
<?xml version="1.0" encoding="UTF-8"?>
<schemalist>
<schema path="/com/github/ToshioCP/tfe/" id="com.github.ToshioCP.tfe">
<key name="font-desc" type="s">
<default>'Monospace 12'</default>
<summary>Font</summary>
<description>A font to be used for textview.</description>
</key>
</schema>
</schemalist>
The filename is “com.github.ToshioCP.tfe.gschema.xml”. Schema XML filenames are usually the schema id followed by “.gschema.xml” suffix. You can use different name from schema id, but it is not recommended.
<schemalist>
.path
and id
attributes.
A path determines where the settings are stored in the conceptual global
tree of settings. An id identifies the schema.font-desc
is
Monospace 12
.The XML file is compiled by glib-compile-schemas. When compiling,
glib-compile-schemas
compiles all the XML files which have
“.gschema.xml” file extension in the directory given as an argument. It
converts the XML file into a binary file gschemas.compiled
.
Suppose the XML file above is under tfe6
directory.
$ glib-compile-schemas tfe6
Then, gschemas.compiled
is generated under
tfe6
. When you test your application, set
GSETTINGS_SCHEMA_DIR
environment variable so that GSettings
objet can find gschemas.compiled
.
$ GSETTINGS_SCHEMA_DIR=(the directory gschemas.compiled is located):$GSETTINGS_SCHEMA_DIR (your application name)
GSettings object looks for this file by the following process.
glib-2.0/schemas
subdirectories of all the
directories specified in the environment variable
XDG_DATA_DIRS
. Common directores are
/usr/share/glib-2.0/schemas
and
/usr/local/share/glib-2.0/schemas
.$HOME/.local/share/glib-2.0/schemas
exists, it is
also searched.GSETTINGS_SCHEMA_DIR
environment variable is
defined, it searches all the directories specified in the variable.
GSETTINGS_SCHEMA_DIR
can specify multiple directories
delimited by colon (:).The directories above includes more than one
.gschema.xml
file. Therefore, when you install your
application, follow the instruction below to install your schemas.
.gschema.xml
file.$HOME/.local/share/glib-2.0/schemas
.glib-compile-schemas
on the directory. It compiles
all the schema files in the directory and creates or updates the
database file gschemas.compiled
.Now, we go on to the next topic, how to program GSettings.
You need to compile your schema file in advance.
Suppose id, key, class name and a property name are:
The example below uses g_settings_bind
. If you use it,
GSettings key and instance property must have the same the type. In the
example, it is assumed that the type of “sample_key” and
“sample_property” are the same.
*settings;
GSettings *sample_object;
Sample
= g_settings_new ("com.github.ToshioCP.sample");
settings = sample_new ();
sample_object (settings, "sample_key", sample_object, "sample_property", G_SETTINGS_BIND_DEFAULT); g_settings_bind
The function g_settings_bind
binds the GSettings value
and the property of the instance. If the property value is changed, the
GSettings value is also changed, and vice versa. The two values are
always the same.
The function g_settings_bind
is simple and easy but it
isn’t always possible. The type of GSettings are restricted to the type
GVariant has. Some property types are out of GVariant. For example,
GtkFontDialogButton has “font-desc” property and its type is
PangoFontDescription. PangoFontDescription is a C structure and it is
wrapped in a boxed type GValue to store in the property. GVariant
doesn’t support boxed type.
In that case, another function
g_settings_bind_with_mapping
is used. It binds GSettings
GVariant value and object property via GValue with mapping
functions.
void
(
g_settings_bind_with_mapping * settings,
GSettingsconst gchar* key,
* object,
GObjectconst gchar* property,
, // G_SETTINGS_BIND_DEFAULT is commonly used
GSettingsBindFlags flags, // GSettings => property, See the example below
GSettingsBindGetMapping get_mapping, // property => GSettings, See the example below
GSettingsBindSetMapping set_mapping, // NULL if unnecessary
gpointer user_data//NULL if unnecessary
GDestroyNotify destroy )
The mapping functions are defined like these:
gboolean(* GSettingsBindGetMapping) (
* value,
GValue* variant,
GVariant
gpointer user_data)
*
GVariant(* GSettingsBindSetMapping) (
const GValue* value,
const GVariantType* expected_type,
gpointer user_data)
The following codes are extracted from tfepref.c
.
static gboolean // GSettings => property
get_mapping (GValue* value, GVariant* variant, gpointer user_data) {
const char *s = g_variant_get_string (variant, NULL);
PangoFontDescription *font_desc = pango_font_description_from_string (s);
g_value_take_boxed (value, font_desc);
return TRUE;
}
static GVariant* // Property => GSettings
set_mapping (const GValue* value, const GVariantType* expected_type, gpointer user_data) {
char*font_desc_string = pango_font_description_to_string (g_value_get_boxed (value));
return g_variant_new_take_string (font_desc_string);
}
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_with_mapping (pref->settings, "font-desc", pref->font_dialog_btn, "font-desc", G_SETTINGS_BIND_DEFAULT,
get_mapping, set_mapping, NULL, NULL);
}
tfe_pref_init
initializes the new
TfePref instance.get_mapping
and set_mapping
.value
is a GValue to be stored in the property.
The second argument variant
is a GVarinat structure that
comes from the GSettings value.font_desc
.font_desc
into the GValue value
.
The ownership of font_desc
moves to
value
.value
holds the property data. The second argument
expected_type
is the type of GVariant that the GSettings
value has. It isn’t used in this function.value
and converts it to string.font_desc_string
moves to the returned
value.The following is the full codes of tfepref.c
#include <gtk/gtk.h>
#include "tfepref.h"
struct _TfePref
{
GtkWindow parent;
GSettings *settings;
GtkFontDialogButton *font_dialog_btn;
};
G_DEFINE_FINAL_TYPE (TfePref, tfe_pref, GTK_TYPE_WINDOW);
static void
tfe_pref_dispose (GObject *gobject) {
TfePref *pref = TFE_PREF (gobject);
/* GSetting bindings are automatically removed when the object is finalized, so it isn't necessary to unbind them explicitly.*/
g_clear_object (&pref->settings);
gtk_widget_dispose_template (GTK_WIDGET (pref), TFE_TYPE_PREF);
G_OBJECT_CLASS (tfe_pref_parent_class)->dispose (gobject);
}
/* ---------- get_mapping/set_mapping ---------- */
static gboolean // GSettings => property
get_mapping (GValue* value, GVariant* variant, gpointer user_data) {
const char *s = g_variant_get_string (variant, NULL);
PangoFontDescription *font_desc = pango_font_description_from_string (s);
g_value_take_boxed (value, font_desc);
return TRUE;
}
static GVariant* // Property => GSettings
set_mapping (const GValue* value, const GVariantType* expected_type, gpointer user_data) {
char*font_desc_string = pango_font_description_to_string (g_value_get_boxed (value));
return g_variant_new_take_string (font_desc_string);
}
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_with_mapping (pref->settings, "font-desc", pref->font_dialog_btn, "font-desc", G_SETTINGS_BIND_DEFAULT,
get_mapping, set_mapping, NULL, NULL);
}
static void
tfe_pref_class_init (TfePrefClass *class) {
G_OBJECT_CLASS (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, font_dialog_btn);
}
GtkWidget *
tfe_pref_new (void) {
return GTK_WIDGET (g_object_new (TFE_TYPE_PREF, NULL));
}
There’s a test program located at src/tfe6/test
directory.
#include <gtk/gtk.h>
#include "../tfepref.h"
GSettings *settings;
// "changed::font-desc" signal handler
static void
changed_font_desc_cb (GSettings *settings, char *key, gpointer user_data) {
char *s;
s = g_settings_get_string (settings, key);
g_print ("%s\n", s);
g_free (s);
}
static void
app_shutdown (GApplication *application) {
g_object_unref (settings);
}
static void
app_activate (GApplication *application) {
GtkWidget *pref = tfe_pref_new ();
gtk_window_set_application (GTK_WINDOW (pref), GTK_APPLICATION (application));
gtk_window_present (GTK_WINDOW (pref));
}
static void
app_startup (GApplication *application) {
settings = g_settings_new ("com.github.ToshioCP.tfe");
g_signal_connect (settings, "changed::font-desc", G_CALLBACK (changed_font_desc_cb), NULL);
g_print ("%s\n", "Change the font with the font button. Then the new font will be printed out.\n");
}
#define APPLICATION_ID "com.github.ToshioCP.test_tfe_pref"
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);
g_signal_connect (app, "shutdown", G_CALLBACK (app_shutdown), NULL);
stat =g_application_run (G_APPLICATION (app), argc, argv);
g_object_unref (app);
return stat;
}
This program sets its active window to TfePref instance, which is a child object of GtkWindow.
It sets the “changed::font-desc” signal handler in the startup function. The process from the user’s font selection to the handler is:
The program building is divided into four steps.
Commands are shown in the next four sub-subsections. You don’t need to try them. The final sub-subsection shows the meson-ninja way, which is the easiest.
$ cd src/tef6/test
$ cp ../com.github.ToshioCP.tfe.gschema.xml com.github.ToshioCP.tfe.gschema.xml
$ glib-compile-schemas .
Be careful. The commands glib-compile-schemas
has an
argument “.”, which means the current directory. This results in
creating gschemas.compiled
file.
$ glib-compile-resources --sourcedir=.. --generate-source --target=resource.c ../tfe.gresource.xml
$ gcc `pkg-config --cflags gtk4` test_pref.c ../tfepref.c resource.c `pkg-config --libs gtk4`
$ GSETTINGS_SCHEMA_DIR=. ./a.out
Jamrul Italic Semi-Expanded 12 # <= select Jamrul Italic 12
Monospace 12 #<= select Monospace Regular 12
Meson wraps up the commands above. Create the following text and save
it to meson.build
.
Note: Gtk4-tutorial repository has meson.build file that defines several tests. So you can try it instead of the following text.
project('tfe_pref_test', 'c')
gtkdep = dependency('gtk4')
gnome=import('gnome')
resources = gnome.compile_resources('resources','../tfe.gresource.xml', source_dir: '..')
gnome.compile_schemas(build_by_default: true, depend_files: 'com.github.ToshioCP.tfe.gschema.xml')
executable('test_pref', ['test_pref.c', '../tfepref.c'], resources, dependencies: gtkdep, export_dynamic: true, install: false)
compile_resources
method. When you
call this method, you need the prefix “gnome.”.
compile_schemas
method. It compiles
the schema file ‘com.github.ToshioCP.tfe.gschema.xml’. You need to copy
‘../com.github.ToshioCP.tfe.gschema.xml’ to the current directory in
advance.resources
, which is made
by gnome.compile_resources
. It depends on
gtkdep
, which is GTK4 library. The symbols are exported and
no installation support.Type like this to build and test the program.
$ cd src/tef6/test
$ cp ../com.github.ToshioCP.tfe.gschema.xml com.github.ToshioCP.tfe.gschema.xml
$ meson setup _build
$ ninja -C _build
$ GSETTINGS_SCHEMA_DIR=_build _build/test_pref
A window appears and you can choose a font via GtkFontDialog. If you select a new font, the font string is output through the standard output.