Gtk4-tutorial/docs/sec20.html
2022-11-21 22:48:20 +09:00

1298 lines
128 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<title>GTK 4 tutorial</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
ul.task-list{list-style: none;}
pre{overflow: visible;}
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::after
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ff0000; font-weight: bold; } /* Alert */
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #7d9029; } /* Attribute */
code span.bn { color: #40a070; } /* BaseN */
code span.bu { } /* BuiltIn */
code span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4070a0; } /* Char */
code span.cn { color: #880000; } /* Constant */
code span.co { color: #60a0b0; font-style: italic; } /* Comment */
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
code span.dt { color: #902000; } /* DataType */
code span.dv { color: #40a070; } /* DecVal */
code span.er { color: #ff0000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #40a070; } /* Float */
code span.fu { color: #06287e; } /* Function */
code span.im { } /* Import */
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #007020; font-weight: bold; } /* Keyword */
code span.op { color: #666666; } /* Operator */
code span.ot { color: #007020; } /* Other */
code span.pp { color: #bc7a00; } /* Preprocessor */
code span.sc { color: #4070a0; } /* SpecialChar */
code span.ss { color: #bb6688; } /* SpecialString */
code span.st { color: #4070a0; } /* String */
code span.va { color: #19177c; } /* Variable */
code span.vs { color: #4070a0; } /* VerbatimString */
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
div.sourceCode { margin: 10px; padding: 16px 10px 8px 10px; border: 2px solid silver; background-color: ghostwhite; overflow-x:scroll}
pre:not(.sourceCode) { margin: 10px; padding: 16px 10px 8px 10px; border: 2px solid silver; background-color: ghostwhite; overflow-x:scroll}
table {margin-left: auto; margin-right: auto; border-collapse: collapse; border: 1px solid;}
th {padding: 2px 6px; border: 1px solid; background-color: ghostwhite;}
td {padding: 2px 6px; border: 1px solid;}
img {display: block; margin-left: auto; margin-right: auto;}
figcaption {text-align: center;}
</style>
</head>
<body style="padding-top: 70px;">
<div class="container">
<nav class="navbar fixed-top navbar-expand-lg navbar-dark bg-primary">
<div class="container-fluid">
<span class="navbar-brand">Gtk4 tutorial</span>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="index.html">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="sec19.html">Prev: section19</a>
</li>
<li class="nav-item">
<a class="nav-link" href="sec21.html">Next: section21</a>
</li>
</ul>
</div>
</div>
</nav>
<h1
id="gtkmenubutton-accelerators-font-pango-and-gsettings">GtkMenuButton,
accelerators, font, pango and gsettings</h1>
<p>Traditional menu structure is fine. However, buttons or menu items we
often use are not so many. Some mightnt be clicked at all. Therefore,
its a good idea to put some frequently used buttons on the toolbar and
put the rest of the less frequently used operations into the menu. Such
menu are often connected to GtkMenuButton.</p>
<p>We will restructure tfe text file editor in this section. It will be
more practical. The buttons are changed to:</p>
<ul>
<li>Put open, save and close buttons to the toolbar. In addition,
GtkMenuButton is added to the toolbar. This button shows a popup menu
when clicked on. Here, popup means widely, including pull-down
menu.</li>
<li>Put new, save as, preference and quit items to the menu under the
menu button.</li>
</ul>
<h2 id="signal-elements-in-ui-files">Signal elements in ui files</h2>
<p>The four buttons are included in the ui file <code>tfe.ui</code>. The
difference from prior sections is signal tag. The following is extracted
from <code>tfe.ui</code> and it describes the open button.</p>
<div class="sourceCode" id="cb1"><pre
class="sourceCode xml"><code class="sourceCode xml"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a>&lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkButton&quot;</span><span class="ot"> id=</span><span class="st">&quot;btno&quot;</span>&gt;</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;label&quot;</span>&gt;Open&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">signal</span><span class="ot"> name=</span><span class="st">&quot;clicked&quot;</span><span class="ot"> handler=</span><span class="st">&quot;open_cb&quot;</span><span class="ot"> swapped=</span><span class="st">&quot;TRUE&quot;</span><span class="ot"> object=</span><span class="st">&quot;nb&quot;</span>&gt;&lt;/<span class="kw">signal</span>&gt;</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>&lt;/<span class="kw">object</span>&gt;</span></code></pre></div>
<p>Signal tag specifies the name of the signal, handler and user_data
object. They are the value of name, handler and object attributes.
Swapped attribute has the same meaning as
<code>g_signal_connect_swapped</code> function. So, the signal tag above
works the same as the function below.</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a>g_signal_connect_swapped <span class="op">(</span>btno<span class="op">,</span> <span class="st">&quot;clicked&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>open_cb<span class="op">),</span> nb<span class="op">);</span></span></code></pre></div>
<p>You need to compile the source file with “-WI, export-dynamic”
options. You can achieve this by adding “export_dynamic: true” argument
to executable function in <code>meson.build</code>. And remove static
class from the handler.</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>open_cb <span class="op">(</span>GtkNotebook <span class="op">*</span>nb<span class="op">)</span> <span class="op">{</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a> notebook_page_open <span class="op">(</span>nb<span class="op">);</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>If you add static, the function is in the scope of the file and it
cant be seen from outside. Then the signal tag cant find the
function.</p>
<h2 id="menu-and-gkmenubutton">Menu and GkMenuButton</h2>
<p>Menus are described in <code>menu.ui</code> file.</p>
<div class="sourceCode" id="cb4"><pre
class="sourceCode numberSource xml numberLines"><code class="sourceCode xml"><span id="cb4-1"><a href="#cb4-1"></a><span class="fu">&lt;?xml</span><span class="ot"> version=</span><span class="st">&quot;1.0&quot;</span><span class="ot"> encoding=</span><span class="st">&quot;UTF-8&quot;</span><span class="fu">?&gt;</span></span>
<span id="cb4-2"><a href="#cb4-2"></a>&lt;<span class="kw">interface</span>&gt;</span>
<span id="cb4-3"><a href="#cb4-3"></a> &lt;<span class="kw">menu</span><span class="ot"> id=</span><span class="st">&quot;menu&quot;</span>&gt;</span>
<span id="cb4-4"><a href="#cb4-4"></a> &lt;<span class="kw">section</span>&gt;</span>
<span id="cb4-5"><a href="#cb4-5"></a> &lt;<span class="kw">item</span>&gt;</span>
<span id="cb4-6"><a href="#cb4-6"></a> &lt;<span class="kw">attribute</span><span class="ot"> name=</span><span class="st">&quot;label&quot;</span>&gt;New&lt;/<span class="kw">attribute</span>&gt;</span>
<span id="cb4-7"><a href="#cb4-7"></a> &lt;<span class="kw">attribute</span><span class="ot"> name=</span><span class="st">&quot;action&quot;</span>&gt;win.new&lt;/<span class="kw">attribute</span>&gt;</span>
<span id="cb4-8"><a href="#cb4-8"></a> &lt;/<span class="kw">item</span>&gt;</span>
<span id="cb4-9"><a href="#cb4-9"></a> &lt;<span class="kw">item</span>&gt;</span>
<span id="cb4-10"><a href="#cb4-10"></a> &lt;<span class="kw">attribute</span><span class="ot"> name=</span><span class="st">&quot;label&quot;</span>&gt;Save As…&lt;/<span class="kw">attribute</span>&gt;</span>
<span id="cb4-11"><a href="#cb4-11"></a> &lt;<span class="kw">attribute</span><span class="ot"> name=</span><span class="st">&quot;action&quot;</span>&gt;win.saveas&lt;/<span class="kw">attribute</span>&gt;</span>
<span id="cb4-12"><a href="#cb4-12"></a> &lt;/<span class="kw">item</span>&gt;</span>
<span id="cb4-13"><a href="#cb4-13"></a> &lt;/<span class="kw">section</span>&gt;</span>
<span id="cb4-14"><a href="#cb4-14"></a> &lt;<span class="kw">section</span>&gt;</span>
<span id="cb4-15"><a href="#cb4-15"></a> &lt;<span class="kw">item</span>&gt;</span>
<span id="cb4-16"><a href="#cb4-16"></a> &lt;<span class="kw">attribute</span><span class="ot"> name=</span><span class="st">&quot;label&quot;</span>&gt;Preference&lt;/<span class="kw">attribute</span>&gt;</span>
<span id="cb4-17"><a href="#cb4-17"></a> &lt;<span class="kw">attribute</span><span class="ot"> name=</span><span class="st">&quot;action&quot;</span>&gt;win.pref&lt;/<span class="kw">attribute</span>&gt;</span>
<span id="cb4-18"><a href="#cb4-18"></a> &lt;/<span class="kw">item</span>&gt;</span>
<span id="cb4-19"><a href="#cb4-19"></a> &lt;/<span class="kw">section</span>&gt;</span>
<span id="cb4-20"><a href="#cb4-20"></a> &lt;<span class="kw">section</span>&gt;</span>
<span id="cb4-21"><a href="#cb4-21"></a> &lt;<span class="kw">item</span>&gt;</span>
<span id="cb4-22"><a href="#cb4-22"></a> &lt;<span class="kw">attribute</span><span class="ot"> name=</span><span class="st">&quot;label&quot;</span>&gt;Quit&lt;/<span class="kw">attribute</span>&gt;</span>
<span id="cb4-23"><a href="#cb4-23"></a> &lt;<span class="kw">attribute</span><span class="ot"> name=</span><span class="st">&quot;action&quot;</span>&gt;win.close-all&lt;/<span class="kw">attribute</span>&gt;</span>
<span id="cb4-24"><a href="#cb4-24"></a> &lt;/<span class="kw">item</span>&gt;</span>
<span id="cb4-25"><a href="#cb4-25"></a> &lt;/<span class="kw">section</span>&gt;</span>
<span id="cb4-26"><a href="#cb4-26"></a> &lt;/<span class="kw">menu</span>&gt;</span>
<span id="cb4-27"><a href="#cb4-27"></a>&lt;/<span class="kw">interface</span>&gt;</span></code></pre></div>
<p>There are four items, “New”, “Saveas”, “Preference” and “Quit”.</p>
<ul>
<li>“New” menu creates a new empty page.</li>
<li>“Saveas” menu saves the current page as a new filename.</li>
<li>“Preference” menu sets preference items. This version of
<code>tfe</code> has only font preference.</li>
<li>“Quit” menu quits the application.</li>
</ul>
<p>These four menus are not used so often. Thats why they are put to
the menu behind the menu button.</p>
<p>The menus and the menu button are connected with
<code>gtk_menu_button_set_menu_model</code> function. The variable
<code>btnm</code> below points a GtkMenuButton object.</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a> build <span class="op">=</span> gtk_builder_new_from_resource <span class="op">(</span><span class="st">&quot;/com/github/ToshioCP/tfe/menu.ui&quot;</span><span class="op">);</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a> menu <span class="op">=</span> G_MENU_MODEL <span class="op">(</span>gtk_builder_get_object <span class="op">(</span>build<span class="op">,</span> <span class="st">&quot;menu&quot;</span><span class="op">));</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a> gtk_menu_button_set_menu_model <span class="op">(</span>btnm<span class="op">,</span> menu<span class="op">);</span></span></code></pre></div>
<h2 id="actions-and-accelerators">Actions and Accelerators</h2>
<p>Menus are connected to actions. Actions are defined with an array and
<code>g_action_map_add_action_entries</code> function.</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a> <span class="dt">const</span> GActionEntry win_entries<span class="op">[]</span> <span class="op">=</span> <span class="op">{</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a> <span class="op">{</span> <span class="st">&quot;open&quot;</span><span class="op">,</span> open_activated<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">,</span> NULL <span class="op">},</span></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a> <span class="op">{</span> <span class="st">&quot;save&quot;</span><span class="op">,</span> save_activated<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">,</span> NULL <span class="op">},</span></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a> <span class="op">{</span> <span class="st">&quot;close&quot;</span><span class="op">,</span> close_activated<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">,</span> NULL <span class="op">},</span></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a> <span class="op">{</span> <span class="st">&quot;new&quot;</span><span class="op">,</span> new_activated<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">,</span> NULL <span class="op">},</span></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a> <span class="op">{</span> <span class="st">&quot;saveas&quot;</span><span class="op">,</span> saveas_activated<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">,</span> NULL <span class="op">},</span></span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a> <span class="op">{</span> <span class="st">&quot;pref&quot;</span><span class="op">,</span> pref_activated<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">,</span> NULL <span class="op">},</span></span>
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a> <span class="op">{</span> <span class="st">&quot;close-all&quot;</span><span class="op">,</span> quit_activated<span class="op">,</span> NULL<span class="op">,</span> NULL<span class="op">,</span> NULL <span class="op">}</span></span>
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true" tabindex="-1"></a> <span class="op">};</span></span>
<span id="cb6-10"><a href="#cb6-10" aria-hidden="true" tabindex="-1"></a> g_action_map_add_action_entries <span class="op">(</span>G_ACTION_MAP <span class="op">(</span>win<span class="op">),</span> win_entries<span class="op">,</span> G_N_ELEMENTS <span class="op">(</span>win_entries<span class="op">),</span> nb<span class="op">);</span></span></code></pre></div>
<p>There are seven actions, open, save, close, new, saveas, pref and
close-all. But there were only four menus. New, saveas, pref and
close-all actions correspond to new, saveas, preference and quit menu
respectively. The three actions open, save and close doesnt have
corresponding menus. Are thy necessary? These actions are defined
because of accelerators.</p>
<p>Accelerators are a kind of short cut key function. They are defined
with arrays and <code>gtk_application_set_accels_for_action</code>
function.</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a> <span class="kw">struct</span> <span class="op">{</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a> <span class="dt">const</span> <span class="dt">char</span> <span class="op">*</span>action<span class="op">;</span></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a> <span class="dt">const</span> <span class="dt">char</span> <span class="op">*</span>accels<span class="op">[</span><span class="dv">2</span><span class="op">];</span></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span> action_accels<span class="op">[]</span> <span class="op">=</span> <span class="op">{</span></span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a> <span class="op">{</span> <span class="st">&quot;win.open&quot;</span><span class="op">,</span> <span class="op">{</span> <span class="st">&quot;&lt;Control&gt;o&quot;</span><span class="op">,</span> NULL <span class="op">}</span> <span class="op">},</span></span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a> <span class="op">{</span> <span class="st">&quot;win.save&quot;</span><span class="op">,</span> <span class="op">{</span> <span class="st">&quot;&lt;Control&gt;s&quot;</span><span class="op">,</span> NULL <span class="op">}</span> <span class="op">},</span></span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a> <span class="op">{</span> <span class="st">&quot;win.close&quot;</span><span class="op">,</span> <span class="op">{</span> <span class="st">&quot;&lt;Control&gt;w&quot;</span><span class="op">,</span> NULL <span class="op">}</span> <span class="op">},</span></span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a> <span class="op">{</span> <span class="st">&quot;win.new&quot;</span><span class="op">,</span> <span class="op">{</span> <span class="st">&quot;&lt;Control&gt;n&quot;</span><span class="op">,</span> NULL <span class="op">}</span> <span class="op">},</span></span>
<span id="cb7-9"><a href="#cb7-9" aria-hidden="true" tabindex="-1"></a> <span class="op">{</span> <span class="st">&quot;win.saveas&quot;</span><span class="op">,</span> <span class="op">{</span> <span class="st">&quot;&lt;Shift&gt;&lt;Control&gt;s&quot;</span><span class="op">,</span> NULL <span class="op">}</span> <span class="op">},</span></span>
<span id="cb7-10"><a href="#cb7-10" aria-hidden="true" tabindex="-1"></a> <span class="op">{</span> <span class="st">&quot;win.close-all&quot;</span><span class="op">,</span> <span class="op">{</span> <span class="st">&quot;&lt;Control&gt;q&quot;</span><span class="op">,</span> NULL <span class="op">}</span> <span class="op">},</span></span>
<span id="cb7-11"><a href="#cb7-11" aria-hidden="true" tabindex="-1"></a> <span class="op">};</span></span>
<span id="cb7-12"><a href="#cb7-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-13"><a href="#cb7-13" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> <span class="op">(</span>i <span class="op">=</span> <span class="dv">0</span><span class="op">;</span> i <span class="op">&lt;</span> G_N_ELEMENTS<span class="op">(</span>action_accels<span class="op">);</span> i<span class="op">++)</span></span>
<span id="cb7-14"><a href="#cb7-14" aria-hidden="true" tabindex="-1"></a> gtk_application_set_accels_for_action<span class="op">(</span>GTK_APPLICATION<span class="op">(</span>app<span class="op">),</span> action_accels<span class="op">[</span>i<span class="op">].</span>action<span class="op">,</span> action_accels<span class="op">[</span>i<span class="op">].</span>accels<span class="op">);</span></span></code></pre></div>
<p>This code is a bit complicated. The array
<code>action-accels[]</code> is an array of structures. The structure
is:</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a> <span class="kw">struct</span> <span class="op">{</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a> <span class="dt">const</span> <span class="dt">char</span> <span class="op">*</span>action<span class="op">;</span></span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a> <span class="dt">const</span> <span class="dt">char</span> <span class="op">*</span>accels<span class="op">[</span><span class="dv">2</span><span class="op">];</span></span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span></code></pre></div>
<p>The member <code>action</code> is a string. The member
<code>accels</code> is an array of two strings. For example,</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="op">{</span> <span class="st">&quot;win.open&quot;</span><span class="op">,</span> <span class="op">{</span> <span class="st">&quot;&lt;Control&gt;o&quot;</span><span class="op">,</span> NULL <span class="op">}</span> <span class="op">},</span></span></code></pre></div>
<p>This is the first element of the array
<code>action_accels</code>.</p>
<ul>
<li>The member <code>action</code> is “win.open”. This specifies the
action “open” belongs to the window object.</li>
<li>The member <code>accels</code> is an array of two strings,
&lt;Control&gt;o” and NULL. The first string specifies a key
combination. Control key and o. If you keep pressing the control key
and push o key, then it activates the action <code>win.open</code>.
The second string NULL (or zero) means the end of the list (array). You
can define more than one accelerator keys and the list must ends with
NULL (zero). If you want to do so, the array length needs to be three or
more. The parser recognizes “&lt;control&gt;o”,
&lt;Shift&gt;&lt;Alt&gt;F2”, “&lt;Ctrl&gt;minus” and so on. If you want
to use symbol key like “&lt;Ctrl&gt;-”, use “&lt;Ctrl&gt;minus” instead.
Such relation between lower case and symbol (its character code) is
specified in <a
href="https://gitlab.gnome.org/GNOME/gtk/-/blob/master/gdk/gdkkeysyms.h"><code>gdkkeysyms.h</code></a>
in the GTK 4 source code.</li>
</ul>
<h2 id="saveas-handler">Saveas handler</h2>
<p>TfeTextView has already had a saveas function. So, only we need to
write is the wrapper function in <code>tfenotebook.c</code>.</p>
<div class="sourceCode" id="cb10"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb10-1"><a href="#cb10-1"></a><span class="dt">static</span> TfeTextView <span class="op">*</span></span>
<span id="cb10-2"><a href="#cb10-2"></a>get_current_textview <span class="op">(</span>GtkNotebook <span class="op">*</span>nb<span class="op">)</span> <span class="op">{</span></span>
<span id="cb10-3"><a href="#cb10-3"></a> <span class="dt">int</span> i<span class="op">;</span></span>
<span id="cb10-4"><a href="#cb10-4"></a> GtkWidget <span class="op">*</span>scr<span class="op">;</span></span>
<span id="cb10-5"><a href="#cb10-5"></a> GtkWidget <span class="op">*</span>tv<span class="op">;</span></span>
<span id="cb10-6"><a href="#cb10-6"></a></span>
<span id="cb10-7"><a href="#cb10-7"></a> i <span class="op">=</span> gtk_notebook_get_current_page <span class="op">(</span>nb<span class="op">);</span></span>
<span id="cb10-8"><a href="#cb10-8"></a> scr <span class="op">=</span> gtk_notebook_get_nth_page <span class="op">(</span>nb<span class="op">,</span> i<span class="op">);</span></span>
<span id="cb10-9"><a href="#cb10-9"></a> tv <span class="op">=</span> gtk_scrolled_window_get_child <span class="op">(</span>GTK_SCROLLED_WINDOW <span class="op">(</span>scr<span class="op">));</span></span>
<span id="cb10-10"><a href="#cb10-10"></a> <span class="cf">return</span> TFE_TEXT_VIEW <span class="op">(</span>tv<span class="op">);</span></span>
<span id="cb10-11"><a href="#cb10-11"></a><span class="op">}</span></span>
<span id="cb10-12"><a href="#cb10-12"></a></span>
<span id="cb10-13"><a href="#cb10-13"></a><span class="dt">void</span></span>
<span id="cb10-14"><a href="#cb10-14"></a>notebook_page_saveas <span class="op">(</span>GtkNotebook <span class="op">*</span>nb<span class="op">)</span> <span class="op">{</span></span>
<span id="cb10-15"><a href="#cb10-15"></a> g_return_if_fail<span class="op">(</span>GTK_IS_NOTEBOOK <span class="op">(</span>nb<span class="op">));</span></span>
<span id="cb10-16"><a href="#cb10-16"></a></span>
<span id="cb10-17"><a href="#cb10-17"></a> TfeTextView <span class="op">*</span>tv<span class="op">;</span></span>
<span id="cb10-18"><a href="#cb10-18"></a></span>
<span id="cb10-19"><a href="#cb10-19"></a> tv <span class="op">=</span> get_current_textview <span class="op">(</span>nb<span class="op">);</span></span>
<span id="cb10-20"><a href="#cb10-20"></a> tfe_text_view_saveas <span class="op">(</span>TFE_TEXT_VIEW <span class="op">(</span>tv<span class="op">));</span></span>
<span id="cb10-21"><a href="#cb10-21"></a><span class="op">}</span></span></code></pre></div>
<p>The function <code>get_current_textview</code> is the same as before.
The function <code>notebook_page_saveas</code> simply calls
<code>tfe_text_view_saveas</code>.</p>
<p>In <code>tfeapplication.c</code>, saveas handler just call
<code>notebook_page_saveas</code>.</p>
<div class="sourceCode" id="cb11"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb11-1"><a href="#cb11-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb11-2"><a href="#cb11-2"></a>saveas_activated <span class="op">(</span>GSimpleAction <span class="op">*</span>action<span class="op">,</span> GVariant <span class="op">*</span>parameter<span class="op">,</span> gpointer user_data<span class="op">)</span> <span class="op">{</span></span>
<span id="cb11-3"><a href="#cb11-3"></a> GtkNotebook <span class="op">*</span>nb <span class="op">=</span> GTK_NOTEBOOK <span class="op">(</span>user_data<span class="op">);</span></span>
<span id="cb11-4"><a href="#cb11-4"></a> notebook_page_saveas <span class="op">(</span>nb<span class="op">);</span></span>
<span id="cb11-5"><a href="#cb11-5"></a><span class="op">}</span></span></code></pre></div>
<h2 id="preference-and-alert-dialog">Preference and alert dialog</h2>
<h3 id="preference-dialog">Preference dialog</h3>
<p>Preference dialog xml definition is added to <code>tfe.ui</code>.</p>
<div class="sourceCode" id="cb12"><pre
class="sourceCode xml"><code class="sourceCode xml"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a>&lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkDialog&quot;</span><span class="ot"> id=</span><span class="st">&quot;pref&quot;</span>&gt;</span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;title&quot;</span>&gt;Preferences&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb12-3"><a href="#cb12-3" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;resizable&quot;</span>&gt;FALSE&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb12-4"><a href="#cb12-4" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;modal&quot;</span>&gt;TRUE&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb12-5"><a href="#cb12-5" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;transient-for&quot;</span>&gt;win&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb12-6"><a href="#cb12-6" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">child</span><span class="ot"> internal-child=</span><span class="st">&quot;content_area&quot;</span>&gt;</span>
<span id="cb12-7"><a href="#cb12-7" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkBox&quot;</span><span class="ot"> id=</span><span class="st">&quot;content_area&quot;</span>&gt;</span>
<span id="cb12-8"><a href="#cb12-8" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">child</span>&gt;</span>
<span id="cb12-9"><a href="#cb12-9" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkBox&quot;</span><span class="ot"> id=</span><span class="st">&quot;pref_boxh&quot;</span>&gt;</span>
<span id="cb12-10"><a href="#cb12-10" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;orientation&quot;</span>&gt;GTK_ORIENTATION_HORIZONTAL&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb12-11"><a href="#cb12-11" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;spacing&quot;</span>&gt;12&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb12-12"><a href="#cb12-12" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;margin-start&quot;</span>&gt;12&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb12-13"><a href="#cb12-13" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;margin-end&quot;</span>&gt;12&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb12-14"><a href="#cb12-14" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;margin-top&quot;</span>&gt;12&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb12-15"><a href="#cb12-15" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;margin-bottom&quot;</span>&gt;12&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb12-16"><a href="#cb12-16" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">child</span>&gt;</span>
<span id="cb12-17"><a href="#cb12-17" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkLabel&quot;</span><span class="ot"> id=</span><span class="st">&quot;fontlabel&quot;</span>&gt;</span>
<span id="cb12-18"><a href="#cb12-18" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;label&quot;</span>&gt;Font:&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb12-19"><a href="#cb12-19" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;xalign&quot;</span>&gt;1&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb12-20"><a href="#cb12-20" aria-hidden="true" tabindex="-1"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb12-21"><a href="#cb12-21" aria-hidden="true" tabindex="-1"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb12-22"><a href="#cb12-22" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">child</span>&gt;</span>
<span id="cb12-23"><a href="#cb12-23" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkFontButton&quot;</span><span class="ot"> id=</span><span class="st">&quot;fontbtn&quot;</span>&gt;</span>
<span id="cb12-24"><a href="#cb12-24" aria-hidden="true" tabindex="-1"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb12-25"><a href="#cb12-25" aria-hidden="true" tabindex="-1"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb12-26"><a href="#cb12-26" aria-hidden="true" tabindex="-1"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb12-27"><a href="#cb12-27" aria-hidden="true" tabindex="-1"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb12-28"><a href="#cb12-28" aria-hidden="true" tabindex="-1"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb12-29"><a href="#cb12-29" aria-hidden="true" tabindex="-1"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb12-30"><a href="#cb12-30" aria-hidden="true" tabindex="-1"></a>&lt;/<span class="kw">object</span>&gt;</span></code></pre></div>
<ul>
<li>Preference dialog is an independent dialog. It is not a descendant
widget of the top-level GtkApplicationwindow <code>win</code>.
Therefore, Theres no child tag that surrounds the dialog object.</li>
<li>There are four properties of the dialog. GtkDialog is a child object
(not child widget) of GtkWindow, so it inherits all the properties from
GtkWindow. Title, resizable, modal and transient-for properties are
inherited from GtkWindow. Transient-for specifies a temporary parent
window, which the dialogs location is based on.</li>
<li>internal-child attribute is used in the child tag above. GtkDialog
has a GtkBox child widget. Its id is “content_area” in
<code>gtkdialog.ui</code>, which is the ui file of GtkDialog. (It is in
the GTK 4 source files.) This box is provided for users to add content
widgets in it. The tag
<code>&lt;child internal-child="content_area"&gt;</code> is put at the
top of the contents. Then you need to specify an object tag and define
its class as GtkBox and its id as content_area. This object is defined
in <code>gtkdialog.ui</code> but you need to define it again in the
child tag.</li>
<li>In the content area, defines GtkBox, GtkLabel and
GtkFontButton.</li>
</ul>
<p>I want the preference dialog to keep alive during the application
lives. So, it is necessary to catch “close-request” signal from the
dialog and stop the signal propagation. This is accomplished by
returning TRUE by the signal handler.</p>
<div class="sourceCode" id="cb13"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a>pref_close_cb <span class="op">(</span>GtkDialog <span class="op">*</span>pref<span class="op">,</span> gpointer user_data<span class="op">)</span> <span class="op">{</span></span>
<span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> TRUE<span class="op">;</span></span>
<span id="cb13-3"><a href="#cb13-3" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span>
<span id="cb13-4"><a href="#cb13-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb13-5"><a href="#cb13-5" aria-hidden="true" tabindex="-1"></a>g_signal_connect <span class="op">(</span>GTK_DIALOG <span class="op">(</span>pref<span class="op">),</span> <span class="st">&quot;close-request&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>pref_close_cb<span class="op">),</span> NULL<span class="op">);</span></span></code></pre></div>
<p>Generally, signal emission consists of five stages.</p>
<ol type="1">
<li>Default handler is invoked if the signals flag is
<code>G_SIGNAL_RUN_FIRST</code>. Default handler is set when a signal is
registered. It is different from user signal handler, simply called
signal handler, connected by <code>g_signal_connect</code>series
function. Default handler can be invoked in either stage 1, 3 or 5. Most
of the default handlers are <code>G_SIGNAL_RUN_FIRST</code> or
<code>G_SIGNAL_RUN_LAST</code>.</li>
<li>Signal handlers are invoked, unless it is connected by
<code>g_signal_connect_after</code>.</li>
<li>Default handler is invoked if the signals flag is
<code>G_SIGNAL_RUN_LAST</code>.</li>
<li>Signal handlers are invoked, if it is connected by
<code>g_signal_connect_after</code>.</li>
<li>Default handler is invoked if the signals flag is
<code>G_SIGNAL_RUN_CLEANUP</code>.</li>
</ol>
<p>In the case of “close-request” signal, the default handlers flag is
<code>G_SIGNAL_RUN_LAST</code>. The handler <code>pref_close_cb</code>
is not connected by <code>g_signal_connect_after</code>. So the number
of stages are two.</p>
<ol type="1">
<li>Signal handler <code>pref_close_cb</code> is invoked.</li>
<li>Default handler is invoked.</li>
</ol>
<p>And If the user signal handler returns TRUE, then other handlers will
be stopped being invoked. Therefore, the program above prevents the
invocation of the default handler and stop the closing process of the
dialog.</p>
<p>The following codes are extracted from
<code>tfeapplication.c</code>.</p>
<div class="sourceCode" id="cb14"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a><span class="dt">static</span> gulong pref_close_request_handler_id <span class="op">=</span> <span class="dv">0</span><span class="op">;</span></span>
<span id="cb14-2"><a href="#cb14-2" aria-hidden="true" tabindex="-1"></a><span class="dt">static</span> gulong alert_close_request_handler_id <span class="op">=</span> <span class="dv">0</span><span class="op">;</span></span>
<span id="cb14-3"><a href="#cb14-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-4"><a href="#cb14-4" aria-hidden="true" tabindex="-1"></a><span class="op">...</span> <span class="op">...</span></span>
<span id="cb14-5"><a href="#cb14-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-6"><a href="#cb14-6" aria-hidden="true" tabindex="-1"></a><span class="dt">static</span> gboolean</span>
<span id="cb14-7"><a href="#cb14-7" aria-hidden="true" tabindex="-1"></a>dialog_close_cb <span class="op">(</span>GtkDialog <span class="op">*</span>dialog<span class="op">,</span> gpointer user_data<span class="op">)</span> <span class="op">{</span></span>
<span id="cb14-8"><a href="#cb14-8" aria-hidden="true" tabindex="-1"></a> gtk_widget_hide <span class="op">(</span>GTK_WIDGET <span class="op">(</span>dialog<span class="op">));</span></span>
<span id="cb14-9"><a href="#cb14-9" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> TRUE<span class="op">;</span></span>
<span id="cb14-10"><a href="#cb14-10" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span>
<span id="cb14-11"><a href="#cb14-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-12"><a href="#cb14-12" aria-hidden="true" tabindex="-1"></a><span class="op">...</span> <span class="op">...</span></span>
<span id="cb14-13"><a href="#cb14-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-14"><a href="#cb14-14" aria-hidden="true" tabindex="-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb14-15"><a href="#cb14-15" aria-hidden="true" tabindex="-1"></a>pref_activated <span class="op">(</span>GSimpleAction <span class="op">*</span>action<span class="op">,</span> GVariant <span class="op">*</span>parameter<span class="op">,</span> gpointer nb<span class="op">)</span> <span class="op">{</span></span>
<span id="cb14-16"><a href="#cb14-16" aria-hidden="true" tabindex="-1"></a> gtk_widget_show <span class="op">(</span>GTK_WIDGET <span class="op">(</span>pref<span class="op">));</span></span>
<span id="cb14-17"><a href="#cb14-17" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span>
<span id="cb14-18"><a href="#cb14-18" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-19"><a href="#cb14-19" aria-hidden="true" tabindex="-1"></a><span class="op">...</span> <span class="op">...</span></span>
<span id="cb14-20"><a href="#cb14-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-21"><a href="#cb14-21" aria-hidden="true" tabindex="-1"></a><span class="co">/* ----- quit application ----- */</span></span>
<span id="cb14-22"><a href="#cb14-22" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span></span>
<span id="cb14-23"><a href="#cb14-23" aria-hidden="true" tabindex="-1"></a>tfe_application_quit <span class="op">(</span>GtkWindow <span class="op">*</span>win<span class="op">)</span> <span class="op">{</span></span>
<span id="cb14-24"><a href="#cb14-24" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>pref_close_request_handler_id <span class="op">&gt;</span> <span class="dv">0</span><span class="op">)</span></span>
<span id="cb14-25"><a href="#cb14-25" aria-hidden="true" tabindex="-1"></a> g_signal_handler_disconnect <span class="op">(</span>pref<span class="op">,</span> pref_close_request_handler_id<span class="op">);</span></span>
<span id="cb14-26"><a href="#cb14-26" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>alert_close_request_handler_id <span class="op">&gt;</span> <span class="dv">0</span><span class="op">)</span></span>
<span id="cb14-27"><a href="#cb14-27" aria-hidden="true" tabindex="-1"></a> g_signal_handler_disconnect <span class="op">(</span>alert<span class="op">,</span> alert_close_request_handler_id<span class="op">);</span></span>
<span id="cb14-28"><a href="#cb14-28" aria-hidden="true" tabindex="-1"></a> g_clear_object <span class="op">(&amp;</span>settings<span class="op">);</span></span>
<span id="cb14-29"><a href="#cb14-29" aria-hidden="true" tabindex="-1"></a> gtk_window_destroy <span class="op">(</span>GTK_WINDOW <span class="op">(</span>alert<span class="op">));</span></span>
<span id="cb14-30"><a href="#cb14-30" aria-hidden="true" tabindex="-1"></a> gtk_window_destroy <span class="op">(</span>GTK_WINDOW <span class="op">(</span>pref<span class="op">));</span></span>
<span id="cb14-31"><a href="#cb14-31" aria-hidden="true" tabindex="-1"></a> gtk_window_destroy <span class="op">(</span>win<span class="op">);</span></span>
<span id="cb14-32"><a href="#cb14-32" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span>
<span id="cb14-33"><a href="#cb14-33" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-34"><a href="#cb14-34" aria-hidden="true" tabindex="-1"></a><span class="op">...</span> <span class="op">...</span></span>
<span id="cb14-35"><a href="#cb14-35" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-36"><a href="#cb14-36" aria-hidden="true" tabindex="-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb14-37"><a href="#cb14-37" aria-hidden="true" tabindex="-1"></a>tfe_startup <span class="op">(</span>GApplication <span class="op">*</span>application<span class="op">)</span> <span class="op">{</span></span>
<span id="cb14-38"><a href="#cb14-38" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-39"><a href="#cb14-39" aria-hidden="true" tabindex="-1"></a> <span class="op">...</span> <span class="op">...</span></span>
<span id="cb14-40"><a href="#cb14-40" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-41"><a href="#cb14-41" aria-hidden="true" tabindex="-1"></a> pref <span class="op">=</span> GTK_DIALOG <span class="op">(</span>gtk_builder_get_object <span class="op">(</span>build<span class="op">,</span> <span class="st">&quot;pref&quot;</span><span class="op">));</span></span>
<span id="cb14-42"><a href="#cb14-42" aria-hidden="true" tabindex="-1"></a> pref_close_request_handler_id <span class="op">=</span> g_signal_connect <span class="op">(</span>GTK_DIALOG <span class="op">(</span>pref<span class="op">),</span> <span class="st">&quot;close-request&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>dialog_close_cb<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb14-43"><a href="#cb14-43" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-44"><a href="#cb14-44" aria-hidden="true" tabindex="-1"></a> <span class="op">...</span> <span class="op">...</span> </span>
<span id="cb14-45"><a href="#cb14-45" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>The function <code>tfe_application_quit</code> destroys top-level
windows and quits the application. It first disconnects the handlers
from the signal “close-request”.</p>
<h3 id="alert-dialog">Alert dialog</h3>
<p>If a user closes a page which hasnt been saved, it is advisable to
show an alert to confirm it. Alert dialog is used in this application
for such a situation.</p>
<div class="sourceCode" id="cb15"><pre
class="sourceCode xml"><code class="sourceCode xml"><span id="cb15-1"><a href="#cb15-1" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkDialog&quot;</span><span class="ot"> id=</span><span class="st">&quot;alert&quot;</span>&gt;</span>
<span id="cb15-2"><a href="#cb15-2" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;title&quot;</span>&gt;Are you sure?&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb15-3"><a href="#cb15-3" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;resizable&quot;</span>&gt;FALSE&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb15-4"><a href="#cb15-4" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;modal&quot;</span>&gt;TRUE&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb15-5"><a href="#cb15-5" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;transient-for&quot;</span>&gt;win&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb15-6"><a href="#cb15-6" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">child</span><span class="ot"> internal-child=</span><span class="st">&quot;content_area&quot;</span>&gt;</span>
<span id="cb15-7"><a href="#cb15-7" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkBox&quot;</span>&gt;</span>
<span id="cb15-8"><a href="#cb15-8" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">child</span>&gt;</span>
<span id="cb15-9"><a href="#cb15-9" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkBox&quot;</span>&gt;</span>
<span id="cb15-10"><a href="#cb15-10" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;orientation&quot;</span>&gt;GTK_ORIENTATION_HORIZONTAL&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb15-11"><a href="#cb15-11" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;spacing&quot;</span>&gt;12&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb15-12"><a href="#cb15-12" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;margin-start&quot;</span>&gt;12&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb15-13"><a href="#cb15-13" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;margin-end&quot;</span>&gt;12&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb15-14"><a href="#cb15-14" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;margin-top&quot;</span>&gt;12&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb15-15"><a href="#cb15-15" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;margin-bottom&quot;</span>&gt;12&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb15-16"><a href="#cb15-16" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">child</span>&gt;</span>
<span id="cb15-17"><a href="#cb15-17" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkImage&quot;</span>&gt;</span>
<span id="cb15-18"><a href="#cb15-18" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;icon-name&quot;</span>&gt;dialog-warning&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb15-19"><a href="#cb15-19" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;icon-size&quot;</span>&gt;GTK_ICON_SIZE_LARGE&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb15-20"><a href="#cb15-20" aria-hidden="true" tabindex="-1"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb15-21"><a href="#cb15-21" aria-hidden="true" tabindex="-1"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb15-22"><a href="#cb15-22" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">child</span>&gt;</span>
<span id="cb15-23"><a href="#cb15-23" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkLabel&quot;</span><span class="ot"> id=</span><span class="st">&quot;lb_alert&quot;</span>&gt;</span>
<span id="cb15-24"><a href="#cb15-24" aria-hidden="true" tabindex="-1"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb15-25"><a href="#cb15-25" aria-hidden="true" tabindex="-1"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb15-26"><a href="#cb15-26" aria-hidden="true" tabindex="-1"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb15-27"><a href="#cb15-27" aria-hidden="true" tabindex="-1"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb15-28"><a href="#cb15-28" aria-hidden="true" tabindex="-1"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb15-29"><a href="#cb15-29" aria-hidden="true" tabindex="-1"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb15-30"><a href="#cb15-30" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">child</span><span class="ot"> type=</span><span class="st">&quot;action&quot;</span>&gt;</span>
<span id="cb15-31"><a href="#cb15-31" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkButton&quot;</span><span class="ot"> id=</span><span class="st">&quot;btn_cancel&quot;</span>&gt;</span>
<span id="cb15-32"><a href="#cb15-32" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;label&quot;</span>&gt;Cancel&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb15-33"><a href="#cb15-33" aria-hidden="true" tabindex="-1"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb15-34"><a href="#cb15-34" aria-hidden="true" tabindex="-1"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb15-35"><a href="#cb15-35" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">child</span><span class="ot"> type=</span><span class="st">&quot;action&quot;</span>&gt;</span>
<span id="cb15-36"><a href="#cb15-36" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">object</span><span class="ot"> class=</span><span class="st">&quot;GtkButton&quot;</span><span class="ot"> id=</span><span class="st">&quot;btn_accept&quot;</span>&gt;</span>
<span id="cb15-37"><a href="#cb15-37" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">property</span><span class="ot"> name=</span><span class="st">&quot;label&quot;</span>&gt;Close&lt;/<span class="kw">property</span>&gt;</span>
<span id="cb15-38"><a href="#cb15-38" aria-hidden="true" tabindex="-1"></a> &lt;/<span class="kw">object</span>&gt;</span>
<span id="cb15-39"><a href="#cb15-39" aria-hidden="true" tabindex="-1"></a> &lt;/<span class="kw">child</span>&gt;</span>
<span id="cb15-40"><a href="#cb15-40" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">action-widgets</span>&gt;</span>
<span id="cb15-41"><a href="#cb15-41" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">action-widget</span><span class="ot"> response=</span><span class="st">&quot;cancel&quot;</span><span class="ot"> default=</span><span class="st">&quot;true&quot;</span>&gt;btn_cancel&lt;/<span class="kw">action-widget</span>&gt;</span>
<span id="cb15-42"><a href="#cb15-42" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">action-widget</span><span class="ot"> response=</span><span class="st">&quot;accept&quot;</span>&gt;btn_accept&lt;/<span class="kw">action-widget</span>&gt;</span>
<span id="cb15-43"><a href="#cb15-43" aria-hidden="true" tabindex="-1"></a> &lt;/<span class="kw">action-widgets</span>&gt;</span>
<span id="cb15-44"><a href="#cb15-44" aria-hidden="true" tabindex="-1"></a> &lt;<span class="kw">signal</span><span class="ot"> name=</span><span class="st">&quot;response&quot;</span><span class="ot"> handler=</span><span class="st">&quot;alert_response_cb&quot;</span><span class="ot"> swapped=</span><span class="st">&quot;NO&quot;</span><span class="ot"> object=</span><span class="st">&quot;nb&quot;</span>&gt;&lt;/<span class="kw">signal</span>&gt;</span>
<span id="cb15-45"><a href="#cb15-45" aria-hidden="true" tabindex="-1"></a> &lt;/<span class="kw">object</span>&gt;</span></code></pre></div>
<p>This ui file describes the alert dialog. Some part are the same as
preference dialog. There are two objects in the content area, GtkImage
and GtkLabel.</p>
<p>GtkImage shows an image. The image can comes from files, resources,
icon theme and so on. The image above displays an icon from the current
icon theme. You can see icons in the theme by
<code>gtk4-icon-browser</code>.</p>
<pre><code>$ gtk4-icon-browser</code></pre>
<p>The icon named “dialog-warning” is something like this.</p>
<figure>
<img src="image/dialog_warning.png"
alt="dialog-warning icon is like …" />
<figcaption aria-hidden="true">dialog-warning icon is like
</figcaption>
</figure>
<p>These are made by my hand. The real image on the alert dialog is
nicer.</p>
<p>The GtkLabel <code>lb_alert</code> has no text yet. An alert message
will be inserted by the program later.</p>
<p>There are two child tags which have “action” type. They are button
objects located in the action area. Action-widgets tag describes the
actions of the buttons. <code>btn_cancel</code> button emits response
signal with cancel response (<code>GTK_RESPONSE_CANCEL</code>) if it is
clicked on. <code>btn_accept</code> button emits response signal with
accept response (<code>GTK_RESPONSE_ACCEPT</code>) if it is clicked on.
The response signal is connected to <code>alert_response_cb</code>
handler.</p>
<p>The alert dialog keeps alive while the application lives. The
“close-request” signal is stopped by the handler
<code>dialog_close_cb</code> like the preference dialog.</p>
<h2 id="close-and-quit-handlers">Close and quit handlers</h2>
<p>If a user closes a page or quits the application without saving the
contents, the application alerts.</p>
<div class="sourceCode" id="cb17"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb17-1"><a href="#cb17-1" aria-hidden="true" tabindex="-1"></a><span class="dt">static</span> gboolean is_quit<span class="op">;</span></span>
<span id="cb17-2"><a href="#cb17-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb17-3"><a href="#cb17-3" aria-hidden="true" tabindex="-1"></a><span class="op">...</span> <span class="op">...</span></span>
<span id="cb17-4"><a href="#cb17-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb17-5"><a href="#cb17-5" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span></span>
<span id="cb17-6"><a href="#cb17-6" aria-hidden="true" tabindex="-1"></a>close_cb <span class="op">(</span>GtkNotebook <span class="op">*</span>nb<span class="op">)</span> <span class="op">{</span></span>
<span id="cb17-7"><a href="#cb17-7" aria-hidden="true" tabindex="-1"></a> is_quit <span class="op">=</span> false<span class="op">;</span></span>
<span id="cb17-8"><a href="#cb17-8" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>has_saved <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">)))</span></span>
<span id="cb17-9"><a href="#cb17-9" aria-hidden="true" tabindex="-1"></a> notebook_page_close <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">));</span></span>
<span id="cb17-10"><a href="#cb17-10" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span> <span class="op">{</span></span>
<span id="cb17-11"><a href="#cb17-11" aria-hidden="true" tabindex="-1"></a> gtk_label_set_text <span class="op">(</span>lb_alert<span class="op">,</span> <span class="st">&quot;Contents aren&#39;t saved yet.</span><span class="sc">\n</span><span class="st">Are you sure to close?&quot;</span><span class="op">);</span></span>
<span id="cb17-12"><a href="#cb17-12" aria-hidden="true" tabindex="-1"></a> gtk_button_set_label <span class="op">(</span>close_btn_close<span class="op">,</span> <span class="st">&quot;Close&quot;</span><span class="op">);</span></span>
<span id="cb17-13"><a href="#cb17-13" aria-hidden="true" tabindex="-1"></a> gtk_widget_show <span class="op">(</span>GTK_WIDGET <span class="op">(</span>alert<span class="op">));</span></span>
<span id="cb17-14"><a href="#cb17-14" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb17-15"><a href="#cb17-15" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span>
<span id="cb17-16"><a href="#cb17-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb17-17"><a href="#cb17-17" aria-hidden="true" tabindex="-1"></a><span class="op">...</span> <span class="op">...</span></span>
<span id="cb17-18"><a href="#cb17-18" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb17-19"><a href="#cb17-19" aria-hidden="true" tabindex="-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb17-20"><a href="#cb17-20" aria-hidden="true" tabindex="-1"></a>close_activated <span class="op">(</span>GSimpleAction <span class="op">*</span>action<span class="op">,</span> GVariant <span class="op">*</span>parameter<span class="op">,</span> gpointer user_data<span class="op">)</span> <span class="op">{</span></span>
<span id="cb17-21"><a href="#cb17-21" aria-hidden="true" tabindex="-1"></a> GtkNotebook <span class="op">*</span>nb <span class="op">=</span> GTK_NOTEBOOK <span class="op">(</span>user_data<span class="op">);</span></span>
<span id="cb17-22"><a href="#cb17-22" aria-hidden="true" tabindex="-1"></a> close_cb <span class="op">(</span>nb<span class="op">);</span></span>
<span id="cb17-23"><a href="#cb17-23" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span>
<span id="cb17-24"><a href="#cb17-24" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb17-25"><a href="#cb17-25" aria-hidden="true" tabindex="-1"></a><span class="op">...</span> <span class="op">...</span></span>
<span id="cb17-26"><a href="#cb17-26" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb17-27"><a href="#cb17-27" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span></span>
<span id="cb17-28"><a href="#cb17-28" aria-hidden="true" tabindex="-1"></a>alert_response_cb <span class="op">(</span>GtkDialog <span class="op">*</span>alert<span class="op">,</span> <span class="dt">int</span> response_id<span class="op">,</span> gpointer user_data<span class="op">)</span> <span class="op">{</span></span>
<span id="cb17-29"><a href="#cb17-29" aria-hidden="true" tabindex="-1"></a> GtkNotebook <span class="op">*</span>nb <span class="op">=</span> GTK_NOTEBOOK <span class="op">(</span>user_data<span class="op">);</span></span>
<span id="cb17-30"><a href="#cb17-30" aria-hidden="true" tabindex="-1"></a> GtkWidget <span class="op">*</span>win <span class="op">=</span> gtk_widget_get_ancestor <span class="op">(</span>GTK_WIDGET <span class="op">(</span>nb<span class="op">),</span> GTK_TYPE_WINDOW<span class="op">);</span></span>
<span id="cb17-31"><a href="#cb17-31" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb17-32"><a href="#cb17-32" aria-hidden="true" tabindex="-1"></a> gtk_widget_hide <span class="op">(</span>GTK_WIDGET <span class="op">(</span>alert<span class="op">));</span></span>
<span id="cb17-33"><a href="#cb17-33" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>response_id <span class="op">==</span> GTK_RESPONSE_ACCEPT<span class="op">)</span> <span class="op">{</span></span>
<span id="cb17-34"><a href="#cb17-34" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>is_quit<span class="op">)</span></span>
<span id="cb17-35"><a href="#cb17-35" aria-hidden="true" tabindex="-1"></a> tfe_application_quit <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">));</span></span>
<span id="cb17-36"><a href="#cb17-36" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span></span>
<span id="cb17-37"><a href="#cb17-37" aria-hidden="true" tabindex="-1"></a> notebook_page_close <span class="op">(</span>nb<span class="op">);</span></span>
<span id="cb17-38"><a href="#cb17-38" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb17-39"><a href="#cb17-39" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span>
<span id="cb17-40"><a href="#cb17-40" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb17-41"><a href="#cb17-41" aria-hidden="true" tabindex="-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb17-42"><a href="#cb17-42" aria-hidden="true" tabindex="-1"></a>quit_activated <span class="op">(</span>GSimpleAction <span class="op">*</span>action<span class="op">,</span> GVariant <span class="op">*</span>parameter<span class="op">,</span> gpointer user_data<span class="op">)</span> <span class="op">{</span></span>
<span id="cb17-43"><a href="#cb17-43" aria-hidden="true" tabindex="-1"></a> GtkNotebook <span class="op">*</span>nb <span class="op">=</span> GTK_NOTEBOOK <span class="op">(</span>user_data<span class="op">);</span></span>
<span id="cb17-44"><a href="#cb17-44" aria-hidden="true" tabindex="-1"></a> GtkWidget <span class="op">*</span>win <span class="op">=</span> gtk_widget_get_ancestor <span class="op">(</span>GTK_WIDGET <span class="op">(</span>nb<span class="op">),</span> GTK_TYPE_WINDOW<span class="op">);</span></span>
<span id="cb17-45"><a href="#cb17-45" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb17-46"><a href="#cb17-46" aria-hidden="true" tabindex="-1"></a> is_quit <span class="op">=</span> true<span class="op">;</span></span>
<span id="cb17-47"><a href="#cb17-47" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>has_saved_all <span class="op">(</span>nb<span class="op">))</span></span>
<span id="cb17-48"><a href="#cb17-48" aria-hidden="true" tabindex="-1"></a> tfe_application_quit <span class="op">(</span>GTK_WINDOW <span class="op">(</span>win<span class="op">));</span></span>
<span id="cb17-49"><a href="#cb17-49" aria-hidden="true" tabindex="-1"></a> <span class="cf">else</span> <span class="op">{</span></span>
<span id="cb17-50"><a href="#cb17-50" aria-hidden="true" tabindex="-1"></a> gtk_label_set_text <span class="op">(</span>lb_alert<span class="op">,</span> <span class="st">&quot;Contents aren&#39;t saved yet.</span><span class="sc">\n</span><span class="st">Are you sure to quit?&quot;</span><span class="op">);</span></span>
<span id="cb17-51"><a href="#cb17-51" aria-hidden="true" tabindex="-1"></a> gtk_button_set_label <span class="op">(</span>btn_accept<span class="op">,</span> <span class="st">&quot;Quit&quot;</span><span class="op">);</span></span>
<span id="cb17-52"><a href="#cb17-52" aria-hidden="true" tabindex="-1"></a> gtk_widget_show <span class="op">(</span>GTK_WIDGET <span class="op">(</span>alert<span class="op">));</span></span>
<span id="cb17-53"><a href="#cb17-53" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb17-54"><a href="#cb17-54" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span>
<span id="cb17-55"><a href="#cb17-55" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb17-56"><a href="#cb17-56" aria-hidden="true" tabindex="-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb17-57"><a href="#cb17-57" aria-hidden="true" tabindex="-1"></a>tfe_startup <span class="op">(</span>GApplication <span class="op">*</span>application<span class="op">)</span> <span class="op">{</span></span>
<span id="cb17-58"><a href="#cb17-58" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb17-59"><a href="#cb17-59" aria-hidden="true" tabindex="-1"></a> <span class="op">...</span> <span class="op">...</span></span>
<span id="cb17-60"><a href="#cb17-60" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb17-61"><a href="#cb17-61" aria-hidden="true" tabindex="-1"></a> alert <span class="op">=</span> GTK_DIALOG <span class="op">(</span>gtk_builder_get_object <span class="op">(</span>build<span class="op">,</span> <span class="st">&quot;alert&quot;</span><span class="op">));</span></span>
<span id="cb17-62"><a href="#cb17-62" aria-hidden="true" tabindex="-1"></a> alert_close_request_handler_id <span class="op">=</span> g_signal_connect <span class="op">(</span>GTK_DIALOG <span class="op">(</span>alert<span class="op">),</span> <span class="st">&quot;close-request&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>dialog_close_cb<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb17-63"><a href="#cb17-63" aria-hidden="true" tabindex="-1"></a> lb_alert <span class="op">=</span> GTK_LABEL <span class="op">(</span>gtk_builder_get_object <span class="op">(</span>build<span class="op">,</span> <span class="st">&quot;lb_alert&quot;</span><span class="op">));</span></span>
<span id="cb17-64"><a href="#cb17-64" aria-hidden="true" tabindex="-1"></a> btn_accept <span class="op">=</span> GTK_BUTTON <span class="op">(</span>gtk_builder_get_object <span class="op">(</span>build<span class="op">,</span> <span class="st">&quot;btn_accept&quot;</span><span class="op">));</span></span>
<span id="cb17-65"><a href="#cb17-65" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb17-66"><a href="#cb17-66" aria-hidden="true" tabindex="-1"></a> <span class="op">...</span> <span class="op">...</span></span>
<span id="cb17-67"><a href="#cb17-67" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb17-68"><a href="#cb17-68" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>The static variable <code>is_quit</code> is true when user tries to
quit the application and false otherwise. When user presses “Ctrl-w”,
<code>close_activated</code> handler is invoked. It just calls
<code>close_cb</code>. When user clicks on the close button,
<code>close_cb</code> handler is invoked.</p>
<p>The handler sets <code>is_quit</code> to false. The function
<code>has_saved</code> returns true if the current page has been saved.
If it is true, it calls <code>notebook_page_close</code> to close the
current page. Otherwise, it sets the message of the dialog and the label
of the button, then shows the alert dialog.</p>
<p>The response signal of the dialog is connected to the handler
<code>alert_response_cb</code>. It hides the dialog first. Then checks
the <code>response_id</code>. If it is <code>GTK_RESPONSE_ACCEPT</code>,
which means user clicked on the close button, then it closes the current
page. Otherwise it does nothing.</p>
<p>When user press “Ctrl-q” or clicked on the quit menu, then
<code>quit_activated</code> handler is invoked. The handler sets
<code>is_quit</code> to true. The function <code>has_saved_all</code>
returns true if all the pages have been saved. If it is true, it calls
<code>tfe_application_quit</code> to quit the application. Otherwise, it
sets the message of the dialog and the label of the button, then shows
the alert dialog.</p>
<p>If the user clicked on the buttons on the alert dialog,
<code>alert_resoponse_cb</code> is invoked. It hides the dialog and
checks the <code>response_id</code>. If it is
<code>GTK_RESPONSE_ACCEPT</code>, which means user clicked on the quit
button, then it calls <code>tfe_application_quit</code> to quit the
application. Otherwise it does nothing.</p>
<p>The static variables <code>alert</code>, <code>lb_alert</code> and
<code>btn_accept</code> are set in the startup handler. And the signal
“close-request” and <code>dialog_close_cb</code> handler are
connected.</p>
<div class="sourceCode" id="cb18"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb18-1"><a href="#cb18-1"></a>gboolean</span>
<span id="cb18-2"><a href="#cb18-2"></a>has_saved <span class="op">(</span>GtkNotebook <span class="op">*</span>nb<span class="op">)</span> <span class="op">{</span></span>
<span id="cb18-3"><a href="#cb18-3"></a> g_return_val_if_fail <span class="op">(</span>GTK_IS_NOTEBOOK <span class="op">(</span>nb<span class="op">),</span> false<span class="op">);</span></span>
<span id="cb18-4"><a href="#cb18-4"></a></span>
<span id="cb18-5"><a href="#cb18-5"></a> TfeTextView <span class="op">*</span>tv<span class="op">;</span></span>
<span id="cb18-6"><a href="#cb18-6"></a> GtkTextBuffer <span class="op">*</span>tb<span class="op">;</span></span>
<span id="cb18-7"><a href="#cb18-7"></a></span>
<span id="cb18-8"><a href="#cb18-8"></a> tv <span class="op">=</span> get_current_textview <span class="op">(</span>nb<span class="op">);</span></span>
<span id="cb18-9"><a href="#cb18-9"></a> tb <span class="op">=</span> gtk_text_view_get_buffer <span class="op">(</span>GTK_TEXT_VIEW <span class="op">(</span>tv<span class="op">));</span></span>
<span id="cb18-10"><a href="#cb18-10"></a> <span class="cf">if</span> <span class="op">(</span>gtk_text_buffer_get_modified <span class="op">(</span>tb<span class="op">))</span></span>
<span id="cb18-11"><a href="#cb18-11"></a> <span class="cf">return</span> false<span class="op">;</span></span>
<span id="cb18-12"><a href="#cb18-12"></a> <span class="cf">else</span></span>
<span id="cb18-13"><a href="#cb18-13"></a> <span class="cf">return</span> true<span class="op">;</span></span>
<span id="cb18-14"><a href="#cb18-14"></a><span class="op">}</span></span>
<span id="cb18-15"><a href="#cb18-15"></a></span>
<span id="cb18-16"><a href="#cb18-16"></a>gboolean</span>
<span id="cb18-17"><a href="#cb18-17"></a>has_saved_all <span class="op">(</span>GtkNotebook <span class="op">*</span>nb<span class="op">)</span> <span class="op">{</span></span>
<span id="cb18-18"><a href="#cb18-18"></a> g_return_val_if_fail <span class="op">(</span>GTK_IS_NOTEBOOK <span class="op">(</span>nb<span class="op">),</span> false<span class="op">);</span></span>
<span id="cb18-19"><a href="#cb18-19"></a></span>
<span id="cb18-20"><a href="#cb18-20"></a> <span class="dt">int</span> i<span class="op">,</span> n<span class="op">;</span></span>
<span id="cb18-21"><a href="#cb18-21"></a> GtkWidget <span class="op">*</span>scr<span class="op">;</span></span>
<span id="cb18-22"><a href="#cb18-22"></a> GtkWidget <span class="op">*</span>tv<span class="op">;</span></span>
<span id="cb18-23"><a href="#cb18-23"></a> GtkTextBuffer <span class="op">*</span>tb<span class="op">;</span></span>
<span id="cb18-24"><a href="#cb18-24"></a></span>
<span id="cb18-25"><a href="#cb18-25"></a> n <span class="op">=</span> gtk_notebook_get_n_pages <span class="op">(</span>nb<span class="op">);</span></span>
<span id="cb18-26"><a href="#cb18-26"></a> <span class="cf">for</span> <span class="op">(</span>i <span class="op">=</span> <span class="dv">0</span><span class="op">;</span> i <span class="op">&lt;</span> n<span class="op">;</span> <span class="op">++</span>i<span class="op">)</span> <span class="op">{</span></span>
<span id="cb18-27"><a href="#cb18-27"></a> scr <span class="op">=</span> gtk_notebook_get_nth_page <span class="op">(</span>nb<span class="op">,</span> i<span class="op">);</span></span>
<span id="cb18-28"><a href="#cb18-28"></a> tv <span class="op">=</span> gtk_scrolled_window_get_child <span class="op">(</span>GTK_SCROLLED_WINDOW <span class="op">(</span>scr<span class="op">));</span></span>
<span id="cb18-29"><a href="#cb18-29"></a> tb <span class="op">=</span> gtk_text_view_get_buffer <span class="op">(</span>GTK_TEXT_VIEW <span class="op">(</span>tv<span class="op">));</span></span>
<span id="cb18-30"><a href="#cb18-30"></a> <span class="cf">if</span> <span class="op">(</span>gtk_text_buffer_get_modified <span class="op">(</span>tb<span class="op">))</span></span>
<span id="cb18-31"><a href="#cb18-31"></a> <span class="cf">return</span> false<span class="op">;</span></span>
<span id="cb18-32"><a href="#cb18-32"></a> <span class="op">}</span></span>
<span id="cb18-33"><a href="#cb18-33"></a> <span class="cf">return</span> true<span class="op">;</span></span>
<span id="cb18-34"><a href="#cb18-34"></a><span class="op">}</span></span></code></pre></div>
<ul>
<li>1-14: <code>has_saved</code> function.</li>
<li>10: The function <code>gtk_text_buffer_get_modified</code> returns
true if the content of the buffer has been modified since the modified
flag had set false. The flag is set to false when:
<ul>
<li>the buffer is created.</li>
<li>the contents of the buffer is replaced</li>
<li>the contents of the buffer is saved to a file.</li>
</ul></li>
<li>11-13: This function returns true if the contents of the current
page has been saved and no modification has been made. It returns false,
if the current page has been modified and hasnt been saved.</li>
<li>16-33: <code>has_saved_all</code> function. This function is similar
to <code>has_saved</code> function. It returns true if all the pages
have been saved. It returns false if at least one page has been modified
since it last had been saved.</li>
</ul>
<h2 id="notebook-page-tab">Notebook page tab</h2>
<p>If you have some pages and edit them together, you might be confused
which file needs to be saved. Common file editors changes the tab when
the contents are modified. GtkTextBuffer provides “modified-changed”
signal to notify the modification.</p>
<div class="sourceCode" id="cb19"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb19-1"><a href="#cb19-1" aria-hidden="true" tabindex="-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb19-2"><a href="#cb19-2" aria-hidden="true" tabindex="-1"></a>notebook_page_build <span class="op">(</span>GtkNotebook <span class="op">*</span>nb<span class="op">,</span> GtkWidget <span class="op">*</span>tv<span class="op">,</span> <span class="dt">char</span> <span class="op">*</span>filename<span class="op">)</span> <span class="op">{</span></span>
<span id="cb19-3"><a href="#cb19-3" aria-hidden="true" tabindex="-1"></a> <span class="op">...</span> <span class="op">...</span></span>
<span id="cb19-4"><a href="#cb19-4" aria-hidden="true" tabindex="-1"></a> g_signal_connect <span class="op">(</span>GTK_TEXT_VIEW <span class="op">(</span>tv<span class="op">),</span> <span class="st">&quot;change-file&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>file_changed_cb<span class="op">),</span> NULL<span class="op">);</span></span>
<span id="cb19-5"><a href="#cb19-5" aria-hidden="true" tabindex="-1"></a> g_signal_connect <span class="op">(</span>tb<span class="op">,</span> <span class="st">&quot;modified-changed&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>modified_changed_cb<span class="op">),</span> tv<span class="op">);</span></span>
<span id="cb19-6"><a href="#cb19-6" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>When a page is built, connect “change-file” and “modified-changed”
signals to <code>file_changed_cb</code> and
<code>modified_changed_cb</code> handlers respectively.</p>
<div class="sourceCode" id="cb20"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb20-1"><a href="#cb20-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb20-2"><a href="#cb20-2"></a>file_changed_cb <span class="op">(</span>TfeTextView <span class="op">*</span>tv<span class="op">)</span> <span class="op">{</span></span>
<span id="cb20-3"><a href="#cb20-3"></a> GtkWidget <span class="op">*</span>nb <span class="op">=</span> gtk_widget_get_ancestor <span class="op">(</span>GTK_WIDGET <span class="op">(</span>tv<span class="op">),</span> GTK_TYPE_NOTEBOOK<span class="op">);</span></span>
<span id="cb20-4"><a href="#cb20-4"></a> GtkWidget <span class="op">*</span>scr<span class="op">;</span></span>
<span id="cb20-5"><a href="#cb20-5"></a> GtkWidget <span class="op">*</span>label<span class="op">;</span></span>
<span id="cb20-6"><a href="#cb20-6"></a> GFile <span class="op">*</span>file<span class="op">;</span></span>
<span id="cb20-7"><a href="#cb20-7"></a> <span class="dt">char</span> <span class="op">*</span>filename<span class="op">;</span></span>
<span id="cb20-8"><a href="#cb20-8"></a></span>
<span id="cb20-9"><a href="#cb20-9"></a> <span class="cf">if</span> <span class="op">(!</span> GTK_IS_NOTEBOOK <span class="op">(</span>nb<span class="op">))</span> <span class="co">/* tv not connected to nb yet */</span></span>
<span id="cb20-10"><a href="#cb20-10"></a> <span class="cf">return</span><span class="op">;</span></span>
<span id="cb20-11"><a href="#cb20-11"></a> file <span class="op">=</span> tfe_text_view_get_file <span class="op">(</span>tv<span class="op">);</span></span>
<span id="cb20-12"><a href="#cb20-12"></a> scr <span class="op">=</span> gtk_widget_get_parent <span class="op">(</span>GTK_WIDGET <span class="op">(</span>tv<span class="op">));</span></span>
<span id="cb20-13"><a href="#cb20-13"></a> <span class="cf">if</span> <span class="op">(</span>G_IS_FILE <span class="op">(</span>file<span class="op">))</span> <span class="op">{</span></span>
<span id="cb20-14"><a href="#cb20-14"></a> filename <span class="op">=</span> g_file_get_basename <span class="op">(</span>file<span class="op">);</span></span>
<span id="cb20-15"><a href="#cb20-15"></a> g_object_unref <span class="op">(</span>file<span class="op">);</span></span>
<span id="cb20-16"><a href="#cb20-16"></a> <span class="op">}</span> <span class="cf">else</span></span>
<span id="cb20-17"><a href="#cb20-17"></a> filename <span class="op">=</span> get_untitled <span class="op">();</span></span>
<span id="cb20-18"><a href="#cb20-18"></a> label <span class="op">=</span> gtk_label_new <span class="op">(</span>filename<span class="op">);</span></span>
<span id="cb20-19"><a href="#cb20-19"></a> gtk_notebook_set_tab_label <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">),</span> scr<span class="op">,</span> label<span class="op">);</span></span>
<span id="cb20-20"><a href="#cb20-20"></a><span class="op">}</span></span>
<span id="cb20-21"><a href="#cb20-21"></a></span>
<span id="cb20-22"><a href="#cb20-22"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb20-23"><a href="#cb20-23"></a>modified_changed_cb <span class="op">(</span>GtkTextBuffer <span class="op">*</span>tb<span class="op">,</span> gpointer user_data<span class="op">)</span> <span class="op">{</span></span>
<span id="cb20-24"><a href="#cb20-24"></a> TfeTextView <span class="op">*</span>tv <span class="op">=</span> TFE_TEXT_VIEW <span class="op">(</span>user_data<span class="op">);</span></span>
<span id="cb20-25"><a href="#cb20-25"></a> GtkWidget <span class="op">*</span>scr <span class="op">=</span> gtk_widget_get_parent <span class="op">(</span>GTK_WIDGET <span class="op">(</span>tv<span class="op">));</span></span>
<span id="cb20-26"><a href="#cb20-26"></a> GtkWidget <span class="op">*</span>nb <span class="op">=</span> gtk_widget_get_ancestor <span class="op">(</span>GTK_WIDGET <span class="op">(</span>tv<span class="op">),</span> GTK_TYPE_NOTEBOOK<span class="op">);</span></span>
<span id="cb20-27"><a href="#cb20-27"></a> GtkWidget <span class="op">*</span>label<span class="op">;</span></span>
<span id="cb20-28"><a href="#cb20-28"></a> <span class="dt">const</span> <span class="dt">char</span> <span class="op">*</span>filename<span class="op">;</span></span>
<span id="cb20-29"><a href="#cb20-29"></a> <span class="dt">char</span> <span class="op">*</span>text<span class="op">;</span></span>
<span id="cb20-30"><a href="#cb20-30"></a></span>
<span id="cb20-31"><a href="#cb20-31"></a> <span class="cf">if</span> <span class="op">(!</span> GTK_IS_NOTEBOOK <span class="op">(</span>nb<span class="op">))</span> <span class="co">/* tv not connected to nb yet */</span></span>
<span id="cb20-32"><a href="#cb20-32"></a> <span class="cf">return</span><span class="op">;</span></span>
<span id="cb20-33"><a href="#cb20-33"></a> <span class="cf">else</span> <span class="cf">if</span> <span class="op">(</span>gtk_text_buffer_get_modified <span class="op">(</span>tb<span class="op">))</span> <span class="op">{</span></span>
<span id="cb20-34"><a href="#cb20-34"></a> filename <span class="op">=</span> gtk_notebook_get_tab_label_text <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">),</span> scr<span class="op">);</span></span>
<span id="cb20-35"><a href="#cb20-35"></a> text <span class="op">=</span> g_strdup_printf <span class="op">(</span><span class="st">&quot;*%s&quot;</span><span class="op">,</span> filename<span class="op">);</span></span>
<span id="cb20-36"><a href="#cb20-36"></a> label <span class="op">=</span> gtk_label_new <span class="op">(</span>text<span class="op">);</span></span>
<span id="cb20-37"><a href="#cb20-37"></a> g_free <span class="op">(</span>text<span class="op">);</span></span>
<span id="cb20-38"><a href="#cb20-38"></a> gtk_notebook_set_tab_label <span class="op">(</span>GTK_NOTEBOOK <span class="op">(</span>nb<span class="op">),</span> scr<span class="op">,</span> label<span class="op">);</span></span>
<span id="cb20-39"><a href="#cb20-39"></a> <span class="op">}</span> <span class="cf">else</span></span>
<span id="cb20-40"><a href="#cb20-40"></a> file_changed_cb <span class="op">(</span>tv<span class="op">);</span></span>
<span id="cb20-41"><a href="#cb20-41"></a><span class="op">}</span></span></code></pre></div>
<ul>
<li>1-20: <code>file_changed_cb</code> handler.</li>
<li>9-10: If the signal emits during the page is being built, it is
possible that <code>tv</code> isnt a descendant of <code>nb</code>.
That is, theres no page corresponds to <code>tv</code>. Then, it isnt
necessary to change the name of the tab because no tab exists.</li>
<li>13-15: If <code>file</code> is GFile, then it gets the filename and
unrefs <code>file</code>.</li>
<li>16-17: Otherwise, <code>file</code> is probably NULL and it assigns
“Untitled” related name to <code>filename</code></li>
<li>18-19: Creates GtkLabel with <code>filename</code> and sets the tab
of the page with the GtkLabel.</li>
<li>22-41: <code>modified_changed_cb</code> handler.</li>
<li>31-32: If <code>tv</code> isnt a descendant of <code>nb</code>,
then nothing needs to be done.</li>
<li>33-35: If the content is modified, then it gets the text of the tab
and adds asterisk at the beginning of the text.</li>
<li>36-38: Sets the tab with the asterisk prepended text.</li>
<li>39-40: Otherwise the modified bit is off. It is because content is
saved. It calls <code>file_changed_cb</code> and resets the filename,
that means it leaves out the asterisk.</li>
</ul>
<h2 id="font">Font</h2>
<h3 id="gtkfontbutton-and-gtkfontchooser">GtkFontButton and
GtkFontChooser</h3>
<p>The GtkFontButton is a button which displays the current font. It
opens a font chooser dialog if a user clicked on the button. A user can
change the font (family, style, weight and size) with the dialog. Then
the button keeps the new font and displays it.</p>
<p>The button and its signal “font-set” is initialized in the
application startup process.</p>
<div class="sourceCode" id="cb21"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb21-1"><a href="#cb21-1" aria-hidden="true" tabindex="-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb21-2"><a href="#cb21-2" aria-hidden="true" tabindex="-1"></a>font_set_cb <span class="op">(</span>GtkFontButton <span class="op">*</span>fontbtn<span class="op">,</span> gpointer user_data<span class="op">)</span> <span class="op">{</span></span>
<span id="cb21-3"><a href="#cb21-3" aria-hidden="true" tabindex="-1"></a> GtkWindow <span class="op">*</span>win <span class="op">=</span> GTK_WINDOW <span class="op">(</span>user_data<span class="op">);</span></span>
<span id="cb21-4"><a href="#cb21-4" aria-hidden="true" tabindex="-1"></a> PangoFontDescription <span class="op">*</span>pango_font_desc<span class="op">;</span></span>
<span id="cb21-5"><a href="#cb21-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb21-6"><a href="#cb21-6" aria-hidden="true" tabindex="-1"></a> pango_font_desc <span class="op">=</span> gtk_font_chooser_get_font_desc <span class="op">(</span>GTK_FONT_CHOOSER <span class="op">(</span>fontbtn<span class="op">));</span></span>
<span id="cb21-7"><a href="#cb21-7" aria-hidden="true" tabindex="-1"></a> set_font_for_display_with_pango_font_desc <span class="op">(</span>win<span class="op">,</span> pango_font_desc<span class="op">);</span></span>
<span id="cb21-8"><a href="#cb21-8" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span>
<span id="cb21-9"><a href="#cb21-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb21-10"><a href="#cb21-10" aria-hidden="true" tabindex="-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb21-11"><a href="#cb21-11" aria-hidden="true" tabindex="-1"></a>tfe_startup <span class="op">(</span>GApplication <span class="op">*</span>application<span class="op">)</span> <span class="op">{</span></span>
<span id="cb21-12"><a href="#cb21-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb21-13"><a href="#cb21-13" aria-hidden="true" tabindex="-1"></a> <span class="op">...</span> <span class="op">...</span></span>
<span id="cb21-14"><a href="#cb21-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb21-15"><a href="#cb21-15" aria-hidden="true" tabindex="-1"></a> fontbtn <span class="op">=</span> GTK_FONT_BUTTON <span class="op">(</span>gtk_builder_get_object <span class="op">(</span>build<span class="op">,</span> <span class="st">&quot;fontbtn&quot;</span><span class="op">));</span></span>
<span id="cb21-16"><a href="#cb21-16" aria-hidden="true" tabindex="-1"></a> g_signal_connect <span class="op">(</span>fontbtn<span class="op">,</span> <span class="st">&quot;font-set&quot;</span><span class="op">,</span> G_CALLBACK <span class="op">(</span>font_set_cb<span class="op">),</span> win<span class="op">);</span></span>
<span id="cb21-17"><a href="#cb21-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb21-18"><a href="#cb21-18" aria-hidden="true" tabindex="-1"></a> <span class="op">...</span> <span class="op">...</span></span>
<span id="cb21-19"><a href="#cb21-19" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb21-20"><a href="#cb21-20" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>In the startup handler, set the variable <code>fontbtn</code> to
point the GtkFontButton object. Then connect the “font-set” signal to
<code>font_set_cb</code> handler. The signal “font-set” is emitted when
the user selects a font.</p>
<p>GtkFontChooser is an interface implemented by GtkFontButton. The
function <code>gtk_font_chooser_get_font_desc</code> gets the
PangoFontDescription of the currently selected font.</p>
<p>Another function <code>gtk_font_chooser_get_font</code> returns a
font name which includes family, style, weight and size. I thought it
might be able to be applied to tfe editor. The font name can be used to
the <code>font</code> property of GtkTextTag as it is. But it cant be
used to the CSS without converting the string to fit. CSS is appropriate
to change the font of entire text in all the buffers. I think GtkTextTag
is less appropriate. If you know a good solution, please post it to <a
href="https://github.com/ToshioCP/Gtk4-tutorial/issues">issue</a> and
let me know.</p>
<p>It takes many codes to set the CSS from the PangoFontDescription so
the task is left to the function
<code>set_font_for_display_with_pango_font_desc</code>.</p>
<h3 id="css-and-pango">CSS and Pango</h3>
<p>A new file <code>css.c</code> is made for functions related to
CSS.</p>
<div class="sourceCode" id="cb22"><pre
class="sourceCode numberSource C numberLines"><code class="sourceCode c"><span id="cb22-1"><a href="#cb22-1"></a><span class="pp">#include </span><span class="im">&quot;tfe.h&quot;</span></span>
<span id="cb22-2"><a href="#cb22-2"></a></span>
<span id="cb22-3"><a href="#cb22-3"></a><span class="dt">void</span></span>
<span id="cb22-4"><a href="#cb22-4"></a>set_css_for_display <span class="op">(</span>GtkWindow <span class="op">*</span>win<span class="op">,</span> <span class="dt">const</span> <span class="dt">char</span> <span class="op">*</span>css<span class="op">)</span> <span class="op">{</span></span>
<span id="cb22-5"><a href="#cb22-5"></a> GdkDisplay <span class="op">*</span>display<span class="op">;</span></span>
<span id="cb22-6"><a href="#cb22-6"></a></span>
<span id="cb22-7"><a href="#cb22-7"></a> display <span class="op">=</span> gtk_widget_get_display <span class="op">(</span>GTK_WIDGET <span class="op">(</span>win<span class="op">));</span></span>
<span id="cb22-8"><a href="#cb22-8"></a> GtkCssProvider <span class="op">*</span>provider <span class="op">=</span> gtk_css_provider_new <span class="op">();</span></span>
<span id="cb22-9"><a href="#cb22-9"></a> gtk_css_provider_load_from_data <span class="op">(</span>provider<span class="op">,</span> css<span class="op">,</span> <span class="op">-</span><span class="dv">1</span><span class="op">);</span></span>
<span id="cb22-10"><a href="#cb22-10"></a> gtk_style_context_add_provider_for_display <span class="op">(</span>display<span class="op">,</span> GTK_STYLE_PROVIDER <span class="op">(</span>provider<span class="op">),</span> GTK_STYLE_PROVIDER_PRIORITY_USER<span class="op">);</span></span>
<span id="cb22-11"><a href="#cb22-11"></a><span class="op">}</span></span>
<span id="cb22-12"><a href="#cb22-12"></a></span>
<span id="cb22-13"><a href="#cb22-13"></a><span class="dt">void</span></span>
<span id="cb22-14"><a href="#cb22-14"></a>set_font_for_display <span class="op">(</span>GtkWindow <span class="op">*</span>win<span class="op">,</span> <span class="dt">const</span> <span class="dt">char</span> <span class="op">*</span>fontfamily<span class="op">,</span> <span class="dt">const</span> <span class="dt">char</span> <span class="op">*</span>fontstyle<span class="op">,</span> <span class="dt">const</span> <span class="dt">char</span> <span class="op">*</span>fontweight<span class="op">,</span> <span class="dt">int</span> fontsize<span class="op">)</span> <span class="op">{</span></span>
<span id="cb22-15"><a href="#cb22-15"></a> <span class="dt">char</span> <span class="op">*</span>textview_css<span class="op">;</span></span>
<span id="cb22-16"><a href="#cb22-16"></a></span>
<span id="cb22-17"><a href="#cb22-17"></a> textview_css <span class="op">=</span> g_strdup_printf <span class="op">(</span><span class="st">&quot;textview {padding: 10px; font-family: </span><span class="sc">\&quot;</span><span class="st">%s</span><span class="sc">\&quot;</span><span class="st">; font-style: %s; font-weight: %s; font-size: %dpt;}&quot;</span><span class="op">,</span></span>
<span id="cb22-18"><a href="#cb22-18"></a> fontfamily<span class="op">,</span> fontstyle<span class="op">,</span> fontweight<span class="op">,</span> fontsize<span class="op">);</span></span>
<span id="cb22-19"><a href="#cb22-19"></a> set_css_for_display <span class="op">(</span>win<span class="op">,</span> textview_css<span class="op">);</span></span>
<span id="cb22-20"><a href="#cb22-20"></a> g_free <span class="op">(</span>textview_css<span class="op">);</span></span>
<span id="cb22-21"><a href="#cb22-21"></a><span class="op">}</span> </span>
<span id="cb22-22"><a href="#cb22-22"></a></span>
<span id="cb22-23"><a href="#cb22-23"></a><span class="dt">void</span></span>
<span id="cb22-24"><a href="#cb22-24"></a>set_font_for_display_with_pango_font_desc <span class="op">(</span>GtkWindow <span class="op">*</span>win<span class="op">,</span> PangoFontDescription <span class="op">*</span>pango_font_desc<span class="op">)</span> <span class="op">{</span></span>
<span id="cb22-25"><a href="#cb22-25"></a> PangoStyle pango_style<span class="op">;</span></span>
<span id="cb22-26"><a href="#cb22-26"></a> PangoWeight pango_weight<span class="op">;</span> </span>
<span id="cb22-27"><a href="#cb22-27"></a> <span class="dt">const</span> <span class="dt">char</span> <span class="op">*</span>family<span class="op">;</span></span>
<span id="cb22-28"><a href="#cb22-28"></a> <span class="dt">const</span> <span class="dt">char</span> <span class="op">*</span>style<span class="op">;</span></span>
<span id="cb22-29"><a href="#cb22-29"></a> <span class="dt">const</span> <span class="dt">char</span> <span class="op">*</span>weight<span class="op">;</span></span>
<span id="cb22-30"><a href="#cb22-30"></a> <span class="dt">int</span> fontsize<span class="op">;</span></span>
<span id="cb22-31"><a href="#cb22-31"></a></span>
<span id="cb22-32"><a href="#cb22-32"></a> family <span class="op">=</span> pango_font_description_get_family <span class="op">(</span>pango_font_desc<span class="op">);</span></span>
<span id="cb22-33"><a href="#cb22-33"></a> pango_style <span class="op">=</span> pango_font_description_get_style <span class="op">(</span>pango_font_desc<span class="op">);</span></span>
<span id="cb22-34"><a href="#cb22-34"></a> <span class="cf">switch</span> <span class="op">(</span>pango_style<span class="op">)</span> <span class="op">{</span></span>
<span id="cb22-35"><a href="#cb22-35"></a> <span class="cf">case</span> PANGO_STYLE_NORMAL<span class="op">:</span></span>
<span id="cb22-36"><a href="#cb22-36"></a> style <span class="op">=</span> <span class="st">&quot;normal&quot;</span><span class="op">;</span></span>
<span id="cb22-37"><a href="#cb22-37"></a> <span class="cf">break</span><span class="op">;</span></span>
<span id="cb22-38"><a href="#cb22-38"></a> <span class="cf">case</span> PANGO_STYLE_ITALIC<span class="op">:</span></span>
<span id="cb22-39"><a href="#cb22-39"></a> style <span class="op">=</span> <span class="st">&quot;italic&quot;</span><span class="op">;</span></span>
<span id="cb22-40"><a href="#cb22-40"></a> <span class="cf">break</span><span class="op">;</span></span>
<span id="cb22-41"><a href="#cb22-41"></a> <span class="cf">case</span> PANGO_STYLE_OBLIQUE<span class="op">:</span></span>
<span id="cb22-42"><a href="#cb22-42"></a> style <span class="op">=</span> <span class="st">&quot;oblique&quot;</span><span class="op">;</span></span>
<span id="cb22-43"><a href="#cb22-43"></a> <span class="cf">break</span><span class="op">;</span></span>
<span id="cb22-44"><a href="#cb22-44"></a> <span class="cf">default</span><span class="op">:</span></span>
<span id="cb22-45"><a href="#cb22-45"></a> style <span class="op">=</span> <span class="st">&quot;normal&quot;</span><span class="op">;</span></span>
<span id="cb22-46"><a href="#cb22-46"></a> <span class="cf">break</span><span class="op">;</span></span>
<span id="cb22-47"><a href="#cb22-47"></a> <span class="op">}</span></span>
<span id="cb22-48"><a href="#cb22-48"></a> pango_weight <span class="op">=</span> pango_font_description_get_weight <span class="op">(</span>pango_font_desc<span class="op">);</span></span>
<span id="cb22-49"><a href="#cb22-49"></a> <span class="cf">switch</span> <span class="op">(</span>pango_weight<span class="op">)</span> <span class="op">{</span></span>
<span id="cb22-50"><a href="#cb22-50"></a> <span class="cf">case</span> PANGO_WEIGHT_THIN<span class="op">:</span></span>
<span id="cb22-51"><a href="#cb22-51"></a> weight <span class="op">=</span> <span class="st">&quot;100&quot;</span><span class="op">;</span></span>
<span id="cb22-52"><a href="#cb22-52"></a> <span class="cf">break</span><span class="op">;</span></span>
<span id="cb22-53"><a href="#cb22-53"></a> <span class="cf">case</span> PANGO_WEIGHT_ULTRALIGHT<span class="op">:</span></span>
<span id="cb22-54"><a href="#cb22-54"></a> weight <span class="op">=</span> <span class="st">&quot;200&quot;</span><span class="op">;</span></span>
<span id="cb22-55"><a href="#cb22-55"></a> <span class="cf">break</span><span class="op">;</span></span>
<span id="cb22-56"><a href="#cb22-56"></a> <span class="cf">case</span> PANGO_WEIGHT_LIGHT<span class="op">:</span></span>
<span id="cb22-57"><a href="#cb22-57"></a> weight <span class="op">=</span> <span class="st">&quot;300&quot;</span><span class="op">;</span></span>
<span id="cb22-58"><a href="#cb22-58"></a> <span class="cf">break</span><span class="op">;</span></span>
<span id="cb22-59"><a href="#cb22-59"></a> <span class="cf">case</span> PANGO_WEIGHT_SEMILIGHT<span class="op">:</span></span>
<span id="cb22-60"><a href="#cb22-60"></a> weight <span class="op">=</span> <span class="st">&quot;350&quot;</span><span class="op">;</span></span>
<span id="cb22-61"><a href="#cb22-61"></a> <span class="cf">break</span><span class="op">;</span></span>
<span id="cb22-62"><a href="#cb22-62"></a> <span class="cf">case</span> PANGO_WEIGHT_BOOK<span class="op">:</span></span>
<span id="cb22-63"><a href="#cb22-63"></a> weight <span class="op">=</span> <span class="st">&quot;380&quot;</span><span class="op">;</span></span>
<span id="cb22-64"><a href="#cb22-64"></a> <span class="cf">break</span><span class="op">;</span></span>
<span id="cb22-65"><a href="#cb22-65"></a> <span class="cf">case</span> PANGO_WEIGHT_NORMAL<span class="op">:</span></span>
<span id="cb22-66"><a href="#cb22-66"></a> weight <span class="op">=</span> <span class="st">&quot;400&quot;</span><span class="op">;</span> <span class="co">/* or &quot;normal&quot; */</span></span>
<span id="cb22-67"><a href="#cb22-67"></a> <span class="cf">break</span><span class="op">;</span></span>
<span id="cb22-68"><a href="#cb22-68"></a> <span class="cf">case</span> PANGO_WEIGHT_MEDIUM<span class="op">:</span></span>
<span id="cb22-69"><a href="#cb22-69"></a> weight <span class="op">=</span> <span class="st">&quot;500&quot;</span><span class="op">;</span></span>
<span id="cb22-70"><a href="#cb22-70"></a> <span class="cf">break</span><span class="op">;</span></span>
<span id="cb22-71"><a href="#cb22-71"></a> <span class="cf">case</span> PANGO_WEIGHT_SEMIBOLD<span class="op">:</span></span>
<span id="cb22-72"><a href="#cb22-72"></a> weight <span class="op">=</span> <span class="st">&quot;600&quot;</span><span class="op">;</span></span>
<span id="cb22-73"><a href="#cb22-73"></a> <span class="cf">break</span><span class="op">;</span></span>
<span id="cb22-74"><a href="#cb22-74"></a> <span class="cf">case</span> PANGO_WEIGHT_BOLD<span class="op">:</span></span>
<span id="cb22-75"><a href="#cb22-75"></a> weight <span class="op">=</span> <span class="st">&quot;700&quot;</span><span class="op">;</span> <span class="co">/* or &quot;bold&quot; */</span></span>
<span id="cb22-76"><a href="#cb22-76"></a> <span class="cf">break</span><span class="op">;</span></span>
<span id="cb22-77"><a href="#cb22-77"></a> <span class="cf">case</span> PANGO_WEIGHT_ULTRABOLD<span class="op">:</span></span>
<span id="cb22-78"><a href="#cb22-78"></a> weight <span class="op">=</span> <span class="st">&quot;800&quot;</span><span class="op">;</span></span>
<span id="cb22-79"><a href="#cb22-79"></a> <span class="cf">break</span><span class="op">;</span></span>
<span id="cb22-80"><a href="#cb22-80"></a> <span class="cf">case</span> PANGO_WEIGHT_HEAVY<span class="op">:</span></span>
<span id="cb22-81"><a href="#cb22-81"></a> weight <span class="op">=</span> <span class="st">&quot;900&quot;</span><span class="op">;</span></span>
<span id="cb22-82"><a href="#cb22-82"></a> <span class="cf">break</span><span class="op">;</span></span>
<span id="cb22-83"><a href="#cb22-83"></a> <span class="cf">case</span> PANGO_WEIGHT_ULTRAHEAVY<span class="op">:</span></span>
<span id="cb22-84"><a href="#cb22-84"></a> weight <span class="op">=</span> <span class="st">&quot;900&quot;</span><span class="op">;</span> <span class="co">/* In PangoWeight definition, the weight is 1000. But CSS allows the weight below 900. */</span></span>
<span id="cb22-85"><a href="#cb22-85"></a> <span class="cf">break</span><span class="op">;</span></span>
<span id="cb22-86"><a href="#cb22-86"></a> <span class="cf">default</span><span class="op">:</span></span>
<span id="cb22-87"><a href="#cb22-87"></a> weight <span class="op">=</span> <span class="st">&quot;normal&quot;</span><span class="op">;</span></span>
<span id="cb22-88"><a href="#cb22-88"></a> <span class="cf">break</span><span class="op">;</span></span>
<span id="cb22-89"><a href="#cb22-89"></a> <span class="op">}</span></span>
<span id="cb22-90"><a href="#cb22-90"></a> fontsize <span class="op">=</span> pango_font_description_get_size <span class="op">(</span>pango_font_desc<span class="op">)</span> <span class="op">/</span> PANGO_SCALE<span class="op">;</span></span>
<span id="cb22-91"><a href="#cb22-91"></a> set_font_for_display <span class="op">(</span>win<span class="op">,</span> family<span class="op">,</span> style<span class="op">,</span> weight<span class="op">,</span> fontsize<span class="op">);</span></span>
<span id="cb22-92"><a href="#cb22-92"></a><span class="op">}</span></span></code></pre></div>
<ul>
<li>3-11: <code>set_css_for_display</code>. This function sets CSS for
GdkDisplay. The content of the function is the same as the part of
startup handler in the previous version of
<code>tfeapplication.c</code>.</li>
<li>13-20: <code>set_font_for_display</code>. This function sets CSS
with font-family, font-style, font-weight and font-size.
<ul>
<li>font-family is a name of a font. For example, sans-serif, monospace,
Helvetica and “Noto Sans” are font-family. It is recommended to quote
font family names that contains white space, digits, or punctuation
characters other than hyphens.</li>
<li>font-style is one of normal, italic and oblique.</li>
<li>font-weight specifies the thickness of a font. It is normal or bold.
It can be specified with a number between 100 and 900. Normal is the
same as 400. Bold is 700.</li>
<li>font-size specifies the size of a font. Small, medium, large and
12pt are font-size.</li>
</ul></li>
<li>17: Makes CSS text. The function <code>g_strdup_printf</code>
creates a new string with printf-like formatting.</li>
<li>23-92: <code>set_font_for_display_with_pango_font_desc</code>. This
function takes out font-family, font-style, font-weight and font-size
from the PangoFontDescription object and calls
<code>set_font</code>for_display`.</li>
<li>32: Gets the font-family of <code>pango_font_desc</code>.</li>
<li>33-47: Gets the font-style of <code>pango_font_desc</code>. The
functions <code>pango_font_description_get_style</code> returns an
enumerated value.</li>
<li>48-89: Gets the font-weight of <code>pango_font_desc</code>. The
function <code>pango_font_description_get_weight</code> returns an
enumerated value. They corresponds to the numbers from 100 to 900.</li>
<li>90: Gets the font-size of <code>pango_font_desc</code>. The function
<code>pango_font_description_get_size</code> returns the size of a font.
The unit of this size is (1/PANGO_SCALE)pt. If the font size is 10pt,
the function returns 10<em>PANGO_SCALE. PANGO_SCALE is defined as 1024.
Therefore, 10</em>PANGO_SCALE is 10240.</li>
<li>91: calls <code>set_font_for_display</code> to set CSS for the
GdkDisplay.</li>
</ul>
<p>For further information, see <a
href="https://docs.gtk.org/Pango/">Pango API Reference</a>.</p>
<h2 id="gsettings">GSettings</h2>
<p>We want to maintain the font data after the application quits. There
are some ways to implement it.</p>
<ul>
<li>Make a configuration file. For example, a text file
“~/.config/tfe/font.cfg” keeps font information.</li>
<li>Use GSettings object. The basic idea of GSettings are similar to
configuration file. Configuration information data is put into a
database file.</li>
</ul>
<p>The coding with GSettings object is simple and easy. However, it is a
bit hard to understand the concept. This subsection describes the
concept first and then how to program it.</p>
<h3 id="gsettings-schema">GSettings schema</h3>
<p>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.</p>
<ul>
<li>A schema has an id. The id must be unique. We often use the same
string as application id, but schema id and application id are
different. You can use different name from application id. Schema id is
a string delimited by periods. For example, “com.github.ToshioCP.tfe” is
a correct schema id.</li>
<li>A schema usually has a path. The path is a location in the database.
Each key is stored under the path. For example, if a key
<code>font</code> is defined with a path
<code>/com/github/ToshioCP/tfe/</code>, the keys location in the
database is <code>/com/github/ToshioCP/tfe/font</code>. Path is a string
begins with and ends with a slash (<code>/</code>). And it is delimited
by slashes.</li>
<li>GSettings save information as key-value style. Key is a string
begins with lower case characters followed by lower case, digit or dash
(<code>-</code>) and ends with lower case or digit. No consecutive
dashes are allowed. Values can be any type. GSettings stores values as
GVariant type, which may contain, for example, integer, double, boolean,
string or complex types like an array. The type of values needs to be
defined in the schema.</li>
<li>A default value needs to be set for each key.</li>
<li>A summery and description can be set for each key optionally.</li>
</ul>
<p>Schemas are described in an XML format. For example,</p>
<div class="sourceCode" id="cb23"><pre
class="sourceCode numberSource xml numberLines"><code class="sourceCode xml"><span id="cb23-1"><a href="#cb23-1"></a><span class="fu">&lt;?xml</span><span class="ot"> version=</span><span class="st">&quot;1.0&quot;</span><span class="ot"> encoding=</span><span class="st">&quot;UTF-8&quot;</span><span class="fu">?&gt;</span></span>
<span id="cb23-2"><a href="#cb23-2"></a>&lt;<span class="kw">schemalist</span>&gt;</span>
<span id="cb23-3"><a href="#cb23-3"></a> &lt;<span class="kw">schema</span><span class="ot"> path=</span><span class="st">&quot;/com/github/ToshioCP/tfe/&quot;</span><span class="ot"> id=</span><span class="st">&quot;com.github.ToshioCP.tfe&quot;</span>&gt;</span>
<span id="cb23-4"><a href="#cb23-4"></a> &lt;<span class="kw">key</span><span class="ot"> name=</span><span class="st">&quot;font&quot;</span><span class="ot"> type=</span><span class="st">&quot;s&quot;</span>&gt;</span>
<span id="cb23-5"><a href="#cb23-5"></a> &lt;<span class="kw">default</span>&gt;&#39;Monospace 12&#39;&lt;/<span class="kw">default</span>&gt;</span>
<span id="cb23-6"><a href="#cb23-6"></a> &lt;<span class="kw">summary</span>&gt;Font&lt;/<span class="kw">summary</span>&gt;</span>
<span id="cb23-7"><a href="#cb23-7"></a> &lt;<span class="kw">description</span>&gt;The font to be used for textview.&lt;/<span class="kw">description</span>&gt;</span>
<span id="cb23-8"><a href="#cb23-8"></a> &lt;/<span class="kw">key</span>&gt;</span>
<span id="cb23-9"><a href="#cb23-9"></a> &lt;/<span class="kw">schema</span>&gt;</span>
<span id="cb23-10"><a href="#cb23-10"></a>&lt;/<span class="kw">schemalist</span>&gt;</span></code></pre></div>
<ul>
<li>4: The type attribute is “s”. It is <a
href="https://docs.gtk.org/glib/struct.VariantType.html#gvariant-type-strings">GLib
API Reference, GVariant Type Strings</a>. Other common types are:
<ul>
<li>“b”: gboolean</li>
<li>“i”: gint32.</li>
<li>“d”: double.</li>
</ul></li>
</ul>
<p>Further information is in <a
href="https://docs.gtk.org/glib/struct.VariantType.html">GLib API
Reference, VarientType</a>.</p>
<h3 id="gsettings-1">gsettings</h3>
<p>First, lets try <code>gsettings</code> application. It is a
configuration tool for GSettings.</p>
<pre><code>$ 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 &quot;gsettings help COMMAND&quot; to get detailed help.</code></pre>
<p>List schemas.</p>
<pre><code>$ 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
... ...
</code></pre>
<p>Each line is an id of a schema. Each schema has a key-value
configuration data. You can see them with list-recursively command.
Lets look at the keys and values of <code>org.gnome.calculator</code>
schema.</p>
<pre><code>$ gsettings list-recursively org.gnome.calculator
org.gnome.calculator source-currency &#39;&#39;
org.gnome.calculator source-units &#39;degree&#39;
org.gnome.calculator button-mode &#39;basic&#39;
org.gnome.calculator target-currency &#39;&#39;
org.gnome.calculator base 10
org.gnome.calculator angle-units &#39;degrees&#39;
org.gnome.calculator word-size 64
org.gnome.calculator accuracy 9
org.gnome.calculator show-thousands false
org.gnome.calculator window-position (122, 77)
org.gnome.calculator refresh-interval 604800
org.gnome.calculator target-units &#39;radian&#39;
org.gnome.calculator precision 2000
org.gnome.calculator number-format &#39;automatic&#39;
org.gnome.calculator show-zeroes false</code></pre>
<p>This schema is used by GNOME Calculator. Run the calculator and
change the mode, then check the schema again.</p>
<pre><code>$ gnome-calculator</code></pre>
<figure>
<img src="image/gnome_calculator_basic.png"
alt="gnome-calculator basic mode" />
<figcaption aria-hidden="true">gnome-calculator basic mode</figcaption>
</figure>
<p>Then, change the mode to advanced and quit.</p>
<figure>
<img src="image/gnome_calculator_advanced.png"
alt="gnome-calculator advanced mode" />
<figcaption aria-hidden="true">gnome-calculator advanced
mode</figcaption>
</figure>
<p>Run gsettings and check whether the value of <code>button-mode</code>
changes.</p>
<pre><code>$ gsettings list-recursively org.gnome.calculator
... ...
org.gnome.calculator button-mode &#39;advanced&#39;
... ...
</code></pre>
<p>Now we know that GNOME Calculator used gsettings and it has set
<code>button-mode</code> key to “advanced”. The value remains even the
calculator quits. So when the calculator is run again, it will appear as
an advanced mode calculator.</p>
<h3 id="glib-compile-schemas">glib-compile-schemas</h3>
<p>GSettings schemas are specified with an XML format. The XML schema
files must have the filename extension <code>.gschema.xml</code>. The
following is the XML schema file for the application
<code>tfe</code>.</p>
<div class="sourceCode" id="cb29"><pre
class="sourceCode numberSource xml numberLines"><code class="sourceCode xml"><span id="cb29-1"><a href="#cb29-1"></a><span class="fu">&lt;?xml</span><span class="ot"> version=</span><span class="st">&quot;1.0&quot;</span><span class="ot"> encoding=</span><span class="st">&quot;UTF-8&quot;</span><span class="fu">?&gt;</span></span>
<span id="cb29-2"><a href="#cb29-2"></a>&lt;<span class="kw">schemalist</span>&gt;</span>
<span id="cb29-3"><a href="#cb29-3"></a> &lt;<span class="kw">schema</span><span class="ot"> path=</span><span class="st">&quot;/com/github/ToshioCP/tfe/&quot;</span><span class="ot"> id=</span><span class="st">&quot;com.github.ToshioCP.tfe&quot;</span>&gt;</span>
<span id="cb29-4"><a href="#cb29-4"></a> &lt;<span class="kw">key</span><span class="ot"> name=</span><span class="st">&quot;font&quot;</span><span class="ot"> type=</span><span class="st">&quot;s&quot;</span>&gt;</span>
<span id="cb29-5"><a href="#cb29-5"></a> &lt;<span class="kw">default</span>&gt;&#39;Monospace 12&#39;&lt;/<span class="kw">default</span>&gt;</span>
<span id="cb29-6"><a href="#cb29-6"></a> &lt;<span class="kw">summary</span>&gt;Font&lt;/<span class="kw">summary</span>&gt;</span>
<span id="cb29-7"><a href="#cb29-7"></a> &lt;<span class="kw">description</span>&gt;The font to be used for textview.&lt;/<span class="kw">description</span>&gt;</span>
<span id="cb29-8"><a href="#cb29-8"></a> &lt;/<span class="kw">key</span>&gt;</span>
<span id="cb29-9"><a href="#cb29-9"></a> &lt;/<span class="kw">schema</span>&gt;</span>
<span id="cb29-10"><a href="#cb29-10"></a>&lt;/<span class="kw">schemalist</span>&gt;</span></code></pre></div>
<p>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.</p>
<ul>
<li>2: The top level element is <code>&lt;schemalist&gt;</code>.</li>
<li>3: schema tag has <code>path</code> and <code>id</code> attributes.
A path determines where the settings are stored in the conceptual global
tree of settings. An id identifies the schema.</li>
<li>4: Key tag has two attributes. Name is the name of the key. Type is
the type of the value of the key and specified with <a
href="https://docs.gtk.org/glib/struct.VariantType.html">GLib API
Reference, VariantType</a>.</li>
<li>5: default value of the key <code>font</code> is
<code>Monospace 12</code>.</li>
<li>6: Summery and description elements describes the key. They are
optional, but it is recommended to add them in the XML file.</li>
</ul>
<p>The XML file is compiled by glib-compile-schemas. When compiling,
<code>glib-compile-schemas</code> 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 <code>gschemas.compiled</code>.
Suppose the XML file above is under <code>tfe6</code> directory.</p>
<pre><code>$ glib-compile-schemas tfe6</code></pre>
<p>Then, <code>gschemas.compiled</code> is generated under
<code>tfe6</code>. When you test your application, set
<code>GSETTINGS_SCHEMA_DIR</code> so that GSettings objet can find
<code>gschemas.compiled</code>.</p>
<pre><code>$ GSETTINGS_SCHEMA_DIR=(the directory gschemas.compiled is located):$GSETTINGS_SCHEMA_DIR (your application name)</code></pre>
<p>This is because GSettings object searches
<code>GSETTINGS_SCHEMA_DIR</code> for
<code>gschemas.compiled</code>.</p>
<p>GSettings object looks for this file by the following process.</p>
<ul>
<li>It searches <code>glib-2.0/schemas</code> subdirectories of all the
directories specified in the environment variable
<code>XDG_DATA_DIRS</code>. Most common directory is
<code>/usr/share/glib-2.0/schemas</code>.</li>
<li>If <code>GSETTINGS_SCHEMA_DIR</code> environment variable is
defined, it searches all the directories specified in the variable.
<code>GSETTINGS_SCHEMA_DIR</code> can specify multiple directories
delimited by colon (:).</li>
</ul>
<p>In the directories above, all the <code>.gschema.xml</code> files are
stored. Therefore, when you install your application, follow the
instruction below to install your schemas.</p>
<ol type="1">
<li>Make <code>.gschema.xml</code> file.</li>
<li>Copy it to one of the directories above. For example,
<code>/usr/local/share/glib-2.0/schemas</code>.</li>
<li>Run <code>glib-compile-schemas</code> on the directory above.</li>
</ol>
<h3 id="meson.build">Meson.build</h3>
<p>Meson provides <code>gnome.compile_schemas</code> method to compile
XML file in the build directory. This is used to test the application.
Write the following to the <code>meson.build</code> file.</p>
<pre><code>gnome.compile_schemas(build_by_default: true, depend_files: &#39;com.github.ToshioCP.tfe.gschema.xml&#39;)</code></pre>
<ul>
<li><code>build_by_default</code>: If it is true, the target will be
build by default.</li>
<li><code>depend_files</code>: XML files to be compiled.</li>
</ul>
<p>In the example above, this method runs
<code>glib-compile-schemas</code> to generate
<code>gschemas.compiled</code> from the XML file
<code>com.github.ToshioCP.tfe.gschema.xml</code>. The file
<code>gschemas.compiled</code> is located under the build directory. If
you run meson as <code>meson _build</code> and ninja as
<code>ninja -C _build</code>, then it is under <code>_build</code>
directory.</p>
<p>After compilation, you can test your application like this:</p>
<pre><code>$ GSETTINGS_SCHEMA_DIR=_build:$GSETTINGS_SCHEMA_DIR _build/tfe</code></pre>
<h3 id="gsettings-object-and-g_settings_bind">GSettings object and
g_settings_bind</h3>
<p>Write gsettings related codes to `tfeapplication.c.</p>
<div class="sourceCode" id="cb34"><pre class="sourceCode c"><code class="sourceCode c"><span id="cb34-1"><a href="#cb34-1" aria-hidden="true" tabindex="-1"></a><span class="op">...</span> <span class="op">...</span></span>
<span id="cb34-2"><a href="#cb34-2" aria-hidden="true" tabindex="-1"></a><span class="dt">static</span> GSettings <span class="op">*</span>settings<span class="op">;</span></span>
<span id="cb34-3"><a href="#cb34-3" aria-hidden="true" tabindex="-1"></a><span class="op">...</span> <span class="op">...</span></span>
<span id="cb34-4"><a href="#cb34-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-5"><a href="#cb34-5" aria-hidden="true" tabindex="-1"></a><span class="dt">void</span></span>
<span id="cb34-6"><a href="#cb34-6" aria-hidden="true" tabindex="-1"></a>tfe_application_quit <span class="op">(</span>GtkWindow <span class="op">*</span>win<span class="op">)</span> <span class="op">{</span></span>
<span id="cb34-7"><a href="#cb34-7" aria-hidden="true" tabindex="-1"></a> <span class="op">...</span> <span class="op">...</span></span>
<span id="cb34-8"><a href="#cb34-8" aria-hidden="true" tabindex="-1"></a> g_clear_object <span class="op">(&amp;</span>settings<span class="op">);</span></span>
<span id="cb34-9"><a href="#cb34-9" aria-hidden="true" tabindex="-1"></a> <span class="op">...</span> <span class="op">...</span></span>
<span id="cb34-10"><a href="#cb34-10" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span>
<span id="cb34-11"><a href="#cb34-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb34-12"><a href="#cb34-12" aria-hidden="true" tabindex="-1"></a><span class="dt">static</span> <span class="dt">void</span></span>
<span id="cb34-13"><a href="#cb34-13" aria-hidden="true" tabindex="-1"></a>tfe_startup <span class="op">(</span>GApplication <span class="op">*</span>application<span class="op">)</span> <span class="op">{</span></span>
<span id="cb34-14"><a href="#cb34-14" aria-hidden="true" tabindex="-1"></a> <span class="op">...</span> <span class="op">...</span></span>
<span id="cb34-15"><a href="#cb34-15" aria-hidden="true" tabindex="-1"></a> settings <span class="op">=</span> g_settings_new <span class="op">(</span><span class="st">&quot;com.github.ToshioCP.tfe&quot;</span><span class="op">);</span></span>
<span id="cb34-16"><a href="#cb34-16" aria-hidden="true" tabindex="-1"></a> g_settings_bind <span class="op">(</span>settings<span class="op">,</span> <span class="st">&quot;font&quot;</span><span class="op">,</span> fontbtn<span class="op">,</span> <span class="st">&quot;font&quot;</span><span class="op">,</span> G_SETTINGS_BIND_DEFAULT<span class="op">);</span></span>
<span id="cb34-17"><a href="#cb34-17" aria-hidden="true" tabindex="-1"></a> <span class="op">...</span> <span class="op">...</span></span>
<span id="cb34-18"><a href="#cb34-18" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p>Static variable <code>settings</code> keeps a pointer to GSettings
instance. Before application quits, the application releases the
GSettings instance. The function <code>g_clear_object</code> is
used.</p>
<p>Startup handler creates GSettings instance with the schema id
“com.github.ToshioCP.tfe” and assigns the pointer to
<code>settings</code>. The function <code>g_settings_bind</code>
connects the settings keys (key and value) and the “font” property of
<code>fontbtn</code>. Then the two values will be always the same. If
one value changes then the other will automatically change.</p>
<p>You need to make an effort to understand GSettings concept, but
coding is very simple. Just create a GSettings object and bind it to a
property of an object.</p>
<h2 id="installation">Installation</h2>
<p>It is a good idea to install your application in
<code>$HOME/local/bin</code> directory if you have installed GTK 4 from
the source (See Section 2). Then you need to put
<code>--prefix=$HOME/local</code> option to meson like this.</p>
<pre><code>$ meson --prefix=$HOME/local _build</code></pre>
<p>If youve installed GTK 4 from the distribution package,
<code>--prefix</code> option isnt necessary. You just install
<code>tfe</code> to the default bin directory like
<code>/usr/local/bin</code>.</p>
<p>Modify <code>meson.build</code> and add install option and set it
true in executable function.</p>
<pre><code>executable(&#39;tfe&#39;, sourcefiles, resources, dependencies: gtkdep, export_dynamic: true, install: true)</code></pre>
<p>You can install your application by:</p>
<pre><code>$ ninja -C _build install</code></pre>
<p>However, you need to do one more thing. Copy your XML file to
<code>$HOME/local/share/glib-2.0/schemas/</code>, which is specified in
<code>GSETTINGS_SCHEMA_DIR</code> environment variable, and run
<code>glib-compile-schemas</code> on that directory.</p>
<pre><code>schema_dir = get_option(&#39;prefix&#39;) / get_option(&#39;datadir&#39;) / &#39;glib-2.0/schemas/&#39;
install_data(&#39;com.github.ToshioCP.tfe.gschema.xml&#39;, install_dir: schema_dir)</code></pre>
<ul>
<li>get_option: This function returns the value of build options. The
default value of the option prefix is “/usr/local”, but it is
“$HOME/local” because we have run meson with prefix option. The default
value of the option datadir is “share”. The operator / connects the
strings with / separator. So,
<code>$HOME/local/share/glib-2.0/schemas</code> is assigned to the
variable <code>schema_dir</code>.</li>
<li>install_data: This function installs the data to the install
directory.</li>
</ul>
<p>Meson can run a post compile script.</p>
<pre><code>meson.add_install_script(&#39;glib-compile-schemas&#39;, schema_dir)</code></pre>
<p>This method runs glib-compile-schemas with an argument
<code>schema_dir</code>. The following is <code>meson.build</code>.</p>
<div class="sourceCode" id="cb40"><pre
class="sourceCode numberSource numberLines"><code class="sourceCode"><span id="cb40-1"><a href="#cb40-1"></a>project(&#39;tfe&#39;, &#39;c&#39;)</span>
<span id="cb40-2"><a href="#cb40-2"></a></span>
<span id="cb40-3"><a href="#cb40-3"></a>gtkdep = dependency(&#39;gtk4&#39;)</span>
<span id="cb40-4"><a href="#cb40-4"></a></span>
<span id="cb40-5"><a href="#cb40-5"></a>gnome=import(&#39;gnome&#39;)</span>
<span id="cb40-6"><a href="#cb40-6"></a>resources = gnome.compile_resources(&#39;resources&#39;,&#39;tfe.gresource.xml&#39;)</span>
<span id="cb40-7"><a href="#cb40-7"></a>gnome.compile_schemas(build_by_default: true, depend_files: &#39;com.github.ToshioCP.tfe.gschema.xml&#39;)</span>
<span id="cb40-8"><a href="#cb40-8"></a></span>
<span id="cb40-9"><a href="#cb40-9"></a>sourcefiles=files(&#39;tfeapplication.c&#39;, &#39;tfenotebook.c&#39;, &#39;css.c&#39;, &#39;../tfetextview/tfetextview.c&#39;)</span>
<span id="cb40-10"><a href="#cb40-10"></a></span>
<span id="cb40-11"><a href="#cb40-11"></a>executable(&#39;tfe&#39;, sourcefiles, resources, dependencies: gtkdep, export_dynamic: true, install: true)</span>
<span id="cb40-12"><a href="#cb40-12"></a></span>
<span id="cb40-13"><a href="#cb40-13"></a>schema_dir = get_option(&#39;prefix&#39;) / get_option(&#39;datadir&#39;) / &#39;glib-2.0/schemas/&#39;</span>
<span id="cb40-14"><a href="#cb40-14"></a>install_data(&#39;com.github.ToshioCP.tfe.gschema.xml&#39;, install_dir: schema_dir)</span>
<span id="cb40-15"><a href="#cb40-15"></a>meson.add_install_script(&#39;glib-compile-schemas&#39;, schema_dir)</span></code></pre></div>
<p>Source files of <code>tfe</code> is under src/tfe6 directory. Copy
them to your temporary directory and try to compile and install.</p>
<pre><code>$ meson --prefix=$HOME/local _build
$ ninja -C _build
$ GSETTINGS_SCHEMA_DIR=_build:$GSETTINGS_SCHEMA_DIR _build/tfe
$ ninja -C _build install
$ tfe
$ ls $HOME/local/bin
... ...
... tfe
... ...
$ ls $HOME/local/share/glib-2.0/schemas
com.github.ToshioCP.tfe.gschema.xml
gschema.dtd
gschemas.compiled
... ...</code></pre>
<p>The screenshot is as follows.</p>
<figure>
<img src="image/tfe6.png" alt="tfe6" />
<figcaption aria-hidden="true">tfe6</figcaption>
</figure>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
</body>
</html>